【数据处理】利用PCA来简化数据

降维的作用

①数据在低维下更容易处理、更容易使用;

②相关特征,特别是重要特征更能在数据中明确的显示出来;如果只有两维或者三维的话,更便于可视化展示;

③去除数据噪声;

④降低算法开销。

在本次实验中首先学习广泛使用的主成分分析的降维技术PCA;然后通过python语言对其实现和测试;最后通过一个数据集的例子来展示和掌握PCA的工作过程,经过PCA处理后,该数据集就从590个特征降低到了6个特征。

本实验代码链接:https://github.com/ZLT0309/Python_PCA

前导知识

PCA 可以从数据中识别其主要特征,它是通过沿着数据最大方差方向旋转坐标轴来实现的。选择方差最大的方向作为第一条坐标轴,后续坐标轴则与前面的坐标轴正交。协方差矩阵上的特征值分析可以用一系列的正交坐标轴来获取。

参考资料:

https://blog.csdn.net/program_developer/article/details/80632779(精)

http://blog.codinglabs.org/articles/pca-tutorial.html

https://www.cnblogs.com/XDU‐Lakers/p/11612094.html

https://www.cnblogs.com/houzichiguodong/p/9295998.html

线性映射(或线性变换),简单的来说就是将高维空间数据投影到低维空间上,那么在数据分析上,我们是将数据的主成分(包含信息量大的维度)保留下来,忽略掉对数据描述不重要的成分。即将主成分维度组成的向量空间作为低维空间,将高维数据投影到这个空间上就完成了降维的工作。

在 PCA 中,数据从原来的坐标系转换到了新的坐标系,新坐标系的选择是由数据本身决定的。第一个新坐标轴选择的是原始数据中方差最大的方向,第二个新坐标轴的选择和第一个坐标轴正交且具有最大方差的方向。该过程一直重复,重复次数为原始数据中特征的数目。我们会发现,大部分方差都包含在最前面的几个新坐标轴中。因此,我们可以忽略余下的坐标轴,即对数据进行了降维处理。

工作原理

①找出第一个主成分的方向,也就是数据 方差最大 的方向。

②找出第二个主成分的方向,也就是数据 方差次大 的方向,并且该方向与第一个主成分方向正交(果是二维空间就叫垂直)。

③通过这种方式计算出所有的主成分方向。

④通过数据集的协方差矩阵及其特征值分析,我们就可以得到这些主成分的值。

⑤一旦得到了协方差矩阵的特征值和特征向量,我们就可以保留最大的 N 个特征。这些特征向量也给出了 N 个最重要特征的真实结构,我们就可以通过将数据乘上这 N 个特征向量 从而将它转换到新的空间上。

实验内容

1. 在NumPy 中实现PCA

将数据转换成前N 个主成分的伪代码大致如下(pca 函数):

(1) 去除平均值 (第2 行)

(2) 计算协方差矩阵 (第3 行)

(3) 计算协方差矩阵的特征值和特征向量 (第4 行)

(4) 将特征值从大到小排序 (第5 行)

(5) 保留最上面的N 个特征向量

(6) 将数据转换到上述N 个特征向量构建的新空间中

建立一个名为pca.py 的文件,并将下列代码加入用于计算PCA:

def loadDataSet(filename, delim='\t'):
    fr = open(filename)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    datArr = [list(map(float, line)) for line in stringArr]
    return mat(datArr)

def pca(dataMat, topNFeat=9999999):
    meanVals = mean(dataMat,axis=0)
    meanRemoved = dataMat - meanVals
    covMat = cov(meanRemoved, rowvar=0)
    eigVals,eigVects = linalg.eig(mat(covMat))
    eigValInd = argsort(eigVals)
    eigValInd = eigValInd[:-(topNFeat+1):-1]
    redEigVects = eigVects[:, eigValInd]
    lowDDataMat = meanRemoved*redEigVects
    reconMat = (lowDDataMat * redEigVects.T) + meanVals
    return lowDDataMat, reconMat

函数解析: PCA 函数有两个参数,第一参数用于进行PCA 操作的数据集,第二个参数topNfeat 则是一个可选参数,即应用的N 个特征。如果不指定该值,那么函数就会返回前9999 999 个特征 或者 原始数据中的全部特征。下面是详细的注释。

