Python机器学习降维算法

2016/11/17 Python scikitlearn

Python机器学习降维算法

收集数据时,我们总是不想漏掉任何一个可能会影响结果的变量,这将导致数据集变得异常庞大。而实际训练模型时,我们又总想剔除那些无效的或对结果影响甚微的变量,因为更多的变量意味着更大的计算量,这将导致模型训练慢得令人无法忍受。如何才能从众多数据中剔除无效的或对结果影响甚微的变量,以降低计算复杂度,提高模型训练效率呢?答案就是降维。机器学习领域中的降维是指采用某种映射方法,将原本高维度空间中的数据点映射到低维度的空间中。

主成分分析

主成分分析(Principal Component Analysis,PCA)是一种统计方法,也是最常用的降维方法。主成分分析通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。显然,主成分分析的降维并不是简单地丢掉一些特征,而是通过正交变换,把具有相关性的高维变量合并为线性无关的低维变量,从而达到降维的目的。

这里可能存在一个误区,即认为主成分分析就是降维。实际上,主成分分析是根据对样本做正交变换后得到的各成分的方差值(某成分的方差值越大,说明该成分越重要),来判断是否有可以舍弃的成分;而降维则是根据分析结果取得降维后的数据。

Scikit-learn的成分分析子模块decomposition提供了PCA类来实现主成分分析和降维。PCA类最重要的参数是n_components,如果没有指定该参数,则表示PCA类仅做主成分分析,不会自动完成降维操作;如果参数n_components是小于1的浮点数,则表示降维后主成分方差累计贡献率的下限值;如果参数n_components是大于1的整数,则表示降维后的特征维数。

和Scikit-learn的其他模型相比,PCA类还多了下面这两个重要的属性,它们用来表述主成分分析。

explained_variance_:正交变换后各成分的方差值,方差值越大,表示该成分越重要。 explained_variance_ratio_:正交变换后各成分的方差值占总方差值的比例,比例越大,表示该成分越重要。 以下代码以鸢尾花数据集为例演示了如何使用PCA类来实现主成分分析和降维。已知鸢尾花数据集有4个特征维,分别是花萼的长度、宽度和花瓣的长度、宽度。

>>> from sklearn import datasets as dss
>>> from sklearn.decomposition import PCA
>>> ds = dss.load_iris()
>>> ds.data.shape # 150个样本,4个特征维
(150, 4)
>>> m = PCA() # 使用默认参数实例化PCA类,n_components=None
>>> m.fit(ds.data)
PCA(copy=True, iterated_power='auto', n_components=None, random_state=None,
    svd_solver='auto', tol=0.0, whiten=False)
>>> m.explained_variance_ # 正交变换后各成分的方差值
array([4.22824171, 0.24267075, 0.0782095 , 0.02383509])
>>> m.explained_variance_ratio_ # 正交变换后各成分的方差值占总方差值的比例
array([0.92461872, 0.05306648, 0.01710261, 0.00521218])

对鸢尾花数据集的主成分分析结果显示:存在一个明显的成分,其方差值占总方差值的比例超过92%;存在一个方差值很小的成分,其方差值占总方差值的比例只有0.52%;前两个成分贡献的方差占比超过97.7%,数据集特征维完全可以从4个降至2个。

>>> m = PCA(n_components=0.97)
>>> m.fit(ds.data)
PCA(copy=True, iterated_power='auto', n_components=0.97, random_state=None,
    svd_solver='auto', tol=0.0, whiten=False)
>>> m.explained_variance_
array([4.22824171, 0.24267075])
>>> m.explained_variance_ratio_
array([0.92461872, 0.05306648])
>>> d = m.transform(ds.data)
>>> d.shape
(150, 2)

指定参数n_components不小于0.97,即可得到原数据集的降维结果,同样是150个样本,但特征维只有2维。现在终于可以直观地画出全部样本数据了。

>>> import matplotlib.pyplot as plt
>>> plt.scatter(d[:,0], d[:,1], c=ds.target)
<matplotlib.collections.PathCollection object at 0x0000016FBF243CC8>
>>> plt.show()

因子分析

因子分析(Factor Analysis,FA)是研究从变量群中提取共性因子的统计技术,主要是用来描述隐藏在一组测量到的变量中的一些更基本的、但又无法直接测量到的隐性变量。例如,老师注意到学生的各科成绩之间存在着一定的相关性,有的学生文科成绩好,理科成绩偏低,也有的学生各科成绩比较均衡。老师自然就会推想是否存在某些潜在的共性因子影响着学生的学习成绩。

