PCA降维

关于PCA算法的具体介绍在下面这篇文章中,这里我就简单介绍一下,如果大家想要完整的了解PCA,戳下面那个连接就是了

Machine Learning-8

本文中的代码以及数据文件在这里

代码与数据

在介绍PCA之前,先谈谈

什么是降维?

举个例子,比如你现在再看手机,手机正在播放复联。众所周知,屏幕是由无数个像素点组成的,比如100W个吧,不算多。而你在看复联的时候你关注的是英雄的动作以及周围的场面,这是个三维的场景。

在这个过程中,人脑已经将数据从100W维降到了3维。

这就是降维(dimensionality reduction)。

为什么要降维?

  • 使得数据集更易使用
  • 降低很多算法的计算开销
  • 去除噪声
  • 使得结果易懂
  • 将其特征显现出来(将其特征显现出来通常是我们设计机器学习算法的第一步。)

在这篇文章中我将主要介绍未标注数据集的降维技术,当然,该技术同样也可以应用于已标注的数据。

先介绍一些常见的算法

第一种降维算法称为主成分分析(Principal Component Analysis),简称PCA

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

另外一种降维算法是因子分析(Factor Analysis)。

在因子分析中,我么假设在观察数据的生成中含有一些观测不到的隐变量(latent variable)。假设观察数据是这些隐变量和某些噪声的线性组合。那么隐变量的数据可能比观察数据的数目要少,也就是说通过找到隐变量就可以是实现数据的降维。这种方法已经应用于社会科学、金融等领域中了。

还有一种降维算法是独立成分分析(Independent Component Analysis),简称ICA。

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

PCA

优点:降低数据的复杂性,识别最重要的多个特征
缺点:不一定需要,且有可能损失有用信息
试用数据类型:数值型数据

通过PCA进行降维处理,我们就可以同时获得SVM和决策树的优点

  • 一方面,得到了和决策树一样的分类器
  • 另一方面,分类间隔和SVM一样好

既然叫主成分分析,那么

主成分有哪些?

第一个主成分是从数据差异性最大(即方差最大)的方向中提取出来的
第二个主成分则来自与数据差异性次大的方向,并且该方向与第一个主成分方向正交。

主成分怎么获得?

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

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

将数据转换为N个主成分的大概步骤如下

  • 去除平均值
  • 计算协方差矩阵
  • 计算协方差矩阵的特征值和特征向量
  • 将特征值从大到小排序
  • 保留最上面的N个特征向量
  • 将数据转换到上诉N个特征向量构建的新空间中

先建立一个名为pca.py的文件

然后代码如下:

当然第一步还是导入数据,这里有些不同的是使用了两个list comprehension 来构建矩阵

1
2
3
4
5
6
7
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)

pca函数有两个参数:

第一个参数是用于进行pca操作的数据集

第二个参数是个可选特征,即应用的N个特征。如果不指定该特征,那么函数会返回前9999999个特征,或者原始数据中的全部特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def pca(dataMat, topNfeat = 9999999):
meanVals = mean(dataMat, axis = 0)
# 首先去平均值
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved, rowvar =False)
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

然后在终端里输入以下命令检验一下

1
2
3
4
5
6
7
8
9
10
11
12
import pca
from numpy import *
dataMat = pca.loadDataSet('testSet.txt')
lowDMat, reconMat = pca.pca(dataMat, 1)
shape(lowDMat)
import matplotlib
import matplotlib.pyplot as plt
fig = 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()

运行的结果如下

Matlab实现

代码具体思想见我在开篇时的那篇链接

算法公式

1
2
3
4
5
6
7
8
9
10
11
12
function [U, S] = pca(X)

[m, n] = size(X);

U = zeros(n);
S = zeros(n);


Sigma = 1 / m * X' * X;
[U,S,V] = svd(Sigma);

end

数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Z = projectData(X, U, K)

Z = zeros(size(X, 1), K);

for i = 1:size(X,1)
x = X(i,:)';
Z(i,:) = x' * U(:,1:K);
end

end

function X_rec = recoverData(Z, U, K)

X_rec = zeros(size(Z, 1), size(U, 1));

for i=1:size(Z, 1)
v = Z(i, :)';

for j=1: size(U, 1)

X_rec(i, j) = v' * U(j, 1:K)';

end
end

end

总结

降维思想使得数据变得更容易使用,并且他们往往能够去除数据中的噪声,使得其他的机器学习更加准确。

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

实战:数据集中的特征中几乎每个特征都有NaN(Not a Number)该怎样处理

关于有些数据不能用的处理方法我在上篇博客中已经解释了,但是还是要因地制宜。所以就拿这个实战数据来解释一下。

首先,因为在这些特征(共590个)中,几乎所有样本都有 NaN ,因此除去不完整的样本这个方法不太现实。
其次,尽管我们可以用0来进行替换,但是由于我们不知道这些值的意义,所以这也是个下策。举个例子来证明是下策,比如它们都是开氏温度,那么将它们设置成0未免有些太扯淡了。

这时,我们可以采用用平均值来替代这个方法来解决这个问题。

该实战的数据来源

这是替代为平均值的函数,添加到pca.py文件中

1
2
3
4
5
6
7
8
9
def replaceNanWithMean():
dataMat = loadDataSet('secom.data.txt', '')
numFeat = shape(dataMat)[1]
for i in range(numFeat):
# 计算所有非 NaN 的平均值
meanVal = mean(dataMat[nonzero(~isnan(dataMat[:,i].A))[0],i])
# 将所有 NaN 置为平均值
dataMat[nonzero(isnan(dataMat[:,i].A))[0], i] = meanVal
return dataMat

接下来在终端里输入如下

1
2
3
4
5
6
dataMat = pca.replaceNanWithMean()
meanVals = mean(dataMat, axis = 0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved, rowvar = 0)
eigVals, eigVects = linalg.eig(mat(covMat))
eigVals

这样你就可以看到效果了

另需要注意的事

PCA会给出数据中所包含的信息量。

需要特别强调的是,数据(data)信息(information)之间有着巨大的差别。

  • 数据是指接受的原始材料,其中可能包含噪声和不相关的信息。
  • 信息是指数据中的相关部分。

这些并非是抽象概念,我们还可以定量的计算数据中所包含的信息并决定保留的比例