降维算法PCA和SVD - 参数(二)

539 阅读8分钟

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

参数

n_components

  • 含义
    • 降维后需要的维度
      • 即降维后需要保留的特征数量,降维流程中第二步里需要确认的k值,一般输入[0, min(X.shape)]范围中的整数。
  • 属于超参数
    • 会影响到模型的表现。如果留下的特征太多,就达不到降维的效果,如果留下的特征太少,那新特征向量可能无法容纳原始数据集中的大部分信息,因此,n_components既不能太大也不能太小

可以先从我们的降维目标说起:如果我们希望可视化一组数据来观察数据分布,我们往往将数据降到三维以下,很多时候是二维,即n_components的取值为2

高维数据的可视化
  • 调用库和模块
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import pandas as pd
  • 提取数据集
# 使用鸢尾花数据集
iris = load_iris()

x = iris.data
y = iris.target

x.shape

data = pd.DataFrame(x)
data.head()
  • 建模
# pca的使用
pca = PCA(n_components=2)
x_dr = pca.fit_transform(x)
x_dr

"""
array([[-2.68412563,  0.31939725],
       [-2.71414169, -0.17700123],
       [-2.88899057, -0.14494943],
       [-2.74534286, -0.31829898],
       [-2.72871654,  0.32675451],
       [-2.28085963,  0.74133045],
       [-2.82053775, -0.08946138],
       [-2.62614497,  0.16338496],
       [-2.88638273, -0.57831175],
       [-2.6727558 , -0.11377425],
       [-2.50694709,  0.6450689 ],
       [-2.61275523,  0.01472994],
       [-2.78610927, -0.235112  ],
       [-3.22380374, -0.51139459],
       [-2.64475039,  1.17876464],
       [-2.38603903,  1.33806233],
       [-2.62352788,  0.81067951]])
"""

# 可以直接一行完成pca的调用书写
x_dr = PCA(2).fit_transform(x)
x_dr
  • 可视化
# 做出这降维后的数据的特征1和特征2之间的关系图像,可视化
color = ["red","black","orange"]
iris.target_names

plt.figure()
for i in range(3):
    plt.scatter(x_dr[y==i,0],x_dr[y==i,1],alpha=0.7,c=color[i],label=iris.target_names[i])
plt.legend()
plt.title("PCA of IRIS dataset")
plt.show()

image.png

鸢尾花的分布被展现在我们眼前了,明显这是一个分簇的分布,并且每个簇之间的分布相对比较明显,也许versicolor和virginia这两种花之间会有一些分类错误,但setosa肯定不会被分错。这样的数据很容易分类,可以遇见,KNN,随机森林,神经网络,朴素贝叶斯,Adaboost这些分类器在鸢尾花数据集上,未调整的时候都可以有95%上下的准确率.

  • 探索降维后的数据
# 属性explained_variance_,查看降维后的每个新特征向量上所带有的信息量的大小(可解释性方差的大小)
pca.explained_variance_
# array([4.22824171, 0.24267075])

# 属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
# 又叫做可解释方差贡献率
pca.explained_variance_ratio_
# array([0.92461872, 0.05306648])