再建立一个名为 testpca.py,用testSet.txt 作为数据集,里面加入了1000 个数据点(每行一个点,对应 x y 值), 在该数据集上进行PCA 操作:

import pca
import matplotlib
import matplotlib.pyplot as plt
from numpy import *

dataMat = pca.loadDataSet('testSet.txt')
print(dataMat)

lowDMat, reconMat = pca.pca(dataMat,1) #变一维

print(shape(lowDMat))
print(shape(reconMat))

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(dataMat[:,0].flatten().A[0], dataMat[:,1].flatten().A[0],marker='^',s=90)
ax.scatter(reconMat[:,0].flatten().A[0], reconMat[:,1].flatten().A[0],marker='o',s=50,c='red')
plt.show()

运行结果:

使用下面代码替换PCA 调用,并重新运行

lowDMat, reconMat = pca.pca(dataMat,2) #变二维

运行结果:

解析:因为没有剔除任何特征(降维后仍然是2),那么重构之后的数据会和原始的数据重合。

Matplotlib 实战:基于testSet3.txt 的三维数据集,把上面的代码重新运行,第一次降维为2 ,第二次降维为1, 并使用Matplotlib 依照上面的代码把降维前和降维后的数据分别画出图来。

dataMat = pca.loadDataSet('testSet3.txt')
print(dataMat)

from mpl_toolkits.mplot3d import Axes3D
fig=plt.figure()
ax1 = Axes3D(fig)
ax1.scatter3D(dataMat[:,0].flatten().A[0], dataMat[:,1].flatten().A[0],dataMat[:,2].flatten().A[0], cmap='Blues')  #绘制散点图
plt.show()

lowDMat2, reconMat2 = pca.pca(dataMat,2) #变二维
lowDMat, reconMat = pca.pca(lowDMat2,1) #变一维
print(shape(lowDMat))
print(shape(lowDMat2))
print(shape(reconMat))
print(shape(reconMat2))

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(lowDMat2[:,0].flatten().A[0],lowDMat2[:,1].flatten().A[0],marker='^',s=90)
ax.scatter(reconMat[:,0].flatten().A[0], reconMat[:,1].flatten().A[0],marker='o',s=50,c='red')

plt.show()

print(reconMat)

运行结果:

 

2.利用PCA 对半导体制造数据降维

半导体是在一些极为先进的工厂中制造出来的。如果制造过程中存在瑕疵,我们就必须尽早发现,从而确保宝贵的时间不会花费在缺陷产品的生产上。一些工程上的通用解决方案是通过早期测试和频繁测试来发现有缺陷的产品,但仍然有一些存在瑕疵的产品通过了测试。如果机器学习技术能够用于进一步减少错误,那么它就会为制造商节省大量的资金。

Secom.data 拥有590 个特征,但是该数据包含很多的缺失值。这些缺失值是以NaN(Not a Number 的缩写)标识的。下面我们用平均值来代替缺失值,平均值根据那些非NaN 得到。

将下面代码添加到 pca.py 中,这样该文件就包含三个函数。

def replaceNanWithMean():
    datMat = loadDataSet('secom.data',' ')
    numFeat = shape(datMat)[1]
    for i in range(numFeat):
        meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0],i])
        datMat[nonzero(isnan(datMat[:,i].A))[0],i] = meanVal
    return datMat
    

再建立一个名为 updatesecom.py , 对数据集文件 secom.txt 补充NaN 的基础上进行特征值分析:

from pca import *
from numpy import *

dataMat = replaceNanWithMean()
meanVals = mean(dataMat,axis=0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved,rowvar=0)
eigVals, eigVects = linalg.eig(mat(covMat))

print(shape(eigVals))
print(eigVals)

print(shape(eigVects))
print(eigVects)

我们会看到一大堆值,但是其中的什么会引起我们的注意?我们会发现其中很多值都是0 吗?实际上,其中有超过20%的特征值都是0。这就意味着这些特征都是其他特征的副本,也就是说,它们可以通过其他特征来表示,而本身并没有提供额外的信息。

接下来,我们了解一下部分数值的数量级。最前面15 个值的数量级大于10 5 ,实际上那以后的值都变得非常小。这就相当于告诉我们只有部分重要特征,重要特征的数目也很快就会下降。