因子分析又分为探索性因子分析和验证性因子分析两个方向。探索性因子分析是不确定多个自变量中有几个因子,通过各种方法试图找到这些因子。验证性因子分析是已经假设自变量中有几个因子,试图通过这种方法来验证假设是否正确。

因子分析本质上是主成分分析的扩展。相对于主成分分析,因子分析更倾向于描述原始变量之间的相关关系,也就是研究如何以最少的信息丢失,将众多原始变量浓缩成少数几个因子变量,以及如何使因子变量具有较强的可解释性的一种多元统计分析方法。

Scikit-learn的成分分析子模块decomposition提供了FactorAnalysis类来实现因子分析,该类使用基于SVD的方法对加载矩阵进行最大似然估计,将潜在变量转换为观察到的变量。

此处以Scikit-learn的内置手写数字数据集为例。该数据集共有1797个样本,每个样本是一张64像素的手写体数字图片,也就是样本集有64个特征维。如果对这个高维样本集直接进行随机森林分类,虽然耗时略长,但结果是没有问题的,代码如下。

>>> from sklearn.datasets import load_digits
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.model_selection import cross_val_score
>>> X, y = load_digits(return_X_y=True)
>>> X.shape # 64个特征维
(1797, 64)
>>> y.shape
(1797,)
>>> rfc = RandomForestClassifier()
>>> scroe = cross_val_score(rfc, X, y, cv=10) # 交叉验证10
>>> scroe.mean()
0.9471135940409683

使用随机森林分类模型的交叉验证精度达到0.94,这是不错的结果。我们可以对这个样本集做探索性因子分析,尝试转换为特征维较低的数据集,再去做分类测试。如果交叉验证精度没有明显降低,说明用因子分析的方式降维是可行的,代码如下。

>>> from sklearn.decomposition import FactorAnalysis
>>> fa = FactorAnalysis(n_components=16, random_state=0) # 降至16个特征维
>>> X_fa = fa.fit_transform(X)
>>> scroe = cross_val_score(rfc, X_fa, y, cv=10)
>>> scroe.mean()
0.9304593420235877
>>> fa = FactorAnalysis(n_components=8, random_state=0) # 降至8个特征维
>>> X_fa = fa.fit_transform(X)
>>> scroe = cross_val_score(rfc, X_fa, y, cv=10)
>>> scroe.mean()
0.8881967721911856

结果显示,从64个特征维降至16个特征维,交叉验证精度降低小于0.02。即使降至8个特征维,交叉验证精度仍然大于0.88。

截断奇异值分解

截断奇异值分解(Truncated Singular Value Decomposition,TSVD)是一种矩阵因式分解(factorization)技术,非常类似于PCA,只不过SVD分解在数据矩阵上进行,而PCA在数据的协方差矩阵上进行。通常,SVD用于发现矩阵的主成分。TSVD与一般SVD不同的是,它可以产生一个指定维度的分解矩阵,从而达到降维的目的。

以Scikit-learn的内置手写数字数据集为例,尝试用截断奇异值分解的方法将样本集降至较低的特征维,再对其进行随机森林分类,并和64个特征维的随机森林分类对比交叉验证精度。

>>> from sklearn.datasets import load_digits
>>> from sklearn.decomposition import TruncatedSVD
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.model_selection import cross_val_score
>>> X, y = load_digits(return_X_y=True)
>>> X.shape # 64个特征维
(1797, 64)
>>> y.shape
(1797,)
>>> rfc = RandomForestClassifier()
>>> scroe = cross_val_score(rfc, X, y, cv=10) # 交叉验证10
>>> scroe.mean()
0.9471135940409683
>>> tsvd = TruncatedSVD(n_components=16) # 降至16个特征维
>>> X_tsvd = tsvd.fit_transform(X)
>>> scroe = cross_val_score(rfc, X_tsvd, y, cv=10)
>>> scroe.mean()
0.9393389199255122
>>> tsvd = TruncatedSVD(n_components=8) # 降至8个特征维
>>> X_tsvd = tsvd.fit_transform(X)
>>> scroe = cross_val_score(rfc, X_tsvd, y, cv=10)
>>> scroe.mean()
0.9182153941651148

