利用PCA来简化数据
什么是PCA
PCA的全称是Principal Component Analysis,也即主成分分析,说人话就是改变坐标系来让数据更好处理(降维)。
为什么说改变坐标系就能够让数据更好处理呢?
诸位既然参加过高考,学过高数,那么多半是学过直角坐标系与极坐标系,有的时候换个坐标就能让结果好很多。只不过极坐标是为了简化计算过程,而PCA是为了对数据降维。
那么什么是降维呢?书上的例子就非常的直观:假设我们要对下图进行分类,那么得到的决策边界多半是这样子:
那么我们是用了几个变量(也可以视作维度)来学习和描述决策边界的呢?二维对不对。因为所有的点都是二维的,然后两个决策平面可以描述为这种形式:
y
=
a
x
+
b
y = ax+b
y=ax+b
但是,假如我们转换一下坐标,将原图的坐标沿着绿色的线,转化为这种形式:
值得注意的是,左图旋转后应该还是会有高度的呀,不可能变为右图中的一条直线。但是,我们要做分类问题的话,旋转坐标后高度还重要吗?显然不会,高度信息在旋转后不过是一些无意义的噪声罢了。因此去掉高度信息后就得到了右图的形式。而这便是PCA,用比较标准的定义来讲的话,就是:
- 主成分分析(Principal Component Analysis,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)
# 从小到大排序,最大的N个特征值的下标
eigValInd = eigValInd[:-(topNfeat+1):-1]
redEigVects = eigVects[:,eigValInd]
# 将数据转换到新空间
lowDDataMat = meanRemoved * redEigVects
reconMat = (lowDDataMat * redEigVects.T) + meanVals
return lowDDataMat, reconMat
if __name__ == '__main__':
dataMat = loadDataSet('testSet.txt')
lowDMat, reconMat = pca(dataMat, 1)
print(shape(lowDMat))
import matplotlib
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()
第一个函数是读取数据,除了要注意一下py2与py3的语法差异就没什么特别的了,重点在第二个函数,去除均值的目的是为了消除数据的整体平移对主成分分析(PCA)结果的影响,从而更好地捕捉数据的方差结构。然后我们要计算出协方差矩阵和特征矩阵。最后拿这个协方差矩阵去确定如何转换坐标系。
注:
- 协方差矩阵是描述数据特征之间相关性的矩阵。它的每个元素表示了对应特征之间的协方差,反映了它们的相关性程度。在PCA中,通过计算数据矩阵的协方差矩阵,可以获取数据的方差和相关性信息。
- 特征值是协方差矩阵的特征值,代表了协方差矩阵的特征向量对应的重要程度。在PCA中,通过计算协方差矩阵的特征值和特征向量,可以确定数据中方差最大的方向,即主成分。特征值表示了主成分的方差大小,越大的特征值意味着对应的主成分解释了更多的数据方差。
- 利用协方差矩阵和特征值,可以进行主成分的选择和降维。一般来说,我们会按照特征值的大小降序排列,选取前N个最大的特征值对应的特征向量作为主成分,这些主成分包含了数据中最重要的方差结构。根据选取的主成分,可以将原始数据映射到新的低维空间,实现数据的降维操作。
运行结果如下:
实验
现在我们做一个小实验,用来验证算法的效果。由于数据维度太多,没有办法可视化了,只能输出特征矩阵看一下:
from numpy import *
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):
# 计算所有非NaN的平均值
meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0], i])
# 将所有NaN置为平均值
datMat[nonzero(isnan(datMat[:,i].A))[0], i] = meanVal
return datMat
if __name__ == '__main__':
dataMat = replaceNanWithMean()
meanVals = mean(dataMat, axis=0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved, rowvar=0)
eigVals, eigVects = linalg.eig(mat(covMat))
print(eigVals)
输出如下:
[ 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
· · ·
· · ·
· · ·
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,这意味着可以降低非常多的维度。也就是说算法的效果非常好。
小结
由于还没有碰到过真正难以处理的数据,因此本章所提到的算法我还没有办法亲身体会其能力,但是就实验结果来看,它对于数据的处理能力的确是非常强。我感觉它跟注意力机制有一点点像,都是为了把更多的注意力分贝给更重要的内容,如果以后注意力机制发展到头了,内容过多,是否可以参考这个算法的思想,舍弃掉一些不重要的内容呢。感觉这可能会是一个很有意思的思路。