机器学习实战_降维(二)

咱们将会展现两种主要的降维方法:投影(projection)和流形学习(Manifold Learning),同时咱们还会介绍三种流行的降维技术:主成分分析(PCA),核主成分分析(Kernel PCA)和局部线性嵌入(LLE)。算法

主成分分析(PCA)

主成分分析(Principal Component Analysis)是目前为止最流行的降维算法。首先它找到接近数据集分布的超平面,而后将全部的数据都投影到这个超平面上。数组

保留(最大)方差:dom

在将训练集投影到较低维超平面以前,您首先须要选择正确的超平面。例如图左侧是一个简单的二维数据集,以及三个不一样的轴(即一维超平面)。图右边是将数据集投影到每一个轴上的结果。正如你所看到的,投影到实线上保留了最大方差,而在点线上的投影只保留了很是小的方差,投影到虚线上保留的方差则处于上述二者之间。机器学习

clipboard.png

选择保持最大方差的轴看起来是合理的,由于它极可能比其余投影损失更少的信息。证实这种选择的另外一种方法是,选择这个轴使得将原始数据集投影到该轴上的均方距离最小。这是就 PCA 背后的思想,至关简单函数

主成分(Principle Componets):
PCA 寻找训练集中可得到最大方差的轴。在上图中,它是一条实线。它还发现了一个与第一个轴正交的第二个轴,选择它能够得到最大的残差。在这个 2D 例子中,没有选择:就只有这条点线。但若是在一个更高维的数据集中,PCA 也能够找到与前两个轴正交的第三个轴,以及与数据集中维数相同的第四个轴,第五个轴等。 定义第i个轴的单位矢量被称为第i个主成分(PC)。在图中,第一个 PC 是c1,第二个 PC 是c2。在投影图中,前两个 PC 用平面中的正交箭头表示,第三个 PC 与上述 PC 造成的平面正交(指向上或下)性能

概述: 主成分的方向不稳定:若是您稍微打乱一下训练集并再次运行 PCA,则某些新 PC 可能会指向与原始 PC 方向相反。可是,它们一般仍位于同一轴线上。在某些状况下,一对 PC 甚至可能会旋转或交换,但它们定义的平面一般保持不变。

那么如何找到训练集的主成分呢?幸运的是,有一种称为奇异值分解(SVD)的标准矩阵分解技术,能够将训练集矩阵X分解为三个矩阵$U·Σ·V^T$的点积,其中$V^T$$包含咱们想要的全部主成分,以下所示。学习

clipboard.png

下面的 Python 代码使用了 Numpy 提供的svd()函数得到训练集的全部主成分,而后提取前两个 PC:spa

X_centered=X-X.mean(axis=0)    # 中心化
U,s,V=np.linalg.svd(X_centered)
c1=V.T[:,0]
c2=V.T[:,1]
警告:PCA 假定数据集以原点为中心。正如咱们将看到的,Scikit-Learn 的PCA类负责为您的数据集中心化处理。可是,若是您本身实现 PCA(如前面的示例所示),或者若是您使用其余库,不要忘记首先要先对数据作中心化处理。

投影到d维空间:
一旦肯定了全部的主成分,你就能够经过将数据集投影到由前d个主成分构成的超平面上,从而将数据集的维数降至d维。选择这个超平面能够确保投影将保留尽量多的方差。3d

为了将训练集投影到超平面上,能够简单地经过计算训练集矩阵X和Wd的点积,Wd定义为包含前d个主成分的矩阵(即由V^T的前d列组成的矩阵)code

将训练集投影到d维空间的公式:
$$ X_{d-proj} = X \cdot W_d $$

下面的 Python 代码将训练集投影到由前两个主成分定义的超平面上:

W2=V.T[:,:2]    # 降为2维
X2D=X_centered.dot(W2)

