在体育比赛中,人们面对的原本是百万像素的数据,但是只有球的三维位置才最重要,这就成为降维(dimensionlity reduction)。通常而言,我们在应用其他机器学习算法之前,必须先识别出其相关特征。
第一种降维称作主方法分析(Principal Component Analysis,PCA)。
第一个坐标值选择的是原始数据中方差最大的方向,第二个新坐标轴的选择和第一个坐标轴正交且具有最大方差的方向。
另一种降维技术是因子分析(Factor Analysis)。在因子分析中,我们假设在观察数据的生成一些观察不到的隐变量(latent variable)。假设观察数据是这些隐变量和某些噪声的线性组合。那么隐变量的数据可能比观察数据的数目少,也就是说通过隐变量可以实现数据的降维。
还有一种方法就是独立成分分析(Independent Component Analysis,ICA)。ICA假设数据是从N个数据源生成的,这一点和因子分析有些类似。假设数据为多个数据源的混合观察结果,这些数据源之间在统计上是互相独立的,而在PCA中之假设数据是不相关的。
在上述3种降维技术中,PCA的应用目前最为广泛,因此本章主要关注PCA。在下一节中,我们将会对PCA进行介绍,然后再通过一段Python代码来运行PCA。
主成分分析
优点:降低数据的复杂性,识别最重要的多个特征
缺点:不一定需要,且可能损失有用信息
适用数据类型:数据型数据
将数据转化成前N个主成分的伪代码大致如下:
去除平均值
计算协方差矩阵
计算协方差矩阵的特征值和特征向量
将特征值从打到小排序
保留最上面的N个特征向量
将数据转换到上述N个特征向量构建的新空间中
下面开始构建PCA算法
from numpy import *
def loadDataSet(fileName, delim='\t'):
fr = open(fileName)
stringArr = [line.strip().split(delim) for line in fr.readlines()]
datArr = [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
我们在testSet.txt文件中加入一个由1000个数据点组成的数据集,开始进行PCA操作:
In [17]: import pca
...: dataMat = pca.loadDataSet('testSet.txt')
...: lowDMat, reconMat = pca.pca(dataMat, 1)
...: shape(lowDMat)
...:
Out[17]: (1000L, 1L)
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()
...:
使用如下命令代替PCA调用,也会看到类似结果:
lowDMat, reconMat = pca.pca(dataMat, 2)
下面我们先处理一些异常值,用平均值代替NaN:
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 #将所有nan置为平均值
return datMat
PCA会给出数据中所包含的信息量。数据(data)和信息(information)之间具有巨大的差别。数据指的是接受的原始材料,其中包含噪声和不相关的信息。信息指数据中的相关部分。下面开始操作,首先将所有的NaN值替换为平均值:
In [36]: import pca
...: dataMat = pca.replaceNanWithMean()
...:
接下来从pca()函数中借用一些代码来达到我们的目的,之所以借用是因为我们想了解中间结果而非最后输出结果,调用如下语句去除均值:
In [37]: meanVals = mean(dataMat, axis = 0)
...: meanRemoved = dataMat - meanVals
...:
然后计算协方差矩阵:
In [38]: covMat = cov(meanRemoved, rowvar=0)
最后对该矩阵进行特征值分析:
In [39]: eigVals,eigVects = linalg.eig(mat(covMat))
...:
...: eigVals
...:
Out[39]:
array([ 53415197.85687517+0.j, 21746671.90465918+0.j,
8248376.61529074+0.j, ..., 0.00000000+0.j,
0.00000000+0.j, 0.00000000+0.j])
我们会发现超过20%的特征值都是0。这意味着这些特征都是其他特征的副本,也就是说,他们可以通过其他特征来表示,而本身并没有提供额外的信息。
接下来,我们了解一下部分数值的数量级。最前面15个值的数量级大于10的五次方,实际上那以后的值都变得非常小。这相当于告诉我们部分重要特征,重要特征的数目也很快就会下降。
最后,我们注意到一些负值,他们主要源于数值误差,应该四舍五入为0。