降维算法PCA和SVD

一、概述

1.维度
对于数组和Series来说,维度就是功能shape返回的结果,shape中返回了几个数字,就是几维。索引以外的数据,不分行列的叫一维(此时shape返回唯一的维度上的数据个数),有行列之分叫二维(shape返回行x列),也称为表。一张表最多二维,复数的表构成了更高的维度。当一个数组中存在2张3行4列的表时,shape返回的是(更高维,行,列)。当数组中存在2组2张3行4列的表时,数据就是4维,shape返回(2,2,3,4)。
在这里插入图片描述
数组中的每一张表,都可以是一个特征矩阵或一个DataFrame,这些结构永远只有一张表,所以一定有行列,其中行是样本,列是特征。针对每一张表,维度指的是样本的数量或特征的数量,一般无特别说明,指的都是特征的数量。除了索引之外,一个特征是一维,两个特征是二维,n个特征是n维。
在这里插入图片描述
对图像来说,维度就是图像中特征向量的数量。特征向量可以理解为是坐标轴,一个特征向量定义一条直线,是一维,两个相互垂直的特征向量定义一个平面,即一个直角坐标系,就是二维,三个相互垂直的特征向量定义一个空间,即一个立体直角坐标系,就是三维。三个以上的特征向量相互垂直,定义人眼无法看见,也无法想象的高维空间。
在这里插入图片描述
降维算法中的”降维“,指的是降低特征矩阵中特征的数量。降维的目的是为了让算法运算更快,效果更好,但其实还有另一种需求:数据可视化。从上面的图我们其实可以看得出,图像和特征矩阵的维度是可以相互对应的,即一个特征对应一个特征向量,对应一条坐标轴。所以,三维及以下的特征矩阵,是可以被可视化的,帮助更快地理解数据的分布,而三维以上特征矩阵的则不能被可视化,数据的性质也就比较难理解。

2.sklearn中的降维算法
sklearn中降维算法都被包括在模块decomposition中,这个模块本质是一个矩阵分解模块。矩阵分解可以用在降维,深度学习,聚类分析,数据预处理,低纬度特征学习,推荐系统,大数据分析等领域。
在这里插入图片描述
SVD和主成分分析PCA都属于矩阵分解算法中的入门算法,都是通过分解特征矩阵来进行降维,它们虽然是入门算法,却不代表PCA和SVD简单。

二、PCA与SVD

在降维过程中会减少特征的数量,这意味着删除数据,数据量变少则表示模型可以获取的信息会变少,模型的表现可能会因此受影响。同时,在高维数据中,必然有一些特征是不带有有效的信息的(比如噪音),或者有一些特征带有的信息和其他一些特征是重复的(比如一些特征可能会线性相关)。希望能够找出一种办法来衡量特征上所带的信息量,使得降维的过程中,能够既减少特征的数量,又保留大部分有效信息——将那些带有重复信息的特征合并,并删除那些带无效信息的特征等等——逐渐创造出能够代表原特征矩阵大部分信息的,特征更少的,新特征矩阵。

有一种重要的特征选择方法:方差过滤。如果一个特征的方差很小,则意味着这个特征上很可能有大量取值都相同(比如90%都是1,只有10%是0,甚至100%是1),那这一个特征的取值对样本而言就没有区分度,这种特征就不带有有效信息。从方差的这种应用就可以推断出,如果一个特征的方差很大,则说明这个特征上带有大量的信息。因此,在降维中,PCA使用的信息量衡量指标,就是样本方差,又称可解释性方差,方差越大,特征所带的信息量越多。
在这里插入图片描述
Var代表一个特征的方差,n代表样本量,xi代表一个特征中的每个样本取值,xhat代表这一列样本的均值。
**注意:**方差计算公式中为什么除数是n-1? 这是为了得到样本方差的无偏估计。