# 我们可以看到可解释方差的比率的集合,看下损失了多少信息
pca.explained_variance_ratio_.sum()
# 0.9776852063187949
  • 选择最好的n_components:累积可解释方差贡献率曲线

    • 当参数n_components中不填写任何值,则默认返回min(X.shape)个特征,一般来说,样本量都会大于特征数目,所以什么都不填就相当于转换了新特征空间,但没有减少特征的个数。一般来说,不会使用这种输入方式。但我们却可以使用这种输入方式来画出累计可解释方差贡献率曲线,以此选择最好的n_components的整数取值。
    • 累积可解释方差贡献率曲线是一条以降维后保留的特征个数为横坐标,降维后新特征矩阵捕捉到的可解释方差贡献率为纵坐标的曲线,能够帮助我们决定n_components最好的取值。
    # 当我们默认不写n_components的参数的时候,这个值默认是x.shape中较小的那个值,也就是样本数量,或者特征数量中较小者,一般不会这样使用
    import numpy as np
    pca_line = PCA().fit(x)
    plt.plot([1,2,3,4],np.cumsum(pca_line.explained_variance_ratio_))
    plt.xticks([1,2,3,4]) #这是为了限制坐标轴显示为整数
    plt.xlabel("number of components after dimension reduction")
    plt.ylabel("cumulative explained variance ratio")
    plt.show()
    

    image.png

    • 最大似然估计自选超参数
      • 数学大神Minka, T.P.在麻省理工学院媒体实验室做研究时找出了让PCA用最大似然估计(maximum likelihoodestimation)自选超参数的方法,输入 “mle” 作为n_components的参数输入,就可以调用这种方法
      # 我们可以设定n_components的值是"mle",这样pca可以使用最大似然估计来选取超参数,效果较好,用时较长
      pca_mle = PCA(n_components="mle")
      pca_mle = pca_mle.fit(x)
      X_mle = pca_mle.transform(x)
      X_mle
      #可以发现,mle为我们自动选择了3个特征
      
      """
      array([[-2.68412563,  0.31939725, -0.02791483],
             [-2.71414169, -0.17700123, -0.21046427],
             [-2.88899057, -0.14494943,  0.01790026],
             [-2.74534286, -0.31829898,  0.03155937],
             [-2.72871654,  0.32675451,  0.09007924],
             [-2.28085963,  0.74133045,  0.16867766],
             [-2.82053775, -0.08946138,  0.25789216],
             [-2.62614497,  0.16338496, -0.02187932],
             [-2.88638273, -0.57831175,  0.02075957],
             [-2.6727558 , -0.11377425, -0.19763272],
             [-2.50694709,  0.6450689 , -0.07531801],
             [-2.61275523,  0.01472994,  0.10215026],
             [-2.78610927, -0.235112  , -0.20684443],
             [-3.22380374, -0.51139459,  0.06129967],
             [-2.64475039,  1.17876464, -0.15162752],
             [-2.38603903,  1.33806233,  0.2777769 ]])
      """
      
      pca_mle.explained_variance_ratio_.sum()
      #得到了比设定2个特征时更高的信息含量,对于鸢尾花这个很小的数据集来说,3个特征对应这么高的信息含量,并不
      # 需要去纠结于只保留2个特征,毕竟三个特征也可以可视化
      # 0.9947878161267246
      
    • 按信息量占比选超参数
      • 输入[0,1]之间的浮点数,并且让参数svd_solver =='full',表示希望降维后的总解释性方差占比大于n_components指定的百分比,即是说,希望保留百分之多少的信息量。比如说,如果我们希望保留97%的信息量,就可以输入n_components = 0.97,PCA会自动选出能够让保留的信息量超过97%的特征数量
    # 可以输入百分数(小数),百分数表示保留信息的比例
    pca_f = PCA(n_components=0.97,svd_solver="full")
    pca_f = pca_f.fit(x)
    X_f = pca_f.transform(x)
    pca_f.explained_variance_ratio_
    
    # array([0.92461872, 0.05306648])
    

