目录
2.3.2,重要参数svd_solver 与 random_state
1,概述
1.1,什么是维度?
我们先来解释一下维度的概念。
对于数组和Series来说,维度就是功能shape返回的结果,shape中返回了几个数字,就是几维。索引以外的数据,不分行列的叫一维(此时shape返回唯一的维度上的数据个数),有行列之分叫二维(shape返回行x列),也称为表。一张表最多二维,复数的表构成了更高的维度。当一个数组中存在2张3行4列的表时,shape返回的是(更高维,行,列)。当数组中存在2组2张3行4列的表时,数据就是4维,shape返回(2,2,3,4)。
数组中的每一张表,都可以是一个特征矩阵或一个DataFrame,这些结构永远只有一张表,所以一定有行列,其中行是样本,列是特征。针对每一张表,维度指的是样本的数量或特征的数量,一般无特别说明,指的都是特征的数量。除了索引之外,一个特征是一维,两个特征是二维,n个特征是n维。
对图像来说,维度就是图像中特征向量的数量。特征向量可以理解为是坐标轴,一个特征向量定义一条直线,是一维,两个相互垂直的特征向量定义一个平面,即一个直角坐标系,就是二维,三个相互垂直的特征向量定义一个空间,即一个立体直角坐标系,就是三维。三个以上的特征向量相互垂直,定义人眼无法看见,也无法想象的高维空间。
降维算法中的”降维“,指的是降低特征矩阵中特征的数量。降维的目的是为了让算法运算更快,效果更好,但其实还有另一种需求:数据可视化。从上面的图我们其实可以看得出,图像和特征矩阵的维度是可以相互对应的,即一个特征对应一个特征向量,对应一条坐标轴。所以,三维及以下的特征矩阵,是可以被可视化的,这可以帮助我们很快地理解数据的分布,而三维以上特征矩阵的则不能被可视化,数据的性质也就比较难理解。
1.2,sklearn中的降维算法
sklearn中降维算法都被包括在模块decomposition中,这个模块本质是一个矩阵分解模块。在过去的十年中,如果要讨论算法进步的先锋,矩阵分解可以说是独树一帜。矩阵分解可以用在降维,深度学习,聚类分析,数据预处理,低纬度特征学习,推荐系统,大数据分析等领域。
2,PCA与SVD
在降维过程中,我们会减少特征的数量,这意味着删除数据,数据量变少则表示模型可以获取的信息会变少,模型的表现可能会因此受影响。同时,在高维数据中,必然有一些特征是不带有有效的信息的(比如噪音),或者有一些特征带有的信息和其他一些特征是重复的(比如一些特征可能会线性相关)。我们希望能够找出一种办法来帮助我们衡量特征上所带的信息量,让我们在降维的过程中,能够即减少特征的数量,又保留大部分有效信息——将那些带有重复信息的特征合并,并删除那些带无效信息的特征等等——逐渐创造出能够代表原特征矩阵大部分信息的,特征更少的,新特征矩阵。
我们提到过一种重要的特征选择方法:方差过滤。如果一个特征的方差很小,则意味着这个特征上很可能有大量取值都相同(比如90%都是1,只有10%是0,甚至100%是1),那这一个特征的取值对样本而言就没有区分度,这种特征就不带有有效信息。从方差的这种应用就可以推断出,如果一个特征的方差很大,则说明这个特征上带有大量的信息。因此,在降维中,PCA使用的信息量衡量指标,就是样本方差,又称可解释性方差,方差越大,特征所带的信息量越多。主成分分析(Principal components analysis,简称PCA)是最重要的降维方法之一。在数据压缩消除冗余和数据噪音消除等领域都有广泛的应用。一般我们提到降维最容易想到的算法就是PCA。
Var代表一个特征的方差,n代表样本量,xi代表一个特征中的每个样本取值,xhat代表这一列样本的均值。
2.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*特征上的数据均值是
,而方差则可表示成:
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分解为以下三个矩阵,其中
和
是辅助的矩阵,Σ是一个对角矩阵(即除了对角线上有值,其他位置都是0的矩阵),其对角线上的元素就是方差。降维完成之后,PCA找到的每个新特征向量就叫做“主成分”,而被丢弃的特征向量被认为信息量很少,这些信息很可能就是噪音。
而SVD使用奇异值分解来找出空间V,其中Σ也是一个对角矩阵,不过它对角线上的元素是奇异值,这也是SVD中用来衡量特征上的信息量的指标。U和分别是左奇异矩阵和右奇异矩阵,也都是辅助矩阵。
在数学原理中,无论是PCA和SVD都需要遍历所有的特征和样本来计算信息量指标。并且在矩阵分解的过程之中,会产生比原来的特征矩阵更大的矩阵,比如原数据的结构是(m,n),在矩阵分解中为了找出最佳新特征空间V,可能需要产生(n,n),(m,m)大小的矩阵,还需要产生协方差矩阵去计算更多的信息。而现在无论是Python还是R,或者其他的任何语言,在大型矩阵运算上都不是特别擅长,无论代码如何简化,我们不可避免地要等待计算机去完成这个非常庞大的数学计算过程。因此,降维算法的计算量很大,运行比较缓慢,但无论如何,它们的功能无可替代,它们依然是机器学习领域的宠儿。
PCA和特征选择技术都是特征工程的一部分,它们有什么不同?
- 特征选择是从已存在的特征中选取携带信息最多的,选完之后的特征依然具有可解释性,我们依然知道这个特征在原数据的哪个位置,代表着原数据上的什么含义。
- 而PCA,是将已存在的特征进行压缩,降维完毕后的特征不是原本的特征矩阵中的任何一个特征,而是通过某些方式组合起来的新特征。通常来说,在新的特征矩阵生成之前,我们无法知晓PCA都建立了怎样的新特征向量,新特征矩阵生成之后也不具有可读性,我们无法判断新特征矩阵的特征是从原数据中的什么特征组合而来,新特征虽然带有原始数据的信息,却已经不是原数据上代表着的含义了。以PCA为代表的降维算法因此是特征创造(feature creation,或feature construction)的一种。
- 可以想见,PCA一般不适用于探索特征和标签之间的关系的模型(如线性回归),因为无法解释的新特征和标签之间的关系不具有意义。在线性回归模型中,我们使用特征选择。
2.2,重要参数n_components
n_components是我们降维后需要的维度,即降维后需要保留的特征数量,降维流程中第二步里需要确认的k值,一般输入[0, min(X.shape)]范围中的整数。一说到K,大家可能都会想到,类似于KNN中的K和随机森林中的n_estimators,这是一个需要我们人为去确认的超参数,并且我们设定的数字会影响到模型的表现。如果留下的特征太多,就达不到降维的效果,如果留下的特征太少,那新特征向量可能无法容纳原始数据集中的大部分信息,因此,n_components既不能太大也不能太小。那怎么办呢?
可以先从我们的降维目标说起:如果我们希望可视化一组数据来观察数据分布,我们往往将数据降到三维以下,很多时候是二维,即n_components的取值为2。
2.2.1,高维数据的可视化
- 可视化操作
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
# 提取数据集
data=load_iris()
x=data.data
y=data.target
# 现在作为数组是二维数组,但是特征矩阵,是4维
data.data.shape
import pandas as pd
import numpy as np
# 作为数据表或者特征矩阵,那么x是多少维呢?也就是特征的数量
# 有4个特征,所以说是一个4维的特征矩阵
# 下载数据是四维,无法进行可视化,所以需要进行降维可视化
pd.DataFrame(x)
# 四个特征分别是:花瓣的长度和宽度,花萼的长度和宽度,有3种花
# 现在我们想把这3中花在二维图像上面的分布进行可视化,但是现在特征矩阵有4个特征,我们无法可视化
# 所以需要进行降维处理
# 建模,调用pca
# 一共有两个特征,参数n_components标示降到多少维
pca=PCA(n_components=2)# 实例化一个特征矩阵,降到2维
# 拟合建模
pca=pca.fit(x)
# 获取新的矩阵
x_da=pca.transform(x)
pd.DataFrame(x_da)
# 降维后只剩下2列特征
np.unique(y)
# 也可以对降维操作一步到位
x_dr=PCA(2).fit_transform(x)
x_dr.shape
# 应为降维后的数据里面有三种花,所以现在需要对降维后的数据进行区分
#要将三种鸢尾花的数据分布显示在二维平面坐标系中,对应的两个坐标(两个特征向量)应该是三种鸢尾花降维后的
x1和x2,怎样才能取出三种鸢尾花下不同的x1和x2呢?
X_dr[y == 0, 0] #这里是布尔索引,看出来了么?
# 标示在降维后的数据集中,在第0列取出标签为0的花
# 现在对三种花进行绘制散点图
plt.figure(figsize=(20,10))
colors=['red','black','orange']
for i in range(3):
# 先取出每一种花的数据
# alpha参数标示画出来的图像的透明度
plt.scatter(x_dr[y == i,0],x_dr[y == i,1],c=colors[i],alpha=0.7,label=data.target_names[i])
plt.legend()
plt.show()
鸢尾花的分布被展现在我们眼前了,明显这是一个分簇的分布,并且每个簇之间的分布相对比较明显,也许versicolor和virginia这两种花之间会有一些分类错误,但setosa肯定不会被分错。这样的数据很容易分类,可以遇见,KNN,随机森林,神经网络,朴素贝叶斯,Adaboost这些分类器在鸢尾花数据集上,未调整的时候都可以有95%上下的准确率。
- 探索降维后数据
# 探索降维后的数据集
# pca算法是用方差来衡量数据所携带的信息量
# 我们可以调用属性explained_variance_来查看降维后每一个特征上面所携带的信息量,可解释性的方差大小
# 压缩数据时,我们尽量吧数据压缩到某一个特征上面数据量少的特征上
pca.explained_variance_
# 因为降维后剩下两个特征,所以会返回两个可解释性方差
#属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
#又叫做可解释方差贡献率
pca.explained_variance_ratio_
# 标示降维后特征向量的总信息量占原始特征总信息量的百分比
# 也就是说原来四个特征,现在我们进行降维操作,降到2维,但是减少的信息量不到3%
pca.explained_variance_ratio_.sum()
- 选择最好的n_components:累积可解释方差贡献率曲线
当参数n_components中不填写任何值,则默认返回min(X.shape)个特征,一般来说,样本量都会大于特征数目,所以什么都不填就相当于转换了新特征空间,但没有减少特征的个数。一般来说,不会使用这种输入方式。但我们却可以使用这种输入方式来画出累计可解释方差贡献率曲线,以此选择最好的n_components的整数取值。
累积可解释方差贡献率曲线是一条以降维后保留的特征个数为横坐标,降维后新特征矩阵捕捉到的可解释方差贡献率为纵坐标的曲线,能够帮助我们决定n_components最好的取值。
pca_line=PCA().fit(x)
pca_line.explained_variance_ratio_
# 返回的是每一个特征所携带的信息量
import numpy as np
# cumsum()函数返回的是数组中每一个元素和其前面元素的累加和
np.cumsum(pca_line.explained_variance_ratio_)
plt.figure(figsize=(10,10))
plt.plot([*range(1,5)],np.cumsum(pca_line.explained_variance_ratio_))
# x轴标示降维后的特征的个数
plt.xlabel("number of components after dimension reduction")
# y轴标示累积降维后方差的比例
plt.ylabel("cumulative explained variance ratio")
# plt.xticks(range(1,5))
plt.show()
# 从图像可以看出,当我们有很多特征的时候,一般的话我们选取转折点处的数据
2.2.2,最大似然估计自选超参数
除了输入整数,n_components还有哪些选择呢?之前我们提到过,矩阵分解的理论发展在业界独树一帜,勤奋智慧的数学大神Minka, T.P.在麻省理工学院媒体实验室做研究时找出了让PCA用最大似然估计(maximum likelihood estimation)自选超参数的方法,输入“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
# 结果返回三列特征,也就是说3是最好的超参数取值
2.2.3,按信息量占比选超参数
输入[0,1]之间的浮点数,并且让参数svd_solver =='full',表示希望降维后的总解释性方差占比大于n_components指定的百分比,即是说,希望保留百分之多少的信息量。比如说,如果我们希望保留97%的信息量,就可以输入n_components = 0.97,PCA会自动选出能够让保留的信息量超过97%的特征数量。
# 按信息量占比选超参数
# n_components输入的是占比,也就是要求所选择的特征的信息量要占到97%左右,
pca=PCA(n_components=0.97,svd_solver='full')
pca=pca.fit(x)
x_f=pca.transform(x)
x_f
# 结果返回了两列的特征,也就是pca算法选择了两列特征,这两列特征所含的信息量已经达到了97左右
x_f=pca.transform(x)
2.3,PCA中的SVD
2.3.1,pca中的svd
svd_solver是奇异值分解器的意思,为什么PCA算法下面会有有关奇异值分解的参数?不是两种算法么?我们之前曾经提到过,PCA和SVD涉及了大量的矩阵计算,两者都是运算量很大的模型,但其实,SVD有一种惊人的数学性质,即是它可以跳过数学神秘的宇宙,不计算协方差矩阵,直接找出一个新特征向量组成的n维空间,而这个n维空间就是奇异值分解后的右矩阵(所以一开始在讲解降维过程时,我们说”生成新特征向量组成的空间V",并非巧合,而是特指奇异值分解中的矩阵
)。
右奇异矩阵有着如下性质:
k就是n_components,是我们降维后希望得到的维度。若X为(m,n)的特征矩阵, 就是结构为(n,n)的矩阵,取这个矩阵的前k行(进行切片),即将V转换为结构为(k,n)的矩阵。而$V_{(k,n)}^T$与原特征矩阵X相乘,即可得到降维后的特征矩阵X_dr。这是说,奇异值分解可以不计算协方差矩阵等等结构复杂计算冗长的矩阵,就直接求出新特征空间和降维后的特征矩阵。
简而言之,SVD在矩阵分解中的过程比PCA简单快速,虽然两个算法都走一样的分解流程,但SVD可以作弊耍赖直接算出V。但是遗憾的是,SVD的信息量衡量指标比较复杂,要理解”奇异值“远不如理解”方差“来得容易,因此,sklearn将降维流程拆成了两部分:一部分是计算特征空间V,由奇异值分解完成,另一部分是映射数据和求解新特征矩阵,由主成分分析完成,实现了用SVD的性质减少计算量,却让信息量的评估指标是方差,具体流程如下图:
讲到这里,相信大家就能够理解,为什么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_.shape
# 返回的是新的特征空间V(k,n)
# 返回的是2*4的矩阵,但是原始矩阵是150*4,也就是现在返回的是k*n
# 现在的输出结果就是降维后的结果V(k,n)
2.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",通过Halko等人的随机方法进行随机SVD。在"full"方法中,分解器会根据原始数据和输入的n_components值去计算和寻找符合需求的新特征向量,但是在"randomized"方法中,分解器会先生成多个随机向量,然后一一去检测这些随机向量中是否有任何一个符合我们的分解需求,如果符合,就保留这个随机向量,并基于这个随机向量来构建后续的向量空间。这个方法已经被Halko等人证明,比"full"模式下计算快很多,并且还能够保证模型运行效果。适合特征矩阵巨大,计算量庞大的情况。 |
而参数random_state在参数svd_solver的值为"arpack" or "randomized"的时候生效,可以控制这两种SVD模式中的随机模式。通常我们就选用”auto“,不必对这个参数纠结太多。
2.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_参数在人脸识别中的应用
from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
# svd就是奇异值分解器
# 获取人脸数据集
faces = fetch_lfw_people(min_faces_per_person=60)
faces.data.shape #1348*2914
# 行是样本,列是所有行相关的特征
# data数据集标示所有图像的特征数量
# faces.images.shape标示一张图的特征数量
# 虽然是二维,但是无法进行可视化,因为1348代表的是图像个数
# 是一个三维数组
faces.images.shape# 这是我们画图的矩阵,1348*62*47
# 三维矩阵,第一个数代表第三维,后面两个数代表行和列
# 对于此组数据,第一个数1348代表图像的个数
# 62代表每一个特征矩阵的行数
# 47代表每一个特征矩阵的列,也就是特征的个数
# 所以真正的特征矩阵是62*47
# 一个特征矩阵决定了一张图片
# 也就是说,一张表有62行,47列,对应一张图片,一共有1348张这样的表,也就是1348张图片
# 而62*47=2914,刚好就是faces.data.shape的特征个数
# 可以对62*47进行可视化,因为代表一张图片
x=faces.data# 这是我们本质的特征矩阵
# 绘制子图,里面的前两个参数标示绘制多少行,多少列
# figsize(8,4)标示整个画布的大小,也就是横向是8,纵向是4
# subplot_kw={'xticks':[],'yticks':[]}标示不显示坐标
fig,axes=plt.subplots(4,5,figsize=(8,10),subplot_kw={'xticks':[],'yticks':[]})
axes[0][0].imshow(faces.images[1,:,:])
axes[0][1].imshow(faces.images[2,:,:])
# faces.images[1,:,:]这样操作,是因为三维数组中,第一个数代表图像的个数,后两个数代表行和列
# 现在我们画出原始的图像
# 现在对这个扁平化的对象进行遍历
# for i,ax in [*enumerate(axes.flat)]:
# print(ax)
# 这样可以同时遍历图像和数字
# 创建画布
fig,axes=plt.subplots(4,5,figsize=(8,10),subplot_kw={'xticks':[],'yticks':[]})
# 在画布里面填写内容
for i,ax in enumerate(axes.flat):
# i就是元组里面的序号,ax就是画图的对象
# 对子图画图就是对ax对象进行操作
ax.imshow(faces.images[i,:,:],cmap='gray')
# 现在开始对我们的特征进行降维处理
# 现在对人脸数据进行降维处理
# 现在降维只要150个特征
pca=PCA(150).fit(x)
# 返回结果是V(k,n),k就是代表我们降维后的特征个数
# v实际上是v(k,n)
v=pca.components_
print(v.shape)
# 注意,但是此时v是一个新的特征空间,需要乘以原来的特征向量才可以把原来的特征向量映射到新的特征空间,产生新的特征向量,需要使用transform()进行转换获取新的特征向量
# k=150
# n=2914
# v其实就是代表我们要用来映射的新的向量空间
# v决定这我们的新的特征叫做什么,是什么,代表什么含义
# v*x结果就是我们降维后的矩阵
v[0].shape#,代表所有图像的特征
# 又重新还原成我们最初的图像的特征
v[0].reshape(62,47).shape
# 现在对降维后的数据进行可视化
# 现在对这个扁平化的对象进行遍历
# for i,ax in [*enumerate(axes.flat)]:
# print(ax)
# 这样可以同时遍历图像和数字
# 创建画布
fig,axes=plt.subplots(3,8,figsize=(8,4),subplot_kw={'xticks':[],'yticks':[]})
# 在画布里面填写内容
for i,ax in enumerate(axes.flat):
# 对子图画图就是对ax对象进行操作
# 这里的i代表第几个图片
ax.imshow(v[i,:].reshape(62,47),cmap='gray')
这张图稍稍有一些恐怖,但可以看出,比起降维前的数据,新特征空间可视化后的人脸非常模糊,这是因为原始数据还没有被映射到特征空间中。但是可以看出,整体比较亮的图片,获取的信息较多,整体比较暗的图片,却只能看见黑漆漆的一块。在比较亮的图片中,眼睛,鼻子,嘴巴,都相对清晰,脸的轮廓,头发之类的比较模糊。
这说明,新特征空间里的特征向量们,大部分是"五官"和"亮度"相关的向量,所以新特征向量上的信息肯定大部分是由原数据中和"五官"和"亮度"相关的特征中提取出来的。到这里,我们通过可视化新特征空间V,解释了一部分降维后的特征:虽然显示出来的数字看着不知所云,但画出来的图表示,这些特征是和”五官“以及”亮度“有关的。这也再次证明了,PCA能够将原始数据集中重要的数据进行聚集。
2.4,重要接口inverse_transform
我们学到了神奇的接口inverse_transform,可以将我们归一化,标准化,甚至做过哑变量的特征矩阵还原回原始数据中的特征矩阵,这几乎在向我们暗示,任何有inverse_transform这个接口的过程都
是可逆的。PCA应该也是如此。在sklearn中,我们通过让原特征矩阵X右乘新特征空间矩阵V(k,n)来生成新特征矩阵X_dr,那理论上来说,让新特征矩阵X_dr右乘V(k,n)的逆矩阵,就可以将新特征矩阵X_dr还原为X。那sklearn是否这样做了呢?让我们来看看下面的案例。
2.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)返回一模一样的图像,即携带一模一样的信息。
# 导入需要的库
from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
# 导入数据集
faces=fetch_lfw_people(min_faces_per_person=60)
# 获取人脸数据集
x=faces.data
# 实例化模型
pca=PCA(n_components=150)
# 拟合+提取结果
x_dr=pca.fit_transform(x)
# x_dr是降维后的新的特征矩阵,经过转换后得到新的特征向量
x_dr.shape# (1348, 150)
pca.components_
# components_里面保存的矩阵是特征空间(150, 2914)
# 把新得到的特征矩阵重新逆转会原矩阵
x_inverse=pca.inverse_transform(x_dr)
x_inverse.shape
# 把降维后的特征矩阵逆转,也就是变为我们最初的特征矩阵(1348, 2914)
# 现在可视化x_inverse和x_dr数据,对比数据是否是一样的
fig1,ax1=plt.subplots(2,10,figsize=(10,2.5),subplot_kw={"xticks":[],"yticks":[]})
for i in range(10):
ax1[0,i].imshow(faces.images[i,:,:],cmap='binary_r')
ax1[1,i].imshow(x_inverse[i].reshape(62,47),cmap='binary_r')
# 现在发现逆转的数据图像比较模糊,就是因为逆转的数据特征只有150个特征
# 但是并不代表逆转过程中把150个特征完全复原,只是重组为2914个特征而已
# 切记,元数据没有重新逆转,只是将150个特征重新映射到原来的特征空间当中
# 降维过程是不可逆的
可以明显看出,这两组数据可视化后,由降维后再通过inverse_transform转换回原维度的数据画出的图像和原数据画的图像大致相似,但原数据的图像明显更加清晰。这说明,inverse_transform并没有实现数据的完全逆转。这是因为,在降维的时候,部分信息已经被舍弃了,X_dr中往往不会包含原数据100%的信息,所以在逆转的时候,即便维度升高,原数据中已经被舍弃的信息也不可能再回来了。所以,降维不是完全可逆的。
Inverse_transform的功能,是基于X_dr中的数据进行升维,将数据重新映射到原数据所在的特征空间中,而并非恢复所有原有的数据。但同时,我们也可以看出,降维到300以后的数据,的确保留了原数据的大部分信息,所以图像看起来,才会和原数据高度相似,只是稍稍模糊罢了。
2.4.2,用PCA做噪音过滤
降维的目的之一就是希望抛弃掉对模型带来负面影响的特征,而我们相信,带有效信息的特征的方差应该是远大于噪音的,所以相比噪音,有效的特征所带的信息应该不会在PCA过程中被大量抛弃。inverse_transform能够在不恢复原始数据的情况下,将降维后的数据返回到原本的高维空间,即是说能够实现”保证维度,但去掉方差很小特征所带的信息“。利用inverse_transform的这个性质,我们能够实现噪音过滤。
# 案例,使用pca进行噪音过滤
# 导入需要的模块,使用手写字数据集
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
datas=load_digits()
# 以二维数组形式存在的手写数据集(1797, 64)
datas.data.shape
#(1797, 8, 8)
datas.images.shape
#(1797,)
datas.target.shape
#{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
set(datas.target.tolist())
# 可视化我们的数据集
fig,axes=plt.subplots(4,10,figsize=(10,4),subplot_kw={"xticks":[],"yticks":[]})
for i,ax in enumerate(axes.flat):
ax.imshow(datas.data[i].reshape(8,8),cmap='binary_r')
# 现在开始人为的添加噪声
# 标示规定numpy中的随机模式
rng=np.random.RandomState(42)
#在指定的数据集中,随机抽取服从正态分布的数据
#两个参数,分别是指定的数据集,和抽取出来的正太分布的方差
noisy=rng.normal(datas.data,2)
noisy.shape
# 随机的数据和我们手写体的数据维度一样 (1797, 64)
# plot_digit(datas.data)
# 带噪声的数据可视化
fig,axes=plt.subplots(4,10,figsize=(10,4),subplot_kw={"xticks":[],"yticks":[]})
for i,ax in enumerate(axes.flat):
ax.imshow(noisy[i].reshape(8,8),cmap='binary_r')
# 现在对添加噪声的数据进行降噪声处理
# 0.5标示降维后的特征所带的信息量占原始特征信息量的50%
pca=PCA(n_components=0.5,svd_solver="full").fit(noisy)
x_dr=pca.transform(noisy)
x_dr.shape
# 现在进行降噪音处理,只剩下6个特征 (1797, 6)
# 现在对降噪声的数据进行逆转,然后在进行可视化
without_noisy=pca.inverse_transform(x_dr)
without_noisy.shape
# 把降维后的数据集逆转,因为要可视化,但是可视化的数据集必须是64个特征以上,所以我们将数据集逆转
fig,axes=plt.subplots(4,10,figsize=(10,4),subplot_kw={"xticks":[],"yticks":[]})
for i,ax in enumerate(axes.flat):
ax.imshow(without_noisy[i].reshape(8,8),cmap='binary_r')
# components_属性可以调用新的向量空间中的特征矩阵,也就是v(k,n)
# pca.explained_variance_ 属性携带的是降维后每一个特征所携带的信息量
# pca.explained_variance_ratio_属性表示可解释性方差的贡献率,可以用此属性进行画图,获取n_components的取值
2.4.3,小结
重要参数参数n_components,svd_solver,random_state,
讲解了三个重要属性:components_, explained_variance_以及explained_variance_ratio_,无数次用到了接口fit,transform,fit_transform,还讲解了与众不同的重要接口inverse_transform。
参考资料
[1] https://www.cnblogs.com/pinard/p/6239403.html
[2] https://www.cnblogs.com/pinard/p/6251584.html
[3] https://www.bilibili.com/video/BV1WJ411k7L3?p=228
[4] https://scikit-learn.org/stable/