1.降维究竟是怎样实现?
class sklearn.decomposition.PCA (n_components=None, copy=True, whiten=False, svd_solver=’auto’, tol=0.0,iterated_power=’auto’, random_state=None)
PCA作为矩阵分解算法的核心算法,其实没有太多参数,但每个参数的意义和运用都很难,因为几乎每个参数都涉及到高深的数学原理。为了参数的运用和意义变得明朗,来看一组简单的二维数据的降维。
在这里插入图片描述
现有一组简单的数据,有特征x1和x2,三个样本数据的坐标点分别为(1,1),(2,2),(3,3)。可以让x1和x2分别作为两个特征向量,用一个二维平面来描述这组数据。这组数据现在每个特征的均值都为2,方差则等于:
在这里插入图片描述
每个特征的数据一模一样,因此方差也都为1,数据的方差总和是2。
现在目标是:只用一个特征向量来描述这组数据,即将二维数据降为一维数据,并且尽可能地保留信息量,即让数据的总方差尽量靠近2。于是,将原本的直角坐标系逆时针旋转45°,形成了新的特征向量x1和x2组成的新平面,可以注意到,在这个新平面中,x2上的数值此时都变成了0,因此x2明显不带有任何有效信息了(此时x2的方差也为0了)。此时,x1特征上
的数据均值是2根号2,而方差则可表示成:
在这里插入图片描述
x2*上的数据均值为0,方差也为0。

此时,再根据信息含量的排序,取信息含量最大的一个特征,因为想要的是一维数据。所以可以将x2删除,同时也删除图中的x2特征向量,剩下的x1*就代表了曾经需要两个特征来代表的三个样本点。通过旋转原有特征向量组成的坐标轴来找到新特征向量和新坐标平面,将三个样本点的信息压缩到了一条直线上,实现了二维变一维,并且尽量保留原始数据的信息。一个成功的降维,就实现了。

在这个降维过程中,有几个重要的步骤:

过程二维特征矩阵n维特征矩阵
1输入原数据,结构为 (3,2);找出原本的2个特征对应的直角坐标系,本质是找出这2个特征构成的2维平面输入原数据,结构为 (m,n);找出原本的n个特征向量构成的n维空间V
2决定降维后的特征数量:1决定降维后的特征数量:k
3旋转,找出一个新坐标系;本质是找出2个新的特征向量,以及它们构成的新2维平面;新特征向量让数据能够被压缩到少数特征上,并且总信息量不损失太多通过某种变化,找出n个新的特征向量,以及它们构成的新n维空间V
4找出数据点在新坐标系上,2个新坐标轴上的坐标找出原始数据在新特征空间V中的n个新特征向量上对应的值,即“将数据映射到新空间中”
5选取第1个方差最大的特征向量,删掉没有被选中的特征,成功将2维平面降为1维选取前k个信息量最大的特征,删掉没有被选中的特征,成功将n维空间V降为k维

在步骤3当中,用来找出n个新特征向量,让数据能够被压缩到少数特征上并且总信息量不损失太多的技术就是矩阵分解。PCA和SVD是两种不同的降维算法,但他们都遵从上面的过程来实现降维,只是两种算法中矩阵分解的方法不同,信息量的衡量指标不同罢了。PCA使用方差作为信息量的衡量指标,并且特征值分解来找出空间V。降维时,它会通过一系列数学操作(比如说,产生协方差矩阵)将特征矩阵X分解为以下三个矩阵,其中Q和Q-1是辅助的矩阵,Σ是一个对角矩阵(即除了对角线上有值,其他位置都是0的矩阵),其对角线上的元素就是方差。降维完成之后,PCA找到的每个新特征向量就叫做“主成分”,而被丢弃的特征向量被认为信息量很少,这些信息很可能就是噪音。
协方差矩阵:
协方差矩阵
特征矩阵分解:
在这里插入图片描述
SVD使用奇异值分解来找出空间V,其中Σ也是一个对角矩阵,不过它对角线上的元素是奇异值,这也是SVD中用来衡量特征上的信息量的指标。U和V^{T}分别是左奇异矩阵和右奇异矩阵,也都是辅助矩阵。
另一个数学神秘的宇宙
在数学原理中,无论是PCA和SVD都需要遍历所有的特征和样本来计算信息量指标。并且在矩阵分解的过程之中,会产生比原来的特征矩阵更大的矩阵,比如原数据的结构是(m,n),在矩阵分解中为了找出最佳新特征空间V,可能需要产生(n,n),(m,m)大小的矩阵,还需要产生协方差矩阵去计算更多的信息。因此,降维算法的计算量很大,运行比较缓慢,但它们的功能无可替代。