最后,我们可能会注意到有一些小的负值,它们主要源自数值误差应该四舍五入成0。

Matplotlib 实战:计算主成分所占方差比

请在上面代码的基础上继续进行代码编写,实现 对 eigVals 绘制如下图形:

import matplotlib.pyplot as plt

Var = eigVals
Var_sum = sum(Var)
Var_rate = Var/Var_sum
plt.plot(Var_rate[:20],'s-')
plt.show()

运行结果:

Matplotlib 实战:计算主成分累计方差百分比

Var = eigVals
Var_sum = sum(Var)
Var_add = zeros_like(Var)
for i in range(len(Var)):
    Var_add[i] = sum(Var[:i+1])/Var_sum
plt.plot(Var_add[:20],'s-')
plt.show()

运行结果:

从上图中可以看到:前六个主成分就覆盖了数据96.8%的方差,而前20 个主成分覆盖了99.3% 的方差。这就表明了,如果保留前6 个而去除后584 个主成分,我们就可以实现大概100∶1 的压缩比。另外,由于舍弃了噪声的主成分,将后面的成分去除便使得数据更加干净。
可以使用能包含 90%信息量的主成分数量,而也可以使用前20 个主成分。我们无法精确知道所需要的主成分数目,必须通过在实验中取不同的值来确定。有的主成分数目则取决于数据集和具体应用。
上述分析能够得到所用到的主成分数目,然后我们可以将该数目输入到PCA算法中,最后得到约简后数据就可以在分类器中使用了。

接下来的实战:实现 testsecom.py 

dataMat = replaceNanWithMean()

在这个基础上,分别 pca(dataMat,6) 和 pca(dataMat,20) 打印输出看一下降维后数据

lowDMat, reconMat = pca(dataMat,6) 
print(lowDMat)
print(reconMat)

lowDMat, reconMat = pca(dataMat,20) 
print(lowDMat)
print(reconMat)

运行结果:

最后的实战:

不调用我们自己的 pca 函数,而是直接调用from sklearn.decomposition import PCA来重新改写 testpca.py 和 testsecom.py 的功能。

from sklearn.decomposition import PCA
import matplotlib
import matplotlib.pyplot as plt
from numpy import *
import numpy as np

def loadDataSet(filename, delim='\t'):
    fr = open(filename)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    datArr = [list(map(float, line)) for line in stringArr]
    return mat(datArr)

dataMat = loadDataSet('testSet3.txt')
print(shape(dataMat))

from mpl_toolkits.mplot3d import Axes3D
fig=plt.figure()
ax1 = Axes3D(fig)
ax1.scatter3D(dataMat[:,0].flatten().A[0], dataMat[:,1].flatten().A[0],dataMat[:,2].flatten().A[0], cmap='Blues')  #绘制散点图
plt.show()

pca = PCA(n_components=2)
pca.fit(dataMat)
new_data=pca.transform(dataMat)


print(shape(new_data))

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(new_data[:,0], new_data[:,1],marker='^',s=90)
plt.show()

#在很多应用中,当我们将数据降维并用于训练后,训练出来的模型之后的输出也是降维后的数据,
#需要还原回原始维度。这时候需要将pca算法进行逆运算:
old_data=np.dot(new_data,pca.components_)+pca.mean_
fig=plt.figure()
ax1 = Axes3D(fig)
ax1.scatter3D(old_data[:,0],old_data[:,1],old_data[:,2], cmap='Blues')  #绘制散点图
plt.show()

pca = PCA(n_components=1)
pca.fit(dataMat)
new_data=pca.transform(dataMat)


print(shape(new_data))


#在很多应用中,当我们将数据降维并用于训练后,训练出来的模型之后的输出也是降维后的数据,
#需要还原回原始维度。这时候需要将pca算法进行逆运算:
old_data=np.dot(new_data,pca.components_)+pca.mean_
fig=plt.figure()
ax1 = Axes3D(fig)
ax1.scatter3D(old_data[:,0],old_data[:,1],old_data[:,2], cmap='Blues')  #绘制散点图
plt.show()

运行结果:

testsecom.py:

from sklearn.decomposition import PCA
import matplotlib
import matplotlib.pyplot as plt
from numpy import *
import numpy as np

def loadDataSet(filename, delim='\t'):
    fr = open(filename)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    datArr = [list(map(float, line)) for line in stringArr]
    return mat(datArr)

def replaceNanWithMean():
    datMat = loadDataSet('secom.data',' ')
    numFeat = shape(datMat)[1]
    for i in range(numFeat):
        meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0],i])
        datMat[nonzero(isnan(datMat[:,i].A))[0],i] = meanVal
    return datMat

dataMat = replaceNanWithMean()

pca = PCA(n_components=6)
pca.fit(dataMat)
new_data=pca.transform(dataMat)

print(shape(new_data))
print(new_data)

pca = PCA(n_components=20)
pca.fit(dataMat)
new_data=pca.transform(dataMat)

print(shape(new_data))
print(new_data)

运行结果:

(1567, 6)
[[-5183.89616507 -3022.64772377   688.38624272    57.92893141
    349.28054492   -41.19822567]
 [-1866.69728394 -4021.63902468 -1505.57352582   199.23992427
   -640.2312599    -46.32542625]
 [-3154.74165413 -3461.98581552 -1855.44207771  -153.33360802
   -139.53362845   -29.23225585]
 ...
 [-3821.21714302  -157.30328822 -1198.46485098   -15.13555733
    479.52922351   -53.46153759]
 [-4271.04023715 -1300.47276359   381.63452019   298.64738407
    757.62307955   -51.07231854]
 [-3562.87329382 -3727.60719872  -418.43547367   -35.86509796
   -132.60920191   -16.28212905]]
(1567, 20)
[[-5.18389617e+03 -3.02264772e+03  6.88386243e+02 ...  4.36907080e+02
   1.09043355e+02  5.37547659e+02]
 [-1.86669728e+03 -4.02163902e+03 -1.50557353e+03 ...  1.08135933e+02
  -2.31451201e+02 -2.74458205e+02]
 [-3.15474165e+03 -3.46198582e+03 -1.85544208e+03 ...  4.07791808e+01
  -4.03788073e+02  4.18091315e+02]
 ...
 [-3.82121714e+03 -1.57303288e+02 -1.19846485e+03 ...  4.78105865e+01
   1.73650257e+01 -1.52818044e+01]
 [-4.27104024e+03 -1.30047276e+03  3.81634520e+02 ...  8.00017271e+01
  -1.02431144e+00  9.46830199e+00]
 [-3.56287329e+03 -3.72760720e+03 -4.18435474e+02 ...  1.44778096e+02
   9.48804618e+01  1.08919747e+01]]

实验小结

降维技术使得数据变得更易使用,并且它们往往能够去除数据中的噪声,使得其他机器学习任务更加精确。降维往往作为预处理步骤,在数据应用到其他算法之前清洗数据。有很多技术可以用于数据降维,在这些技术中,独立成分分析、因子分析和主成分分析比较流行,其中又以主成分分析应用最广泛。

1.主成分分析(Principal Component Analysis,PCA):

在PCA 中,数据从原来的坐标系转换到新的坐标系,新坐标系的选择是由数据本身决定的。第一个新坐标轴选择的是原始数据中方差最大的方向,第二个新坐标轴的选择和第一个坐标轴正交且具有最大方差的方向。该过程中一直重复,重复次数为原始数据中特征的数目。我们会发现,大部分方差都包含在最前面的几个新坐标轴中。因此,我们就可以忽略余下的坐标轴,即对数据进行了降维处理。

2.因子分析(Factor Analysis):

在因子分析中,我们假设在观察数据的生成中有一些观察不到的隐变量(latent variable)。假设观察数据是这些隐变量和某些噪声的线性组合。那么隐变量的数据可能比观察数据的数目少,也就是说通过找到隐变量就可以实现数据的降维。

3.独立成分分析(Independent Component Analysis,ICA):

ICA 假设数据是从N 个数据源生成的,这一点和因子分析有些类似。假设数据为多个数据源的混合观察结果,这些数据源之间在统计上相互独立的,而在PCA 中只假设数据是不相关的。同因子分析一样,如果数据源的数目少于观察数据的数目,则可以实现降维过程。

微信公众号:

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注