使用 Scikit-Learn
Scikit-Learn 的 PCA 类使用 SVD 分解来实现,就像咱们以前作的那样。如下代码应用 PCA 将数据集的维度降至两维(请注意,它会自动处理数据的中心化):

from sklearn.decomposition import PCA

pca=PCA(n_components=2)
X2D=pca.fit_transform(X)

将 PCA 转化器应用于数据集后,可使用components_访问每个主成分(注意,它返回以 PC 做为水平向量的矩阵,所以,若是咱们想要得到第一个主成分则能够写成pca.components_.T[:,0])。

方差解释率(Explained Variance Ratio)
另外一个很是有用的信息是每一个主成分的方差解释率,可经过explained_variance_ratio_变量得到。它表示位于每一个主成分轴上的数据集方差的比例。例如,让咱们看一下图 8-2 中表示的三维数据集前两个份量的方差解释率:

>>> print(pca.explained_variance_ratio_)
array([0.84248607, 0.14631839])

这代表,84.2% 的数据集方差位于第一轴,14.6% 的方差位于第二轴。第三轴的这一比例不到1.2%,所以能够认为它可能没有包含什么信息

选择正确的维度
一般咱们倾向于选择加起来到方差解释率可以达到足够占比(例如 95%)的维度的数量,而不是任意选择要下降到的维度数量。固然,除非您正在为数据可视化而下降维度 -- 在这种状况下,您一般但愿将维度下降到 2 或 3。

下面的代码在不降维的状况下进行 PCA,而后计算出保留训练集方差 95% 所需的最小维数:

pca=PCA()
pac.fit(X)
cumsum=np.cumsum(pca.explained_variance_ratio_)
d=np.argmax(cumsum>=0.95)+1

你能够设置n_components = d并再次运行 PCA。可是,有一个更好的选择:不指定你想要保留的主成分个数,而是将n_components设置为 0.0 到 1.0 之间的浮点数,代表您但愿保留的方差比率:

pca=PCA(n_components=0.95)
X_reduced=pca.fit_transform(X)

另外一种选择是画出方差解释率关于维数的函数(简单地绘制cumsum)。曲线中一般会有一个肘部,方差解释率中止快速增加。您能够将其视为数据集的真正的维度。在这种状况下,您能够看到将维度下降到大约100个维度不会失去太多的可解释方差。

clipboard.png

PCA 压缩
显然,在降维以后,训练集占用的空间要少得多。例如,尝试将 PCA 应用于 MNIST 数据集,同时保留 95% 的方差。你应该发现每一个实例只有 150 多个特征,而不是原来的 784 个特征。所以,尽管大部分方差都保留下来,但数据集如今还不到其原始大小的 20%!这是一个合理的压缩比率,您能够看到这能够如何极大地加快分类算法(如 SVM 分类器)的速度。

经过应用 PCA 投影的逆变换,也能够将缩小的数据集解压缩回 784 维。固然这并不会返回给你最原始的数据,由于投影丢失了一些信息(在5%的方差内),但它可能很是接近原始数据。原始数据和重构数据之间的均方距离(压缩而后解压缩)被称为重构偏差(reconstruction error)。例如,下面的代码将 MNIST 数据集压缩到 154 维,然后使用inverse_transform()方法将其解压缩回 784 维。图 8-9 显示了原始训练集(左侧)的几位数字在压缩并解压缩后(右侧)的对应数字。您能够看到有轻微的图像质量下降,但数字仍然大部分无缺无损。

pca=PCA(n_components=154)
X_mnist_reduced=pca.fit_transform(X_mnist)
X_mnist_recovered=pca.inverse_transform(X_mnist_reduced)

clipboard.png

PCA逆变换公式,回退到原来的数据维度

$$ X_{recovered} = X_{d-proj} \cdot W_d^T $$

