用PCA简化数据
概述
- 优点:降低数据的复杂性,识别最重要的多个特征
- 缺点:不一定需要,且可能损失有用信息
- 适用数据类型:数值型数据
数据维度

这是一个二维数据,不同的x值对应着不同的y值。

这是一个一维数据。y值始终不变,我们只需要关注x的不同取值。

这个图大家一看,估计二话不说就知道是二维数据了,很明显不同x值对应不同y值。但我要很遗憾地说错了。这是一维数据。看下图。

虽然在前面的坐标系下,数据看起来是二维的,但是我们通过旋转坐标轴到x'、y'的位置,可以发现它其实是一维的。
也可以说是,我们通过旋转坐标轴,使二维的数据变成了一维的数据(一个重要知识点),即实现了降维。

上图也是一个一维数据。在实际情况下,我们很难有那么好的数据,可以用一条直线拟合,所以我们允许存在一些偏差。
主成分分析PCA
通过前面一些数据维度的小例子,其实我们已经了解了主成分分析(Principal Component Analysis,PCA)的一些重要知识点。下面简单介绍下PCA。
主成分,顾名思义就是主要成分。PCA的任务就是找到数据的主要成分来代替原数据,达到实现降维的目的。
主成分要求能够尽可能接近原本数据的分布,尽可能减少降维带来的信息损失。
在PCA中,数据从原来的坐标系转换到了新的坐标系,新坐标系的选择由数据本身决定。第一个新坐标轴(x轴)选择原始数据中方差最大的方向,第二个新坐标轴(y轴)选择和x轴正交且具有最大方差的方向。重复上述过程,次数为原始数据中特征的数目。最后我们会发现,大部分方差都包含在最前面的几个新坐标轴中(主成分),然后,我们就可以忽略剩下的坐标轴,实现了对数据的降维处理。

数据的最大方差给出了数据的最重要信息。
如上图,蓝色圆圈是我们的数据点,比较粗的红线是数据最大方差的方向。假设我们选取它作为新坐标轴,然后进行降维,将数据点都投影到轴上,那么点到线的垂直距离就是我们的信息损失,可以发现这个时候的信息损失是比较小的。
如果换成另一个轴,可以发现有些点到线的距离是比较大的,也就是我们的信息损失比较大。而我们应该选择信息损失尽可能小的坐标轴。
对PCA进行简单介绍后,接下来就是代码实现了。前面有提到,第一个主成分是从数据差异性最大(即方差最大)的方向提取出来的,第二个主成分则来自于差异性次大,且该方向与第一个主成分方向正交。通过数据集的协方差矩阵及其特征值分析,我们就可以求得这些主成分的值。
一旦得到协方差矩阵的特征向量,我们就可以保留最大的N个值。这些特征向量也给出了N个最重要特征的真实结构。我们可以通过将数据乘上这N个特征向量而将它转换到新的空间。
在Numpy中实现PCA
伪代码如下:
去除平均值
计算协方差矩阵
计算协方差矩阵的特征向量和特征值
将特征值从大到小排序
保留最前面的N个特征向量
将数据转换到上述N个特征向量构建的新空间中
代码如下:
import numpy as np
def loadDataSet(fileName, delim = '\t'):
fr = open(fileName)
stringArr = [line.strip().split(delim) for line in fr.readlines()]
dataArr = [list(map(float, line)) for line in stringArr]
return np.mat(dataArr)
def pca(dataMat, topNfeat = 99999):
# 去平均值
meanVals = np.mean(dataMat, axis=0)
meanRemoved = dataMat - meanVals
# 计算协方差矩阵
covMat = np.cov(meanRemoved, rowvar=0)
# 计算特征值并排序
eigVals, eigVects = np.linalg.eig(np.mat(covMat))
eigValInd = np.argsort(eigVals)
eigValInd = eigValInd[:-(topNfeat + 1):-1]
redEigVects = eigVects[:, eigValInd]
# 将数据转换到新空间
lowDDataMat = meanRemoved * redEigVects
reconMat = (lowDDataMat * redEigVects.T) + meanVals
return lowDDataMat, reconMat
pca()
函数有两个参数,一个是数据集,一个是特征的个数。如果不指定topNfeat
,那么就会返回前99999个特征,或者数据集的全部特征。
下面用一个有1000个数据点,2个特征的数据集来测试下。
dataMat = loadDataSet('testSet.txt')
lowDMat, reconMat = pca(dataMat, 1)
# 可视化原数据和降维数据
import matplotlib.pyplot as plt
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()
结果如下。