PCA中的SVD

  • svd_solver是奇异值分解器的意思,为什么PCA算法下面会有有关奇异值分解的参数?

    • SVD有一种惊人的数学性质,即是它可以跳过数学神秘的宇宙,不计算协方差矩阵,直接找出一个新特征向量组成的n维空间,而这个n维空间就是奇异值分解后的右矩阵(所以一开始在讲解降维过程时,我们说”生成新特征向量组成的空间VTV^T",并非巧合,而是特指奇异值分解中的矩阵VTV^T)。

    image.png

    • 右奇异矩阵有着如下性质:

    image.png

    • k就是n_components,是我们降维后希望得到的维度。若X为(m,n)的特征矩阵, VTV^T就是结构为(n,n)的矩阵,取这个矩阵的前k行(进行切片),即将V转换为结构为(k,n)的矩阵。而V(k,n)TV_{(k,n)}^T原特征矩阵X相乘,即可得到降维后的特征矩阵X_dr。这是说,奇异值分解可以不计算协方差矩阵等等结构复杂计算冗长的矩阵,就直接求出新特征空间和降维后的特征矩阵

    • 简而言之,SVD在矩阵分解中的过程比PCA简单快速,虽然两个算法都走一样的分解流程,但SVD可以作弊耍赖直接算出V。但是遗憾的是,SVD的信息量衡量指标比较复杂,要理解”奇异值“远不如理解”方差“来得容易,

      • sklearn将降维流程拆成了两部分:

        • 一部分是计算特征空间V,由奇异值分解完成
        • 另一部分是映射数据和求解新特征矩阵,由主成分分析完成,实现了用SVD的性质减少计算量,却让信息量的评估指标是方差,具体流程如下图:

        image.png

  • 可以理解为什么PCA的类里会包含控制SVD分解器的参数了。通过SVD和PCA的合作,sklearn实现了一种计算更快更简单,但效果却很好的“合作降维“。很多人理解SVD,是把SVD当作PCA的一种求解方法,其实指的就是在矩阵分解时不使用PCA本身的特征值分解,而使用奇异值分解来减少计算量。这种方法确实存在,但在sklearn中,矩阵U和Σ虽然会被计算出来(同样也是一种比起PCA来说简化非常多的数学过程,不产生协方差矩阵),但完全不会被用到,也无法调取查看或者使用,因此我们可以认为,U和Σ在fit过后就被遗弃了奇异值分解追求的仅仅是V,只要有了V,就可以计算出降维后的特征矩阵。在transform过程之后,fit中奇异值分解的结果除了V(k,n)以外,就会被舍弃,而V(k,n)会被保存在属性components_ 当中,可以调用查看。

PCA(2).fit(X).components_
PCA(2).fit(X).components_.shape

重要参数svd_solver 与 random_state

参数svd_solver是在降维过程中,用来控制矩阵分解的一些细节的参数。有四种模式可选:auto, full, arpack,randomized,默认”auto"。

  • auto:基于X.shape和n_components的默认策略来选择分解器:如果输入数据的尺寸大于500 x 500且要提取的特征数小于数据最小维度min(X.shape)的80%,就启用效率更高的”randomized“方法。否则,精确完整的SVD将被计算,截断将会在矩阵被分解完成后有选择地发生
  • full:从scipy.linalg.svd中调用标准的LAPACK分解器来生成精确完整的SVD,适合数据量比较适中,计算时间充足的情况,生成的精确完整的SVD的结构为:

image.png

  • arpack:从scipy.sparse.linalg.svds调用ARPACK分解器来运行截断奇异值分解(SVD truncated),分解时就将特征数量降到n_components中输入的数值k,可以加快运算速度,适合特征矩阵很大的时候,但一般用于特征矩阵为稀疏矩阵的情况,此过程包含一定的随机性。截断后的SVD分解出的结构为:

image.png

  • randomized,通过Halko等人的随机方法进行随机SVD。在"full"方法中,分解器会根据原始数据和输入的n_components值去计算和寻找符合需求的新特征向量,但是在"randomized"方法中,分解器会先生成多个随机向量,然后一一去检测这些随机向量中是否有任何一个符合我们的分解需求,如果符合,就保留这个随机向量,并基于这个随机向量来构建后续的向量空间。这个方法已经被Halko等人证明,比"full"模式下计算快很多,并且还能够保证模型运行效果。适合特征矩阵巨大,计算量庞大的情况

而参数random_state在参数svd_solver的值为"arpack" or "randomized"的时候生效,可以控制这两种SVD模式中的随机模式。通常我们就选用”auto“,不必对这个参数纠结太多。