1.概述
1.1什么是维度
- 对于降维算法,我们到底降得是什么东西。我们知道,当上级交给你一个建模任务的时候,在调用算法之前,你会经历数据预处理和特征工程,我们知道这其中就涉及到特征的选择,无论你是通过业务场景还是过滤法,嵌入式,包装法来筛选特征,但是我们都是一个目的,减少特征的数目,其实这就是降维,但是后续我们有更高级的方法,来实现降维。也就是PCA,SVD等。
1.2 sklearn中的降维算法
- sklearn中降维算法都被包括在模块decomposition中,这个模块本质是一个矩阵分解模块。在过去的十年中,如果要讨论算法进步的先锋,矩阵分解可以说是独树一帜。矩阵分解可以用在降维,深度学习,聚类分析,数据预处理,低纬度特征学习,推荐系统,大数据分析等领域。在2006年,Netflix曾经举办了一个奖金为100万美元的推荐系统算法比赛,最后的获奖者就使用了矩阵分解中的明星:奇异值分解SVD。下面列举sklearn中的降维类。
- 主成分分析
- decomposition.PCA: 主成分分析(PCA)
- decomposition.IncrementalPCA:增量主成分分析(IPCA)
- decomposition.KerneIPCA:核主成分分析(KPCA)
- decomposition.MiniBatchSparsePCA:小批量稀疏主成分分析
- decomposition.SparsePCA:稀疏主成分分析
- decomposition.TruncatedSVD:截断的主成分分析
- 因子分析
- decomposition.FactorAnalysis:因子分析(FA)
- 独立成分分析
- decomposition.FastIPCA
- 主成分分析
2. PCA与SVD
- 在降维过程中,我们会减少特征的数量,这意味着删除数据,数据量变少则表示模型可以获取的信息会变少,模型的表现可能会因此受影响。同时,在高维数据中,必然有一些特征是不带有有效的信息的(比如噪音),或者有一些特征带有的信息和其他一些特征是重复的(比如一些特征可能会线性相关)。我们希望能够找出一种办法来帮助我们衡量特征上所带的信息量,让我们在降维的过程中,能够即减少特征的数量,又保留大部分有效信息——将那些带有重复信息的特征合并,并删除那些带无效信息的特征等等——逐渐创造出能够代表原特征矩阵大部分信息的,特征更少的,新特征矩阵。
- 在降维中,PCA使用的信息量衡量指标,就是样本方差,又称可解释性方差,方差越大,特征所带的信息量越多。样本方差公式:,其中,代表一个特征的方差,代表样本量,代表一个特征中的每个样本取值,代表这一列样本的均值。
2.1 估计量的无偏性
-
方差计算公式中为什么除数是n-1?这是为了得到样本方差的无偏估计。我们知道对于模型参数的估计,有矩估计,极大似然估计,贝叶斯估计和最小均方误差。前三种属于点估计,后一种属于具体的数量型指标。对于点估计来说,同一个参数往往有不同的,看起来合理的估计方法,我们自然的想知道哪一个估计出的参数是最好的。这个问题猛看起来很简单,我们只需把不同方法估计出来的参数,比较一下谁的误差小,不就行了吗。但是,由于是未知的,我们无法用来比较。这还不是主要的问题,更重要的是估计出来的值和样本的分布有很大关系,有时候有些样本使用 效果好,另一个样本 的效果好,也有可能一个很好的估计参数,由于抽样的问题,其表现的很差。
-
这时我们就需要一种衡量指标,从整体性能来衡量它。这里整体性能,有两种意义:一是指估计量的某种特性。具有这种特性就是好的,反之就是差的,比如似然估计和贝叶斯估计;二是指某种具体的数量型指标。两个估计量,指标小的是最优的,比如均方误差。
-
简单的理解,无偏估计就是样本数字特征(期望,方差等)的数学期望等于总体数字特征的数学期望。估计量的无偏性有两个含义。
- 第一个是大数定理。设想估计量,每用一次我们就记录下来,多无限多次的情况下,各次估计值的平均会依概率收敛到被估计值。也就是,若估计量具有无偏性时,则在大量的使用中,取平均值后,能以100%的把握无限逼近真实的估计量,若没有无偏性,平均值和真实值会保持一定距离——这个距离就是系统误差。
- 第二个是没有系统性偏差。使用估计量去估计真实值,在某些样本中会偏高,在另一些样本中会偏低。我们把这些正负的偏差在概率上平均起来,其值为0.比如用一杆秤去秤商品,误差来源有两种,第一是秤本身制作结构上的问题(算法设计)使它会秤出偏高(或偏低,只能取其一)的结果,这属于系统误差。另一种是随机误差,由于在操作上和其他随机性原因而引起的误差(模型所需样本,抽样的问题)。在此,无偏估计要求没有系统误差,但随机误差不可避免的。因此无偏估计不等于在任何时候都能给出正确无误的估计。
-
估计量的无偏性是一个优良的性质,比如在秤是无系统误差的,也就是店里在秤上显示的重量是你所购买商品的真实重量的无偏估计,尽管你在某次中,店里可能少或多给你一些,但是长远平均来看,无偏性保证了双方都不吃亏,因此无偏性很有显示意义。现在设想另一个情况,工厂每周要进一批原料,在使用之前会对某种成分含量的百分比进行估计,根据估计的值进行相应的工艺调整,无论 比 偏高还是偏低,都会损于产品的质量,在此,即使 是 的无偏估计,在长期中,正负偏差的效应也不能抵消,这样的无偏性就不见得有实用的意义了。因此,无偏性的实际价值如何,还要结合实际具体的问题。
2.2 降维究竟是怎样实现?
-
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°,形成了新的特征向量 和 组成的新平面,在这个新平面中,三个样本数据的坐标点可以表示为可以注意到, 上的数值此时都变成了0,因此 明显不带有任何有效信息了,此时的方差也为0了。此时,特征上的数据均值是 ,而方差则可表示成 2 。通过旋转原有特征向量组成的坐标轴来找到新特征向量和新坐标平面,我们将三个样本点的信息压缩到了一条直线上,实现了二维变一维,并且尽量保留原始数据的信息。
-
我们用来找出n个新特征向量,让数据能够被压缩到少数特征上并且总信息量不损失太多的技术就是矩阵分解。PCA和SVD是两种不同的降维算法,但他们都遵从上面的过程来实现降维,只是两种算法中矩阵分解的方法不同,信息量的衡量指标不同罢了。PCA使用方差作为信息量的衡量指标,并且特征值分解来找出空间V。降维时,它会通过一系列数学的操作(比如说,产生协方差矩阵)将特征矩阵X分解为 ,其中 是辅助的矩阵, 是一个对角矩阵(即除了对角线上有值,其他位置都是0的矩阵),其对角线上的元素就是方差。降维完成之后,PCA找到的每个新特征向量就叫做“主成分”,而被丢弃的特征向量被认为信息量很少,这些信息很可能就是噪音。
-
SVD将特征矩阵X分解为 ,它是使用奇异值分解来找出空间V,其中 也是一个对角矩阵,不过它对角线上的元素是奇异值,这也是SVD中用来衡量特征上的信息量的指标。 分别是左奇异矩阵和右奇异矩阵,也都是辅助矩阵。
-
在数学原理中,无论是PCA和SVD都需要遍历所有的特征和样本来计算信息量指标。并且在矩阵分解的过程之中,会产生比原来的特征矩阵更大的矩阵,比如原数据的结构是(m,n),在矩阵分解中为了找出最佳新特征空间V,可能需要产生(n,n),(m,m)大小的矩阵,还需要产生协方差矩阵去计算更多的信息。而现在无论是Python还是R,或者其他的任何语言,在大型矩阵运算上都不是特别擅长,无论代码如何简化,我们不可避免地要等待计算机去完成这个非常庞大的数学计算过程。因此,降维算法的计算量很大,运行比较缓慢,但无论如何,它们的功能无可替代。
-
PCA一般不适用于探索特征和标签之间的关系的模型(如线性回归),因为无法解释的新特征和标签之间的关系不具有意义。PCA,是将已存在的特征进行压缩,降维完毕后的特征不是原本的特征矩阵中的任何一个特征,而是通过某些方式组合起来的新特征。通常来说,在新的特征矩阵生成之前,我们无法知晓PCA都建立了怎样的新特征向量,新特征矩阵生成之后也不具有可读性,我们无法判断新特征矩阵的特征是从原数据中的什么特征组合而来,新特征虽然带有原始数据的信息,却已经不是原数据上代表着的含义了。
2.3 重要参数n_components
- n_components是我们降维后需要的维度,即降维后需要保留的特征数量,这是一个需要我们人为去确认的超参数,并且我们设定的数字会影响到模型的表现。如果留下的特征太多,就达不到降维的效果,如果留下的特征太少,那新特征向量可能无法容纳原始数据集中的大部分信息,因此,n_components既不能太大也不能太小。那怎么办呢?可以先从我们的降维目标说起:如果我们希望可视化一组数据来观察数据分布,我们往往将数据降到三维以下,很多时候是二维,即n_components的取值为2。
2.3.1 学习曲线选择超参数
- 我们使用PCA,将将三种鸢尾花的数据分布显示在二维平面坐标系中。
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import pandas as pd
import numpy as np
iris = load_iris()
y = iris.target
X = iris.data
#调用PCA
pca = PCA(n_components=2)
X_dr = pca.fit_transform(X)
plt.figure(figsize=(10,6))
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()
展示结果:
2019090901.png
- 鸢尾花的分布被展现在我们眼前了,明显这是一个分簇的分布,并且每个簇之间的分布相对比较明显,也许versicolor和virginia这两种花之间会有一些分类错误,但setosa肯定不会被分错。这样的数据很容易分类,可以遇见,KNN,随机森林,神经网络,朴素贝叶斯,Adaboost这些分类器在鸢尾花数据集上,未调整参数的时候都可以有很高的准确率。
# 降维后的数据
#属性explained_variance_,查看降维后每个新特征向量上所带的信息量大小(可解释性方差的大小)
print(pca.explained_variance_)
#属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
#又叫做可解释方差贡献率
print(pca.explained_variance_ratio_)
#大部分信息都被有效地集中在了第一个特征上
pca.explained_variance_ratio_.sum()
结果展示:
[4.22824171 0.24267075]
[0.92461872 0.05306648]
0.9776852063187949
- 累积可解释方差贡献率曲线
当参数n_components中不填写任何值,则默认返回min(X.shape)个特征,一般来说,样本量都会大于特征数目,所以什么都不填就相当于转换了新特征空间,但没有减少特征的个数。一般来说,不会使用这种输入方式。但我们却可以使用这种输入方式来画出累计可解释方差贡献率曲线,以此选择最好的n_components的整数取值。累积可解释方差贡献率曲线是一条以降维后保留的特征个数为横坐标,降维后新特征矩阵捕捉到的可解释方差贡献率为纵坐标的曲线,能够帮助我们决定n_components最好的取值。
# 累积可解释方差贡献率曲线
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()
结果展示:
2019090902.png
2.3.2 最大似然估计自选超参数
- 除了输入整数,n_components还有哪些选择呢?之前我们提到过,矩阵分解的理论发展在业界独树一帜,勤奋智慧的数学大神Minka, T.P.在麻省理工学院媒体实验室做研究时找出了让PCA用最大似然估计(maximum likelihood estimation)自选超参数的方法,输入“mle”作为n_components的参数输入,就可以调用这种方法。
pca_mle = PCA(n_components="mle")
X_mle = pca_mle.fit_transform(X)
X_mle = pca_mle.transform(X)
print(pca_mle.explained_variance_)
print(pca_mle.explained_variance_ratio_)
#可以发现,mle为我们自动选择了3个特征
pca_mle.explained_variance_ratio_.sum()
#得到了比设定2个特征时更高的信息含量,对于鸢尾花这个很小的数据集来说,
# 3个特征对应这么高的信息含量,并不需要去纠结于只保留2个特征,毕竟三个特征也可以可视化
运行结果:
[4.22824171 0.24267075 0.0782095 ]
[0.92461872 0.05306648 0.01710261]
0.9947878161267246
2.3.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")
X_f = pca_f.fit_transform(X)
print(pca_mle.explained_variance_)
print(pca_mle.explained_variance_ratio_)
pca_mle.explained_variance_ratio_.sum()
运行结果:
[4.22824171 0.24267075 0.0782095 ]
[0.92461872 0.05306648 0.01710261]
0.9947878161267246
2.4 SVD
2.4.1 PCA中的SVD哪里来?
-
细心的小伙伴可能注意到了,svd_solver是奇异值分解器的意思,为什么PCA算法下面会有有关奇异值分解的参数?不是两种算法么?我们之前曾经提到过,PCA和SVD涉及了大量的矩阵计算,两者都是运算量很大的模型,但其实,SVD有一种惊人的数学性质,即是它可以不计算协方差矩阵,直接找出一个新特征向量组成的n维空间,而这个n维空间就是奇异值分解后的右矩阵 。我们说”生成新特征向量组成的空间V",并非巧合,而是特指奇异值分解中的矩阵。
-
右奇异矩阵, 有着这样的性质:。k就是n_components,是我们降维后希望得到的维度。这是说,奇异值分解可以不计算协方差矩阵等等结构复杂计算冗长的矩阵,就直接求出新特征空间和降维后的特征矩阵。
-
简而言之,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_ 当中,可以调用查看。
# 查看 V
PCA(2).fit(X).components_
运行结果:
array([[ 0.36138659, -0.08452251, 0.85667061, 0.3582892 ],
[ 0.65658877, 0.73016143, -0.17337266, -0.07548102]])
2.4.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,可以加快运算速度,适合特征矩阵很大的时候,但一般用于特征矩阵为稀疏矩阵的情况,此过程包含一定的随机性。
- "randomized",通过Halko等人的随机方法进行随机SVD。在"full"方法中,分解器会根据原始数据和输入的n_components值去计算和寻找符合需求的新特征向量,但是在"randomized"方法中,分解器会先生成多个随机向量,然后一一去检测这些随机向量中是否有任何一个符合我们的分解需求,如果符合,就保留这个随机向量,并基于这个随机向量来构建后续的向量空间。这个方法已经被Halko等人证明,比"full"模式下计算快很多,并且还能够保证模型运行效果。适合特征矩阵巨大,计算量庞大的情况。
- 参数random_state在参数svd_solver的值为"arpack" or "randomized"的时候生效,可以控制这两种SVD模式中的随机模式。通常我们就选用”auto“,不必对这个参数纠结太多。
2.4.3 重要属性components_
- 现在我们了解了,V(k,n)是新特征空间,是我们要将原始数据进行映射的那些新特征向量组成的矩阵。我们用它来计算新的特征矩阵,但我们希望获取的新的特征矩阵,为什么我们要把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
# 实例化数据集,探索数据
faces = fetch_lfw_people(min_faces_per_person=60)
print(faces.images.shape)
print(faces.data.shape)
X = faces.data
运行结果:
(1348, 62, 47)
(1348, 2914)
#创建画布和子图对象
fig, axes = plt.subplots(4,5,
figsize=(10,6),
subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
)
#填充图像
for i, ax in enumerate(axes.flat):
ax.imshow(faces.images[i,:,:]
,cmap="gray" #选择色彩的模式
)
运行结果:
2019090903.png
# 建模降维,提取新特征空间矩阵
#原本有2900维,我们现在来降到150维
pca = PCA(150).fit(X)
V = pca.components_
# 将新特征空间矩阵可视化
fig, axes = plt.subplots(4,5,figsize=(4,5),subplot_kw = {"xticks":[],"yticks":[]})
for i, ax in enumerate(axes.flat):
ax.imshow(V[i,:].reshape(62,47),cmap="gray")
运行结果:
2019090904.png
- 可以看出,比起降维前的数据,新特征空间可视化后的人脸非常模糊,这是因为原始数据还没有被映射到特征空间中。但是可以看出,整体比较亮的图片,获取的信息较多,整体比较暗的图片,却只能看见黑漆漆的一块。在比较亮的图片中,眼睛,鼻子,嘴巴,都相对清晰,脸的轮廓,头发之类的比较模糊。这说明,新特征空间里的特征向量们,大部分是"五官"和"亮度"相关的向量,所以新特征向量上的信息肯定大部分是由原数据中和"五官"和"亮度"相关的特征中提取出来的。
2.5 重要接口inverse_transform
- 在sklearn中,我们通过让原特征矩阵X右乘新特征空间矩阵V(k,n)的转置来生成新特征矩阵X_dr,那理论上来说,让新特征矩阵X_dr右乘V(k,n)转置的逆矩阵 ,就可以将新特征矩阵X_dr还原为X。那sklearn是通过接口inverse_transform 来实现的。
# 原始数据和pca降维后数据进行inverse_transform 的比对
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(150)
X_dr = pca.fit_transform(X)
X_inverse = pca.inverse_transform(X_dr)
fig, ax = plt.subplots(2,10,figsize=(10,2.5)
,subplot_kw={"xticks":[],"yticks":[]}
)
#现在我们的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")
2019090905.png
3. 用PCA做噪音过滤
- 降维的目的之一就是希望抛弃掉对模型带来负面影响的特征,而我们相信,带有效信息的特征的方差应该是远大于噪音的,所以相比噪音,有效的特征所带的信息应该不会在PCA过程中被大量抛弃。inverse_transform能够在不恢复原始数据的情况下,将降维后的数据返回到原本的高维空间,即是说能够实现”保证维度,但去掉方差很小特征所带的信息“。利用inverse_transform的这个性质,我们能够实现噪音过滤。
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
digits = load_digits()
def plot_digits(data):
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")
# 为数据加上噪音
np.random.RandomState(1)
noisy = np.random.normal(digits.data,2)
# 逆转降维结果,实现降噪
# 降维
pca = PCA(0.5).fit(noisy)
X_dr = pca.transform(noisy)
without_noise = pca.inverse_transform(X_dr)
print(plot_digits(digits.data))
print(plot_digits(noisy))
print(plot_digits(without_noise))
4. PCA对手写数字数据集的降维
- 手写数字数据集结构为(42000, 784),用KNN跑一次半小时,得到准确率在96.6%上下,用随机森林跑一次12秒,准确率在93.8%,虽然KNN效果好,但由于数据量太大,KNN计算太缓慢,所以我们不得不选用随机森林。我们使用了各种技术对手写数据集进行特征选择,最后使用嵌入法SelectFromModel选出了324个特征,将随机森林的效果也调到了96%以上。但是,因为数据量依然巨大,还是有300多个特征。今天,我们就来试着用PCA处理一下这个数据,看看效果如何。
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
data = pd.read_csv(r"data\digit recognizor.csv")
X = data.iloc[:,1:]
y = data.iloc[:,0]
X.shape # (42000, 784)
# 画累计方差贡献率曲线,找最佳降维后维度的范围
pca_line = PCA().fit(X)
plt.figure(figsize=[20,5])
plt.plot(np.cumsum(pca_line.explained_variance_ratio_))
plt.xlabel("number of components after dimension reduction")
plt.ylabel("cumulative explained variance ratio")
plt.show()
运行结果:
2019090907.png
# 细化学习曲线,找出降维后的最佳维度
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,n_jobs=-1).mean()
score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(10,25),score)
plt.show()
运行结果:
2019090908.png
# 找出的最佳维度进行降维,查看RFC模型效果
X_dr = PCA(23).fit_transform(X)
cross_val_score(RFC(n_estimators=100,random_state=0),X_dr,y,cv=5,n_jobs=-1).mean()
运行结果:
0.9460242340745572
# KNN的k值学习曲线
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,n_jobs=-1).mean()
score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,11),score)
plt.show()
2019090909.png
print(cross_val_score(KNN(5),X_dr,y,cv=5,n_jobs=-1).mean())
运行结果:
0.9698090936897883
- 可以发现,原本785列的特征被我们缩减到23列之后,用KNN跑出了目前位置这个数据集上最好的结果。再进行更细致的调整,我们也许可以将KNN的效果调整到98%以上。PCA为我们提供了无限的可能,终于不用再因为数据量太庞大而被迫选择更加复杂的模型了!
网友评论