当topNfeat
设为2,或者不设时,数据是一样的。如下。

示例
这里有一份半导体制造的数据集,一共有590个特征,下面来试试看能不能进行降维处理。
该数据集有很多缺失值,所以需要先用平均值进行填充。
def replaceNanWithMean():
dataMat = loadDataSet('secom.data', ' ')
numFeat = dataMat.shape[1]
for i in range(numFeat):
# 计算所有非NaN的平均值
meanVal = np.mean(dataMat[np.nonzero(~np.isnan(dataMat[:,i].A))[0], I])
# 将所有NaN置为平均值
dataMat[np.nonzero(np.isnan(dataMat[:,i].A))[0], i] = meanVal
return dataMat
replaceNanWithMean()
函数加载数据集并进行缺失值处理。
接下来,对数据集进行去均值,计算协方差矩阵,特征值分析。然后观察下特征值。
dataMat = replaceNanWithMean()
meanVals = np.mean(dataMat, axis=0)
meanRemoved = dataMat - meanVals
covMat = np.cov(meanRemoved, rowvar=0)
eigVals, eigVects = np.linalg.eig(np.mat(covMat))
eigVals
output:
array([ 5.34151979e+07, 2.17466719e+07, 8.24837662e+06, 2.07388086e+06,
1.31540439e+06, 4.67693557e+05, 2.90863555e+05, 2.83668601e+05,
2.37155830e+05, 2.08513836e+05, 1.96098849e+05, 1.86856549e+05,
1.52422354e+05, 1.13215032e+05, 1.08493848e+05, 1.02849533e+05,
1.00166164e+05, 8.33473762e+04, 8.15850591e+04, 7.76560524e+04,
6.66060410e+04, 6.52620058e+04, 5.96776503e+04, 5.16269933e+04,
......
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00])
这里省略部分输出。
通过观察特征值,可以发现有超过20%的特征值都是0。这就意味着这些特征都是其他特征的副本,也就是说,它们可以通过其他特征来表示,而本身并没有提供额外信息。
接下来可视化前20个主成分占总方差的百分比。
eigValInd = np.argsort(eigVals)
eigValInd = eigValInd[::-1]
sortedEigVals = eigVals[eigValInd]
total = sum(sortedEigVals)
varPercentage = sortedEigVals/total*100
plt.rcParams['font.sans-serif'] = ['SimHei']
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(1, 21), varPercentage[:20], marker='^')
plt.xlabel('主成分数目')
plt.ylabel('方差的百分比')
plt.show()
结果如下。

可以看出大部分方差都包含在前面的几个主成分。
舍弃后面的主成分并不会损失太多信息。
如果只保留前6个,则数据集从590个特征简化成6个,大概实现了100:1的压缩。
下表给出了Top7和第20个主成分的方差百分比和累积方差百分比。从表中可以看出,前6个主成分就覆盖数据96.8%的方差,而前20个则覆盖了99.3%的方差。

小结
降维技术使得数据变得更易使用,并且能够去除数据中的噪声。
PCA可以从数据中识别主要特征,它是通过沿着数据最大方差方向旋转坐标轴来实现的。选择方差最大的方向作为第一条坐标轴,后续坐标轴则与前面的坐标轴正交。