增量 PCA(Incremental PCA)
先前 PCA 实现的一个问题是它须要在内存中处理整个训练集以便 SVD 算法运行。幸运的是,咱们已经开发了增量 PCA(IPCA)算法:您能够将训练集分批,并一次只对一个批量使用 IPCA 算法。这对大型训练集很是有用,而且能够在线应用 PCA(即在新实例到达时即时运行)。

下面的代码将 MNIST 数据集分红 100 个小批量(使用 NumPy 的array_split()函数),并将它们提供给 Scikit-Learn 的IncrementalPCA类,以将 MNIST 数据集的维度下降到 154 维(就像之前同样)。请注意,您必须对每一个最小批次调用partial_fit()方法,而不是对整个训练集使用fit()方法

from sklearn.decomposition import IncrementalPCA

n_batches=100
inc_pca=IncrementalPCA(n_components=154)
for X_batch in np.array_spplit(X_mnist,n_batches): #分100批次数据
    inc_pca.partial_fit(X_batch)    # 必须批次调用partial_fit()方法
X_mnist_reduced=inc_pca.transform(X_mnist)

或者,您可使用 NumPy 的memmap类,它容许您操做存储在磁盘上二进制文件中的大型数组,就好像它彻底在内存中;该类仅在须要时加载内存中所需的数据。因为增量 PCA 类在任什么时候间内仅使用数组的一小部分,所以内存使用量仍受到控制。这能够调用一般的fit()方法,以下面的代码所示:

X_mm=np.memmap(filename,dtype='float32',mode='readonly',shape=(m,n))
batch_size=m//n_batches
inc_pca=IncrementalPCA(n_components=154,batch_size=batch_size)
inc_pca.fit(X_mm)

随机 PCA(Randomized PCA)
Scikit-Learn 提供了另外一种执行 PCA 的选择,称为随机 PCA。这是一种随机算法,能够快速找到前d个主成分的近似值。它的计算复杂度是O(m × d^2) + O(d^3),而不是O(m × n^2) + O(n^3),因此当d远小于n时,它比以前的算法快得多。

rnd_pca=PCA(n_components=154,svd_solver='randomized')
X_reduced=rnd_pca.fit_transform(X_mnist)

核 PCA(Kernel PCA)

例如,下面的代码使用 Scikit-Learn 的KernelPCA类来执行带有 RBF 核的 kPCA

from sklearn.decomposition import KernelPCA

rbf_pca=KernelPCA(n_components=2,kernel='rbf',gamma=0.04)
X_reduced=rbf_pca.fit_transform(X)

clipboard.png

选择一种核并调整超参数
因为 kPCA 是无监督学习算法,所以没有明显的性能指标能够帮助您选择最佳的核方法和超参数值。可是,降维一般是监督学习任务(例如分类)的准备步骤,所以您能够简单地使用网格搜索来选择可让该任务达到最佳表现的核方法和超参数。例如,下面的代码建立了一个两步的流水线,首先使用 kPCA 将维度降至两维,而后应用 Logistic 回归进行分类。而后它使用Grid SearchCV为 kPCA 找到最佳的核和gamma值,以便在最后得到最佳的分类准确性:

from sklearn.model_selection import GridSearchCV from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline
clf = Pipeline([
        ("kpca", KernelPCA(n_components=2)),
        ("log_reg", LogisticRegression())
])
param_grid = [{
        "kpca__gamma": np.linspace(0.03, 0.05, 10),
        "kpca__kernel": ["rbf", "sigmoid"]
    }]
grid_search = GridSearchCV(clf, param_grid, cv=3)
grid_search.fit(X, y)

你能够经过调用best_params_变量来查看使模型效果最好的核和超参数:

>>> print(grid_search.best_params_)
{'kpca__gamma': 0.043333333333333335, 'kpca__kernel': 'rbf'}

