官方地址:https://github.com/apachecn/hands-on-ml-zh/blob/master/docs/8.%E9%99%8D%E7%BB%B4.md
一、简介
许多机器学习问题涉及训练实例的几千甚至上百万个特征,这不仅导致训练非常缓慢,也让我们更加难以找到好的解决方案。这个问题通常被称为维度的灾难。对现实世界的问题,我们一般可以大量减少特征的数量,将棘手的问题转化成容易解决的问题。例如,MNIST数字图像:两个相邻像素通常是高度相关的:如果将它们合并成一个像素,也不会丢失太多信息。
数据降维确实会丢失一些信息,所以,它虽然能够加速训练但是也会轻微降低系统性能。同时它也让流水线更为复杂,维护难度上升。所有你应该先尝试使用原始的数据来训练,如果训练速度太慢的话再考虑使用降维。除了加快训练,降维对于数据可视化也是非常有用的。将维度降到两个(或三个),就可以在图形上绘制出高维训练集,通过视觉来检测模式,常常可以获得一些十分重要的洞察,比如说聚类。本章将介绍两种主要的数据降维方法——投影(projection)和流形学习(Manifold Learning),同时我们还会介绍三种流行的降维技术:主成分分析(PCA),核主成分分析(Kernel PCA)和局部线性嵌入(LLE)。
维数灾难
点,线,方形,立方体和超正方体(0D 到 4D 超正方体)
如果你在一个正方形单元中随机取一个点(一个1×1
的正方形),那么随机选的点离所有边界大于 0.001(靠近中间位置)的概率为 0.4%(1 - 0.998^2
)(换句话说,一个随机产生的点不大可能严格落在某一个维度上。但是在一个 1,0000 维的单位超正方体(一个1×1×...×1
的立方体,有 10,000 个 1),这种可能性超过了 99.999999%。在高维超正方体中,大多数点都分布在边界处。
还有一个更麻烦的区别:如果你在一个平方单位中随机选取两个点,那么这两个点之间的距离平均约为 0.52。如果您在单位 3D 立方体中选取两个随机点,平均距离将大致为 0.66。但是,在一个 1,000,000 维超立方体中随机抽取两点呢?那么,平均距离,信不信由你,大概为 408.25(大致 )!这非常违反直觉:当它们都位于同一单元超立方体内时,两点是怎么距离这么远的?这一事实意味着高维数据集有很大风险分布的非常稀疏:大多数训练实例可能彼此远离。当然,这也意味着一个新实例可能远离任何训练实例,这使得预测的可靠性远低于我们处理较低维度数据的预测,因为它们将基于更大的推测(extrapolations)。简而言之,训练集的维度越高,过拟合的风险就越大。
理论上来说,维数爆炸的一个解决方案是增加训练集的大小从而达到拥有足够密度的训练集。不幸的是,在实践中,达到给定密度所需的训练实例的数量随着维度的数量呈指数增长。如果只有 100 个特征(比 MNIST 问题要少得多)并且假设它们均匀分布在所有维度上,那么如果想要各个临近的训练实例之间的距离在 0.1 以内,您需要比宇宙中的原子还要多的训练实例。
二、降维的主要方法
在我们深入研究具体的降维算法之前,我们来看看降低维度的两种主要方法:投影和流形学习。
投影
在大多数现实世界的问题里,训练实例在所有维度上并不是均匀分布的。高维空间的所有训练实例实际上(或近似于)受一个低得多的低维子空间所影响。在下左图中,你可以看到一个由圆圈表示的3D数据集。
左图中,所有训练实例的分布都贴近一个平面:这是高维(3D)空间的较低维(2D)子空间。现在,如果我们将每个训练实例垂直投影到这个子空间上(就像将短线连接到平面的点所表示的那样),我们就可以得到如图右所示的新2D数据集。将数据集的维度从 3D 降低到了 2D。请注意,坐标轴对应于新的特征z1
和z2
(平面上投影的坐标)。
流行学习
有时投影并不总是降维的最佳方法。在很多情况下,子空间可能会扭曲和转动,比如有名的瑞士卷玩具数据集。
简单地将数据集投射到一个平面上(例如,直接丢弃x3
)会将瑞士卷的不同层叠在一起,如图左侧所示。但是真正想要的是展开瑞士卷所获取到的类似图右侧的 2D 数据集。
瑞士卷一个是二维流形的例子。简而言之,二维流形是一种二维形状,它可以在更高维空间中弯曲或扭曲,一个d
维流形是类似于d
维超平面的n
维空间(其中d < n
)的一部分。在我们瑞士卷这个例子中,d = 2
,n = 3
:它有些像 2D 平面,但是它实际上是在第三维中卷曲。
通过对训练实例所在的流形进行建模从而达到降维目的;这叫做流形学习。它依赖于流形假设(manifold hypothesis),它认为大多数现实世界的高维数据集大都靠近一个更低维的流形。这种假设经常在实践中被证实。
让我们再回到 MNIST 数据集:所有手写数字图像都有一些相似之处。它们由连线组成,边界是白色的,大多是在图片中中间的,等等。如果你随机生成图像,只有一小部分看起来像手写数字。换句话说,数字图像的自由度远低于任何随便一个图像时的自由度。这些约束往往会将数据集压缩到较低维流形中。
注意:在训练模型前降低训练集的维数,训练速度会加快,但并不总是会得出更好的训练效果;这一切都取决于数据集。
三、主成分分析法
1、常规PCA
主成分分析(Principal Component Analysis)是目前为止最流行的降维算法。首先它找到接近数据集分布的超平面,然后将所有的数据都投影到这个超平面上。
保留(最大)方差
在将训练集投影到较低维超平面之前,首先需要选择正确的超平面。例如图左侧是一个简单的二维数据集,以及三个不同的轴(即一维超平面)。图右边是将数据集投影到每个轴上的结果。正如你所看到的,投影到实线上保留了最大方差,而在点线上的投影只保留了非常小的方差,投影到虚线上保留的方差则处于上述两者之间。
选择保持最大方差的轴,比其他投影损失更少的信息。选择这个轴使得将原始数据集投影到该轴上的均方距离最小。这是就 PCA 背后的思想。
主成分(Principle Componets)
PCA 寻找训练集中可获得最大方差的轴。 定义第i
个轴的单位矢量被称为第i
个主成分(PC)。在上图中,第一个 PC 是c1
,第二个 PC 是c2
。
主成分的方向不稳定:如果稍微打乱一下训练集并再次运行 PCA,则某些新 PC 可能会指向与原始 PC 方向相反。但是,它们通常仍位于同一轴线上。在某些情况下,一对 PC 甚至可能会旋转或交换,但它们定义的平面通常保持不变。
训练集的主成分通过奇异值分解(SVD)的标准矩阵分解技术,可以将训练集矩阵X
分解为三个矩阵U·Σ·
的点积,其中
包含我们想要的所有主成分:
Numpy 提供的svd()
函数获得训练集的所有主成分,然后提取前两个 PC:
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
维。为了将训练集投影到超平面上,可以通过计算训练集矩阵X
和
的点积,
定义为包含前d
个主成分的矩阵(即由
的前d
列组成的矩阵):
#将训练集投影到由前两个主成分定义的超平面上:
W2=V.T[:,:2]
X2D=X_centered.dot(W2)
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_
变量获得。它表示位于每个主成分轴上的数据集方差的比例:
>>> print(pca.explained_variance_ratio_)
array([0.84248607, 0.14631839])
这表明,84.2% 的数据集方差位于第一轴,14.6% 的方差位于第二轴。第三轴的这一比例不到1.2%,可以认为它可能没有包含什么信息。
选择正确的维度
1、在不降维的情况下进行 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。
2、将n_components设置为 0.0 到 1.0 之间的浮点数,表明您希望保留的方差比率:
pca=PCA(n_components=0.95)
X_reduced=pca.fit_transform(X)
3、画出方差解释率关于维数的函数(简单地绘制cumsum)。曲线中通常会有一个拐点,方差解释率停止快速增长。将其视为数据集的本征维度。在这种情况下,将维度降低到大约100个维度不会失去太多的可解释方差。
PCA 压缩
在降维之后,训练集占用的空间要少得多。例如,将 PCA 应用于 MNIST 数据集,同时保留 95% 的方差。你应该发现每个实例只有 150 多个特征,而不是原来的 784 个特征。因此,尽管大部分方差都保留下来,但数据集现在还不到其原始大小的 20%!这是一个合理的压缩比率,极大地加快分类算法(如 SVM 分类器)的速度。
通过应用 PCA 投影的逆变换,也可以将缩小的数据集解压缩回 784 维。这并不会返回给你最原始的数据,因为投影丢失了一些信息(在5%的方差内)。PCA逆变换公式:
原始数据和重构数据之间的均方距离(压缩然后解压缩)被称为重构误差(reconstruction error)。例如将 MNIST 数据集压缩到 154 维,然后使用inverse_transform()
方法将其解压缩回 784 维。结果有轻微的图像质量降低,但数字仍然大部分完好无损。
pca=PCA(n_components=154)
X_mnist_reduced=pca.fit_transform(X_mnist)
X_mnist_recovered=pca.inverse_transform(X_mnist_reduced)
2、增量 PCA(Incremental PCA)
常规 PCA 实现的一个问题是它需要在内存中处理整个训练集以便 SVD 算法运行。增量 PCA(IPCA)算法可以将训练集分批,并一次只对一个批量使用 IPCA 算法。这对大型训练集非常有用,并且可以在线应用 PCA(即在新实例到达时即时运行)。
将 MNIST 数据集分成 100 个小批量(使用 NumPy 的array_split()
函数),使用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):
inc_pca.partial_fit(X_batch)
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)
3、随机 PCA(Randomized PCA)
Scikit-Learn 提供一种随机 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,使复杂的非线性投影降维成为可能。这就是所谓的核主成分分析(kPCA)。使用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)
使用不同核函数降到二维的瑞士卷,包括线性核函数(相当于直接使用PCA类)、RBF核函数,以及sigmoid核函数(Logistic)
选择核函数和调整超参数
1、可以使用网格搜索,来找到使任务性能最佳的核和超参数。
2、还有一种完全不受监督方法,就是选择重建误差最低的核和超参数。这个重建可以通过设置fit_inverse_transform=True,KernelPCA()会自动执行该操作:
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)
五、局部线性嵌入LLE
局部线性嵌入 是另一种非常强大的非线性降维(NLDR)技术。不像之前的算法依赖于投影,它是一种流形学习技术。简单来说,LLE首先测量每个算法如何与其最近的邻居线性相关,然后为训练集寻找一个能最大程度保留这些局部关系的低维表示。
LLE的工作原理
首先,对于每个训练实例 ,该算法识别其最近的
k
个邻居(在前面的代码中k = 10
中),然后尝试将 重构为这些邻居的线性函数。更具体地,找到权重
从而使
和
之间的平方距离尽可能的小,假设如果
不是
的
k
个最近邻时 。因此,LLE 的第一步是个约束优化问题,
LLE 第一步:对局部关系进行线性建模
其中W
是包含所有权重 的权重矩阵。第二个约束简单地对每个训练实例
的权重进行归一化。
在这步之后,权重矩阵 (包含权重
对训练实例的线形关系进行编码。现在第二步是将训练实例投影到一个
d
维空间(d < n
)中去,同时尽可能的保留这些局部关系。如果 是
在这个
d
维空间的图像,那么我们想要 和
之间的平方距离尽可能的小。它看起来与第一步非常相似,但我们要做的不是保持实例固定并找到最佳权重,而是恰相反:保持权重不变,并在低维空间中找到实例图像的最佳位置。请注意,
Z
是包含所有 的矩阵。
LLE 第二步:保持关系的同时进行降维
Scikit-Learn 的 LLE 实现具有如下的计算复杂度:查找k
个最近邻为O(m log(m) n log(k))
,优化权重为O(m n k^3)
,建立低维表示为O(d m^2)
。不幸的是,最后一项m^2
使得这个算法在处理大数据集的时候表现较差。
其他降维方法
还有很多其他的降维方法,Scikit-Learn 支持其中的好几种。这里是其中最流行的:
- 多维缩放(MDS)在尝试保持实例之间距离的同时降低了维度(参见图 8-13)
- Isomap 通过将每个实例连接到最近的邻居来创建图形,然后在尝试保持实例之间的测地距离时降低维度。
- t-分布随机邻域嵌入(t-Distributed Stochastic Neighbor Embedding,t-SNE)可以用于降低维度,同时试图保持相似的实例临近并将不相似的实例分开。它主要用于可视化,尤其是用于可视化高维空间中的实例(例如,可以将MNIST图像降维到 2D 可视化)。
- 线性判别分析(Linear Discriminant Analysis,LDA)实际上是一种分类算法,但在训练过程中,它会学习类之间最有区别的轴,然后使用这些轴来定义用于投影数据的超平面。LDA 的好处是投影会尽可能地保持各个类之间距离,所以在运行另一种分类算法(如 SVM 分类器)之前,LDA 是很好的降维技术。