思考:PCA和特征选择技术都是特征工程的一部分,它们有什么不同?
特征工程中有三种方式:特征提取,特征创造和特征选择。
特征选择是从已存在的特征中选取携带信息最多的,选完之后的特征依然具有可解释性,依然知道这个特征在原数据的哪个位置,代表着原数据上的什么含义。
而PCA,是将已存在的特征进行压缩,降维完毕后的特征不是原本的特征矩阵中的任何一个特征,而是通过某些方式组合起来的新特征。通常来说,在新的特征矩阵生成之前,无法知晓PCA都建立了怎样的新特征向量,新特征矩阵生成之后也不具有可读性,无法判断新特征矩阵的特征是从原数据中的什么特征组合而来,新特征虽然带有原始数据的信息,却已经不是原数据上代表着的含义了。以PCA为代表的降维算法因此是特征创造(feature creation,或feature construction)的一种。
可以想见,PCA一般不适用于探索特征和标签之间的关系的模型(如线性回归),因为无法解释的新特征和标签之间的关系不具有意义。在线性回归模型中,使用特征选择。

2.重要参数n_components
n_components是降维后需要的维度,即降维后需要保留的特征数量,降维流程中第二步里需要确认的k值,一般输入[0, min(X.shape)]范围中的整数。K类似于KNN中的K和随机森林中的n_estimators,是一个需要人为去确认的超参数,并且设定的数字会影响到模型的表现。如果留下的特征太多,就达不到降维的效果,如果留下的特征太少,那新特征向量可能无法容纳原始数据集中的大部分信息,因此,n_components既不能太大也不能太小。如何选取呢?

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

2.1迷你案例:高维数据的可视化

#1.调用库和模块
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA

#2.提取数据集
iris = load_iris()
y = iris.target
X = iris.data
print(X)
print(X.shape)#作为数组,X是二维 (150,4)
import pandas as pd
print(pd.DataFrame(X))#作为数据表或特征矩阵,X是四维的特征矩阵

#3.建模
#调用PCA
pca = PCA(n_components=2) #实例化
pca = pca.fit(X) #拟合模型
X_dr = pca.transform(X) #获取新矩阵
print(X_dr)
print(X_dr.shape)#(150,2)
#也可以fit_transform一步到位
#X_dr = PCA(2).fit_transform(X)

#4.可视化
#要将三种鸢尾花的数据分布显示在二维平面坐标系中,对应的两个坐标(两个特征向量)应该是三种鸢尾花降维后的x1和x2
X_dr[y == 0, 0] #这里是布尔索引,
#返回X_dr中所有对应的标签为第0(即布尔索引为True)种花的所有行的第0列
#要展示三中分类的分布,需要对三种鸢尾花分别绘图
#可以写成三行代码,
plt.figure()#输出一个画布
plt.scatter(X_dr[y==0, 0], X_dr[y==0, 1], c="red", label=iris.target_names[0])
plt.scatter(X_dr[y==1, 0], X_dr[y==1, 1], c="black", label=iris.target_names[1])
plt.scatter(X_dr[y==2, 0], X_dr[y==2, 1], c="orange", label=iris.target_names[2])
plt.legend()#显示图例
plt.title('PCA of IRIS dataset')#给图加一个标题
plt.show()
#也可以写成for循环
colors = ['red', 'black', 'orange']
print(iris.target_names)
plt.figure()
for i in [0, 1, 2]:
    plt.scatter(X_dr[y == i, 0]
                ,X_dr[y == i, 1]
                ,alpha=0.7#画出图像的透明度
                ,c=colors[i]
                ,label=iris.target_names[i]
               )
plt.legend()
plt.title('PCA of IRIS dataset')
plt.show()
#鸢尾花的分布是一个分簇的分布,并且每个簇之间的分布相对比较明显,也许versicolor和virginia这两种花之间会有一些分类错误,但setosa肯定不会被分错。
#这样的数据很容易分类,KNN,随机森林,神经网络,朴素贝叶斯,Adaboost这些分类器在鸢尾花数据集上,未调整的时候都可以有95%上下的准确率。

#5.探索降维后的数据
#属性explained_variance_,查看降维后每个新特征向量上所带的信息量大小(可解释性方差的大小)
print(pca.explained_variance_)
#属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
#又叫做可解释方差贡献率
print(pca.explained_variance_ratio_)
#大部分信息都被有效地集中在了第一个特征上
print(pca.explained_variance_ratio_.sum())

#6.选择最好的n_components:累积可解释方差贡献率曲线
#当参数n_components中不填写任何值,则默认返回min(X.shape)个特征,一般来说,样本量都会大于特征数目,所以什么都不填就相当于转换了新特征空间,但没有减少特征的个数。
#一般来说,不会使用这种输入方式。
#但可以使用这种输入方式来画出累计可解释方差贡献率曲线,以此选择最好的n_components的整数取值。
#累积可解释方差贡献率曲线是一条以降维后保留的特征个数为横坐标,降维后新特征矩阵捕捉到的可解释方差贡献率为纵坐标的曲线,能够帮助决定n_components最好的取值。
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()