结果显示,从64个特征维降至16个特征维,交叉验证精度降低小于0.01。即使降至8个特征维,交叉验证精度仍然大于0.91。在本例中,截断奇异值分解的降维效果优于因子分析降维。

独立成分分析(ICA)

房间里有两位演讲者在讲话,他们发出的声音分别是s1和s2,有两台录音设备记录了他们混合在一起的声音,得到的记录是x1和x2。

如果能够从录音数据x1和x2中分离出两位演讲者各自独立的讲话声音s1和s2,那么这将是一件美妙的事情。这就是独立成分分析(Independent Component Analysis,ICA)。不过,这并不容易实现,因为很多时候我们只有录音,并不知道房间里有几个人在讲话,因此独立成分分析又被称为盲源分离问题。

Scikit-learn的成分分析子模块decomposition提供了FastICA类来实现独立成分分析。独立成分分析通常不用于降低维度,而用于分离叠加信号。由于ICA模型不包括噪声项,因此要使模型正确,必须使用白化(whitening)。FastICA类的whiten参数可以设置是否使用白化。下面用一个信号分离的例子,演示如何使用FastICA类实现独立成分分析。

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> plt.rcParams['font.sans-serif'] = ['FangSong']
>>> plt.rcParams['axes.unicode_minus'] = False
>>> _x = np.linspace(0, 8*np.pi, 1000)
>>> k1 = np.where(np.int_(0.5*_x/np.pi)%2==0, 1, -1)/np.pi
>>> k2 = np.where(np.int_(_x/np.pi)%2==0, 1, 0)
>>> k3 = np.where(np.int_(_x/np.pi)%2==0, 0, 1)
>>> s1 = np.sin(_x) # 1位演讲者的声音
>>> s2 = _x%(np.pi)*k1*k2 + (np.pi-_x%(np.pi))*k1*k3 # 2位演讲者的声音
>>> x1 = 0.4*s1 + 0.5*s2 # 录音1
>>> x2 = 1.2*s1 - 0.3*s2 # 录音2
>>> plt.subplot(121)
<matplotlib.axes._subplots.AxesSubplot object at 0x000001B6D67ED288>
>>> plt.plot(_x, s1, label='s1')
[<matplotlib.lines.Line2D object at 0x000001B6D68236C8>]
>>> plt.plot(_x, s2, label='s2')
[<matplotlib.lines.Line2D object at 0x000001B6D6823188>]
>>> plt.legend()
<matplotlib.legend.Legend object at 0x000001B6D8A6DA48>
>>> plt.subplot(122)
<matplotlib.axes._subplots.AxesSubplot object at 0x000001B6D8A6FC88>
>>> plt.plot(_x, x1, label='x1')
[<matplotlib.lines.Line2D object at 0x000001B6D9506EC8>]
>>> plt.plot(_x, x2, label='x2')
[<matplotlib.lines.Line2D object at 0x000001B6D94D6C48>]
>>> plt.legend()
<matplotlib.legend.Legend object at 0x000001B6D9512A48>
>>> plt.show()

在一个真实的案例中,我们并不能预先知道s1和s2的存在,可用的数据只有合成信号x1和x2,我们要做的就是从合成信号x1和x2中分离出s1和s2这样的独立音源,代码如下。

>>> from sklearn.decomposition import FastICA
>>> X = np.stack((x1,x2), axis=1) # 将两个信号合并成矩阵
>>> X.shape
(1000, 2)
>>> fica = FastICA(n_components=2) # 快速独立成分分析类实例化
>>> fica.fit(X)
FastICA(algorithm='parallel', fun='logcosh', fun_args=None, max_iter=200,
        n_components=2, random_state=None, tol=0.0001, w_init=None, whiten=True)
>>> X_ica = fica.transform(X) # 独立成分分析结果
>>> X_ica.shape
(1000, 2)
>>> plt.plot(_x, X_ica[:,0], label='独立成分1')
[<matplotlib.lines.Line2D object at 0x000001B6DB9508C8>]
>>> plt.plot(_x, X_ica[:,1], label='独立成分2')
[<matplotlib.lines.Line2D object at 0x000001B6DBAB7F08>]
>>> plt.legend()
<matplotlib.legend.Legend object at 0x000001B6DB90CD48>
>>> plt.show()

Search

    微信好友

    博士的沙漏

    Table of Contents