另外一种彻底为非监督的方法,是选择产生最低重建偏差的核和超参数。可是,重建并不像线性 PCA 那样容易。这里是缘由:图 8-11 显示了原始瑞士卷 3D 数据集(左上角),而且使用 RBF 核应用 kPCA 后生成的二维数据集(右上角)。因为核技巧,这在数学上等同于使用特征映射φ将训练集映射到无限维特征空间(右下),而后使用线性 PCA 将变换的训练集投影到 2D。请注意,若是咱们能够在缩减空间中对给定实例实现反向线性 PCA 步骤,则重构点将位于特征空间中,而不是位于原始空间中(例如,如图中由x表示的那样)。因为特征空间是无限维的,咱们不能找出重建点,所以咱们没法计算真实的重建偏差。幸运的是,能够在原始空间中找到一个贴近重建点的点。这被称为重建前图像(reconstruction pre-image)。一旦你有这个前图像,你就能够测量其与原始实例的平方距离。而后,您能够选择最小化重建前图像错误的核和超参数。

clipboard.png

您可能想知道如何进行这种重建。一种解决方案是训练一个监督回归模型,将预计实例做为训练集,并将原始实例做为训练目标。若是您设置了fit_inverse_transform = True,Scikit-Learn 将自动执行此操做,代码以下所示:

rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.0433,fit_inverse_transform=True)
X_reduced = rbf_pca.fit_transform(X)
X_preimage = rbf_pca.inverse_transform(X_reduced)
概述:默认条件下,fit_inverse_transform = False而且KernelPCA没有inverse_tranfrom()方法。这种方法仅仅当fit_inverse_transform = True的状况下才会建立。

你能够计算重建前图像偏差:

>>> from sklearn.metrics import mean_squared_error
>>> mean_squared_error(X, X_preimage) 32.786308795766132

如今你可使用交叉验证的方格搜索来寻找能够最小化重建前图像偏差的核方法和超参数。

LLE

局部线性嵌入(Locally Linear Embedding)是另外一种很是有效的非线性降维(NLDR)方法。这是一种流形学习技术,不依赖于像之前算法那样的投影。简而言之,LLE 首先测量每一个训练实例与其最近邻(c.n.)之间的线性关系,而后寻找能最好地保留这些局部关系的训练集的低维表示(稍后会详细介绍) 。这使得它特别擅长展开扭曲的流形,尤为是在没有太多噪音的状况下。

例如,如下代码使用 Scikit-Learn 的LocallyLinearEmbedding类来展开瑞士卷。获得的二维数据集如图所示。正如您所看到的,瑞士卷被彻底展开,实例之间的距离保存得很好。可是,距离不能在较大范围内保留的很好:展开的瑞士卷的左侧被挤压,而右侧的部分被拉长。尽管如此,LLE 在对流形建模方面作得很是好。

from sklearn.manifold import LocallyLinearEmbedding

lle=LocallyLinearEmbedding(n_components=2,n_neighbors=10)
X_reduced=lle.fit_transform(X)

clipboard.png

其余降维方法

多维缩放(MDS)在尝试保持实例之间距离的同时下降了维度

Isomap 经过将每一个实例链接到最近的邻居来建立图形,而后在尝试保持实例之间的测地距离时下降维度。

t-分布随机邻域嵌入(t-Distributed Stochastic Neighbor Embedding,t-SNE)能够用于下降维​​度,同时试图保持类似的实例临近并将不类似的实例分开。它主要用于可视化,尤为是用于可视化高维空间中的实例(例如,能够将MNIST图像降维到 2D 可视化)。

线性判别分析(Linear Discriminant Analysis,LDA)其实是一种分类算法,但在训练过程当中,它会学习类之间最有区别的轴,而后使用这些轴来定义用于投影数据的超平面。LDA 的好处是投影会尽量地保持各个类之间距离,因此在运行另外一种分类算法(如 SVM 分类器)以前,LDA 是很好的降维技术。

clipboard.png

参考资料:【1】《Sklearn与TensorFlow机器学习实用指南》

相关文章
相关标签/搜索