用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可以从数据中识别主要特征,它是通过沿着数据最大方差方向旋转坐标轴来实现的。选择方差最大的方向作为第一条坐标轴,后续坐标轴则与前面的坐标轴正交。

你可能感兴趣的:(用PCA简化数据)