2.2 最大似然估计自选超参数
n_components除了输入整数,还可以输入“mle”(最大似然估计maximum likelihood estimation)让PCA自选超参数。

pca_mle = PCA(n_components="mle")
pca_mle = pca_mle.fit(X)
X_mle = pca_mle.transform(X)
print(X_mle)#可以发现,mle自动选择了3个特征
print(pca_mle.explained_variance_ratio_.sum())
#得到了比设定2个特征时更高的信息含量,对于鸢尾花这个很小的数据集来说,3个特征对应这么高的信息含量,并不需要去纠结于只保留2个特征,毕竟三个特征也可以可视化

2.3 按信息量占比选超参数
输入[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)
print(X_f)
print(pca_f.explained_variance_ratio_)
print(pca_f.explained_variance_ratio_.sum())

3.PCA中的SVD
3.1 PCA中的SVD哪里来?
svd_solver是奇异值分解器的意思,为什么PCA算法下面会有有关奇异值分解的参数?不是两种算法么?
PCA和SVD涉及了大量的矩阵计算,两者都是运算量很大的模型,但SVD可以不计算协方差矩阵,直接找出一个新特征向量组成的n维空间,而这个n维空间就是奇异值分解后的右矩阵 V T V^T VT(所以一开始在讲解降维过程时,说的”生成新特征向量组成的空间V",并非巧合,而是特指奇异值分解中的矩阵 V T V^T VT)。
在这里插入图片描述
右奇异矩阵VT有着如下性质:
在这里插入图片描述
k就是n_components,是降维后希望得到的维度。若X为(m,n)的特征矩阵, V T V^T VT就是结构为(n,n)的矩阵,取这个矩阵的前k行(进行切片),即将V转换为结构为(k,n)的矩阵。而 V ( k , n ) T V_{(k,n)}^T V(k,n)T与原特征矩阵X相乘,即可得到降维后的特征矩阵X_dr。奇异值分解可以不计算协方差矩阵等等结构复杂计算冗长的矩阵,就直接求出新特征空间和降维后的特征矩阵。

简而言之,SVD在矩阵分解中的过程比PCA简单快速,虽然两个算法都走一样的分解流程,但SVD可以直接算出V。但SVD的信息量衡量指标比较复杂,要理解”奇异值“远不如理解”方差“来得容易,因此,sklearn将降维流程拆成了两部分:一部分是计算特征空间V,由奇异值分解完成,另一部分是映射数据和求解新特征矩阵,由主成分分析完成,实现了用SVD的性质减少计算量,却让信息量的评估指标是方差,具体流程如下图:
在这里插入图片描述
这就是为什么PCA的类里会包含控制SVD分解器的参数了。
通过SVD和PCA的合作,sklearn实现了一种计算更快更简单,但效果却很好的“合作降维“。很多人把SVD当作PCA的一种求解方法,其实指的就是在矩阵分解时不使用PCA本身的特征值分解,而使用奇异值分解来减少计算量。这种方法确实存在,但在sklearn中,矩阵U和Σ虽然会被计算出来(同样也是一种比起PCA来说简化非常多的数学过程,不产生协方差矩阵),但完全不会被用到,也无法调取查看或者使用,可以认为,U和Σ在fit过后就被遗弃了。奇异值分解追求的仅仅是V,只要有了V,就可以计算出降维后的特征矩阵。在transform过程之后,fit中奇异值分解的结果除了V(k,n)以外,就会被舍弃,而V(k,n)会被保存在属性components_ 当中,可以调用查看。

print(PCA(2).fit(X).components_)
print(PCA(2).fit(X).components_.shape)#(2,4)就是(k,n)

3.2 重要参数svd_solver 与 random_state
参数svd_solver是在降维过程中,用来控制矩阵分解的一些细节的参数。有四种模式可选:“auto”, “full”, “arpack”,“randomized”,默认”auto"。
auto:基于X.shape和n_components的默认策略来选择分解器:如果输入数据的尺寸大于500x500且要提取的特征数小于数据最小维度min(X.shape)的80%,就启用效率更高的”randomized“方法。否则,精确完整的SVD将被计算,截断将会在矩阵被分解完成后有选择地发生。
full:从scipy.linalg.svd中调用标准的LAPACK分解器来生成精确完整的SVD,适合数据量比较适中,计算时间充足的情况,生成的精确完整的SVD的结构为:
在这里插入图片描述
arpack:从scipy.sparse.linalg.svds调用ARPACK分解器来运行截断奇异值分解(SVD truncated),分解时就将特征数量降到n_components中输入的数值k,可以加快运算速度,适合特征矩阵很大的时候,但一般用于特征矩阵为稀疏矩阵的情况,此过程包含一定的随机性。截断后的SVD分解出的结构为:
在这里插入图片描述
randomized:进行随机SVD。在"full"方法中,分解器会根据原始数据和输入的n_components值去计算和寻找符合需求的新特征向量,但是在"randomized"方法中,分解器会先生成多个随机向量,然后一一去检测这些随机向量中是否有任何一个符合分解需求,如果符合,就保留这个随机向量,并基于这个随机向量来构建后续的向量空间。这个方法比"full"模式下计算快很多,并且还能够保证模型运行效果。适合特征矩阵巨大,计算量庞大的情况

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

3.3 重要属性components_
V(k,n)是新特征空间,是将原始数据进行映射的那些新特征向量组成的矩阵。用它来计算新的特征矩阵,但希望获取的毕竟是X_dr,为什么要把V(k,n)这个矩阵保存在n_components这个属性当中来让调取查看呢?

PCA与特征选择的区别是特征选择后的特征矩阵是可解读的,而PCA降维后的特征矩阵式不可解读的:PCA是将已存在的特征进行压缩,降维完毕后的特征不是原本的特征矩阵中的任何一个特征,而是通过某些方式组合起来的新特征。通常来说,在新的特征矩阵生成之前,无法知晓PCA都建立了怎样的新特征向量,新特征矩阵生成之后也不具有可读性,无法判断新特征矩阵的特征是从原数据中的什么特征组合而来,新特征虽然带有原始数据的信息,却已经不是原数据上代表着的含义了。

但是其实,在矩阵分解时,PCA是有目标的:在原有特征的基础上,找出能够让信息尽量聚集的新特征向量。在sklearn使用的PCA和SVD联合的降维方法中,这些新特征向量组成的新特征空间其实就是V(k,n)。当V(k,n)是数字时,无法判断V(k,n)和原有的特征究竟有着怎样的数学联系。但是,如果原特征矩阵是图像,V(k,n)这个空间矩阵也可以被可视化的话,就可以通过两张图来比较,就可以看出新特征空间究竟从原始数据里提取了什么重要的信息。

下面来看看人脸识别中属性components_的运用。

#1.导入需要的库和模块
from sklearn.datasets import fetch_lfw_people#用7个人的1000多张照片组成了一组人脸数据
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np

#2.实例化数据集,探索数据
faces = fetch_lfw_people(min_faces_per_person=60)#实例化 min_faces_per_person=60表示每个人取出60张人脸照片
print(faces.images.shape)#(1348,62,47)1348是矩阵中图像的个数,62是每个图像的特征矩阵的行,47是每个图像的特征矩阵的列
print(faces.data.shape)#(1348,2914)行是样本,列是样本相关的所有特征  62*47=2914
X = faces.data#换成特征矩阵

#3.将原特征矩阵可视化看看图像
#数据本身是图像,和数据本身只是数字,使用的可视化方法不同
#创建画布和子图对象
fig, axes = plt.subplots(4,5           #这个画布上想要4行,每行有5张图
                         ,figsize=(8,4)#每张图的大小和比例
                         ,subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
                         )#.subplots是专门用来画子图和建立子图画布的
#fig就是上面画好的画布
#axes中的一个对象对应fig中的一个空格
#目标是在每一个子图对象中填充图像(共20张图),因此需要写一个在子图对象中遍历的循环
#axes.shape为(4,5),是二维结构,所以可以有两种循环方式,一种是使用索引,循环一次同时生成一列上的四个图
#另一种是把数据拉成一维,循环一次只生成一个图
#究竟使用哪一种循环方式,是要看要画的图的信息,储存在一个怎样的结构里
#当使用子图对象.imshow 来将图像填充到空白画布上时,imshow要求的数据格式必须是一个(m,n)的矩阵,即每个数据都是一张单独的图
#因此需要遍历的是faces.images,其结构是(1277, 62, 47)
#要从一个数据集中取出20个图,明显是一次性的循环切片[i,:,:]来得便利,axes[0][0].imshow(faces.images[0,:,:]
#因此我们要把axes的结构拉成一维来循环
print(axes.flat)#将二维降成一维
print(len([*axes.flat]))#20,[*xxx]是用来把xxx打开的
print(enumerate(axes.flat))#将原来的20个对象加了索引,且每个对象和索引组合起来放到元组中
#填充图像
for i, ax in enumerate(axes.flat):#i就是0~19,ax就是后面的画图对象
    ax.imshow(faces.images[i,:,:]
              ,cmap="gray" #选择色彩的模式
              )
#https://matplotlib.org/tutorials/colors/colormaps.html #cmap的取值可以在这里看到

#4.建模降维,提取新特征空间矩阵
#原本有2900维,我们现在来降到150维
pca = PCA(150).fit(X)#sklearn在降维算法中只接受二维的数据
V = pca.components_
print(V.shape)#(150, 2914)

#5.将新特征空间矩阵可视化
fig, axes = plt.subplots(4,5,figsize=(8,4),subplot_kw = {"xticks":[],"yticks":[]})
for i, ax in enumerate(axes.flat):
    ax.imshow(V[i,:].reshape(62,47),cmap="gray")

可以看出,比起降维前的数据,新特征空间可视化后的人脸非常模糊,这是因为原始数据还没有被映射到特征空间中。但是可以看出,整体比较亮的图片,获取的信息较多,整体比较暗的图片,却只能看见黑漆漆的一块。在比较亮的图片中,眼睛,鼻子,嘴巴,都相对清晰,脸的轮廓,头发之类的比较模糊。

这说明,新特征空间里的特征向量们,大部分是"五官"和"亮度"相关的向量,所以新特征向量上的信息肯定大部分是由原数据中和"五官"和"亮度"相关的特征中提取出来的。到这里,通过可视化新特征空间V,解释了一部分降维后的特征:虽然显示出来的数字看着不知所云,但画出来的图表示,这些特征是和”五官“以及”亮度“有关的。这也证明,PCA能将原始数据集中重要的数据进行聚集。

4.重要接口inverse_transform
接口inverse_transform,可以将归一化,标准化,甚至做过哑变量的特征矩阵还原回原始数据中的特征矩阵,这暗示,任何有inverse_transform这个接口的过程都是可逆的。PCA应该也是如此。在sklearn中,通过让原特征矩阵X右乘新特征空间矩阵V(k,n)来生成新特征矩阵X_dr,那理论上来说,让新特征矩阵X_dr右乘V(k,n)的逆矩阵 V ( k , n ) ( − 1 ) V_{(k,n)}^{(-1)} V(k,n)(1),就可以将新特征矩阵X_dr还原为X。那sklearn是否这样做了呢?
4.1 迷你案例:用人脸识别看PCA降维后的信息保存量
人脸识别是最容易的,用来探索inverse_transform功能的数据。先调用一组人脸数据X(m,n),对人脸图像进行绘制,再对人脸数据进行降维得到X_dr,之后再使用inverse_transform(X_dr)返回一个X_inverse(m,n),并对这个新矩阵中的人脸图像也进行绘制。如果PCA的降维过程是可逆的,应当期待X(m,n)和X_inverse(m,n)返回一模一样的图像,即携带一模一样的信息。

#1.导入需要的库和模块
from sklearn.datasets import fetch_lfw_people#用7个人的1000多张照片组成了一组人脸数据
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np

#2.实例化数据集,探索数据
faces = fetch_lfw_people(min_faces_per_person=60)#实例化 min_faces_per_person=60表示每个人取出60张人脸照片
print(faces.images.shape)#(1348,62,47)1348是矩阵中图像的个数,62是每个图像的特征矩阵的行,47是每个图像的特征矩阵的列
print(faces.data.shape)#(1348,2914)行是样本,列是样本相关的所有特征  62*47=2914
X = faces.data#换成特征矩阵

#3.建模降维,获取降维后的特征矩阵X_dr
pca=PCA(150)#实例化
X_dr=pca.fit_transform(X)#拟合+提取结果
print(X_dr.shape)

#4.将降维后矩阵用inverse_transform返回原空间
X_inverse=pca.inverse_transform(X_dr)
print(X_inverse.shape)

#5. 将特征矩阵X和X_inverse可视化
#数据本身是图像,和数据本身只是数字,使用的可视化方法不同
#创建画布和子图对象
fig, ax = plt.subplots(2,10             #这个画布上想要4行,每行有5张图
                       ,figsize=(10,2.5)#每张图的大小和比例
                       ,subplot_kw={"xticks":[],"yticks":[]}#不要显示坐标轴
                       )    #.subplots是专门用来画子图和建立子图画布的                           
#fig就是上面画好的画布
#ax中的一个对象对应fig中的一个空格

#和上面的案例一样,需要对子图对象进行遍历的循环,来将图像填入子图中
#现在ax中是2行10列,第一行是原数据,第二行是inverse_transform后返回的数据
#所以需要同时循环两份数据,即一次循环画一列上的两张图,而不是把ax拉平
for i in range(10):
    ax[0,i].imshow(faces.images[i,:,:],cmap="binary_r")
    ax[1,i].imshow(X_inverse[i].reshape(62,47),cmap="binary_r")#X_inverse应该和原数据有相同的结果,如果相同,则称inverse_transform实现了降维过程的逆转

可以明显看出,这两组数据可视化后,由降维后再通过inverse_transform转换回原维度的数据画出的图像和原数据画的图像大致相似,但原数据的图像明显更加清晰。这说明,inverse_transform并没有实现数据的完全逆转。这是因为,在降维的时候,部分信息已经被舍弃了,X_dr中往往不会包含原数据100%的信息,所以在逆转的时候,即便维度升高,原数据中已经被舍弃的信息也不可能再回来了。所以,降维不是完全可逆的。(降维后丢弃的信息在inverse_transform后不能找回来,只是将保留的信息映射到原有维度。)

Inverse_transform的功能,是基于X_dr中的数据进行升维,将数据重新映射到原数据所在的特征空间中,而并非恢复所有原有的数据。但同时,可以看出,降维到300以后的数据,的确保留了原数据的大部分信息,所以图像看起来,才会和原数据高度相似,只是稍稍模糊罢了。

4.2 迷你案例:用PCA做噪音过滤
降维的目的之一就是希望抛弃掉对模型带来负面影响的特征,而带有效信息的特征的方差应该是远大于噪音的,所以相比噪音,有效的特征所带的信息应该不会在PCA过程中被大量抛弃。inverse_transform能够在不恢复原始数据的情况下,将降维后的数据返回到原本的高维空间,即是说能够实现”保证维度,但去掉方差很小特征所带的信息“。利用inverse_transform的这个性质,能够实现噪音过滤。

#1.导入需要的库和模块
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np

#2.导入数据,探索数据
digits=load_digits()
print(digits.data.shape)

#3.定义画图函数
def plot_digits(data):
    #data的结构必须是(m,n),并且n要能够被分成(8,8)这样的结构,即n至少必须是64
    fig, axes = plt.subplots(4,10,figsize=(10,4)
                             ,subplot_kw = {"xticks":[],"yticks":[]}
                            )
    for i, ax in enumerate(axes.flat):
        ax.imshow(data[i].reshape(8,8),cmap="binary")
plot_digits(digits.data)

#4.为数据加上噪音
np.random.RandomState(42)
#两个参数,分别是指定的数据集,和抽取出来的正态分布的方差,方差越大,抽取的数据越凌乱
noisy = np.random.normal(digits.data,2)#.random.normal在指定的数据集中,随机抽取服从正态分布的数据
#或者
#rng=np.random.RandomState(42)#规定np中的随机模式
#noisy = rng.normal(digits.data,2)
plot_digits(noisy)

#5.降维
pca = PCA(0.5,svd_solver="full").fit(noisy)#取出能将降维后矩阵所带的特征占有原始数据50%的信息
X_dr = pca.transform(noisy)
print(X_dr.shape)#特征从64降到了6

#6.逆转降维结果,实现降噪
without_noise = pca.inverse_transform(X_dr)#因为画图函数输入的结构要求,所以必须先转换再画图
#特征从6再变成64
plot_digits(without_noise)

5.重要接口,参数和属性总结
上面讲解了重要参数n_components,svd_solver,random_state,讲解了三个重要属性:components_, explained_variance_以及explained_variance_ratio_,无数次用到了接口fit,transform,fit_transform,还讲解了与众不同的重要接口inverse_transform。所有的这些内容都可以被总结在下面这张图中:
在这里插入图片描述

三、案例:PCA对手写数字数据集的降维

手写数字的数据集结构为(42000, 784),用KNN跑一次半小时,得到准确率在96.6%上下,用随机森林跑一次12秒,准确率在93.8%,虽然KNN效果好,但由于数据量太大,KNN计算太缓慢,所以不得不选用随机森林。上面使用了各种技术对手写数据集进行特征选择,最后使用嵌入法SelectFromModel选出了324个特征,将随机森林的效果也调到了96%以上。但是,因为数据量依然巨大,还是有300多个特征。现在试着用PCA处理一下这个数据,看看效果如何。

#1.导入需要的库和模块
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

#2.导入数据,探索数据
data = pd.read_csv(r"C:\work\learnbetter\micro-class\week 3 Preprocessing\digit recognizor.csv")#data是dataframe型,第一列是标签,后面是特征矩阵
X = data.iloc[:,1:]#取出所有行,从索引为1的列到最后一列
y = data.iloc[:,0]#取出所有行,索引为0的那一列
print(X.shape)#(42000,784)

#3.画累计方差贡献率曲线,找最佳降维后维度的范围
pca_line = PCA().fit(X)#返回784个特征的方差贡献率
plt.figure(figsize=[20,5])
plt.plot(np.cumsum(pca_line.explained_variance_ratio_))
#np.cumsum计算方差贡献率的累计和,即第几个数就是pca_line的前几项之和
plt.xlabel("number of components after dimension reduction")
plt.ylabel("cumulative explained variance ratio")
plt.show()#要取转折点,图中可以看出在(0,100)间变化较大

#4.降维后维度的学习曲线,继续缩小最佳维度的范围
#======【TIME WARNING:2mins 30s】======#
score = []
for i in range(1,101,10):#i从1取到101,步长为10
    X_dr = PCA(i).fit_transform(X)#实例化
    once = cross_val_score(RFC(n_estimators=10,random_state=0)#实例化模型
                           ,X_dr,y,cv=5).mean()
    score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,101,10),score)#括号内分别为x轴和y轴的取值
