美文网首页
用PCA简化数据

用PCA简化数据

作者: RossH | 来源:发表于2019-11-19 23:23 被阅读0次

    概述

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

    数据维度

    这是一个二维数据,不同的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简化数据

          本文链接:https://www.haomeiwen.com/subject/cbhnictx.html