plt.show()#20左右有一个转折点

#5.细化学习曲线,找出降维后的最佳维度
#======【TIME WARNING:2mins 30s】======#
score = []
for i in range(10,25):
    X_dr = PCA(i).fit_transform(X)
    once = cross_val_score(RFC(n_estimators=10,random_state=0),X_dr,y,cv=5).mean()
    score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(10,25),score)
plt.show()#找出了转折点  21

#6.导入找出的最佳维度进行降维,查看模型效果
X_dr = PCA(21).fit_transform(X)
cross_val_score(RFC(n_estimators=10,random_state=0),X_dr,y,cv=5).mean()#91.7%
#======【TIME WARNING:1mins 30s】======#
cross_val_score(RFC(n_estimators=100,random_state=0),X_dr,y,cv=5).mean()#94.4%
#模型效果还好,跑出了94.4%的水平,但还是没有使用嵌入法特征选择过后的96%高,有办法能提高模型的表现呢?

#7.特征数量已经不足原来的3%,换个模型试试
#在之前的建模过程中,因为计算量太大,所以一直使用随机森林,但事实上已知KNN的效果比随机森林更好。
#KNN在未调参的状况下已经达到96%的准确率,而随机森林在未调参前只能达到93%,这是模型本身的限制带来的,这个数据使用KNN效果就是会更好。
#现在特征数量已经降到不足原来的3%,可以使用KNN了吗?
from sklearn.neighbors import KNeighborsClassifier as KNN
cross_val_score(KNN(),X_dr,y,cv=5).mean()#k不填的时候默认为5

#8.KNN的k值学习曲线
#======【TIME WARNING: 】======#
score = []
for i in range(10):
    X_dr = PCA(23).fit_transform(X)
    once = cross_val_score(KNN(i+1),X_dr,y,cv=5).mean()
    score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(10),score)
plt.show()#图中2为转折点,但因为是从0开始的,所以k取3

#9.定下超参数后,模型效果如何,模型运行时间如何?
cross_val_score(KNN(3),X_dr,y,cv=5).mean()#96.8%
#=======【TIME WARNING: 3mins】======#
%%timeit#要和下一行单独为一个cell,表示计算该cell运行时间
cross_val_score(KNN(3),X_dr,y,cv=5).mean()
#可以发现,原本785列的特征被缩减到21列之后,用KNN跑出了目前位置这个数据集上最好的结果。
#再进行更细致的调整,也许可以将KNN的效果调整到98%以上。
#PCA为我们提供了无限的可能,终于不用再因为数据量太庞大而被迫选择更加复杂的模型了!

四、附录

1.PCA参数列表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.PCA属性列表
在这里插入图片描述
3.PCA接口列表
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值