· Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition, by Aurélien Géron (O’Reilly). Copyright 2019 Aurélien Géron, 978-1-492-03264-9.
· 《机器学习》周志华
· 环境:Anaconda(Python 3.8) + Pycharm
· 学习时间:2022.05.07~2022.05.08
第八章 降维
许多机器学习问题涉及每个训练实例的成千上万甚至数百万个特征。正如我们将看到的那样,所有这些特征不仅使训练变得极其缓慢,而且还会使找到好的解决方案变得更加困难。这个问题通常称为维度的诅咒。
幸运的是,在实际问题中,通常可以大大减少特征的数量,从而将棘手的问题转变为易于解决的问题。例如,考虑MNIST图像(在第3章中介绍):图像边界上的像素几乎都是白色,因此你可以从训练集中完全删除这些像素而不会丢失太多信息。图7-6确认了这些像素对于分类任务而言完全不重要。另外,两个相邻的像素通常是高度相关的,如果将它们合并为一个像素(例如,通过取两个像素的平均值),不会丢失太多信息。
数据降维确实会丢失一些信息(就好比将图像压缩为JPEG会降低其质量一样),所以,它虽然能够加速训练,但是也会轻微降低系统性能。同时它也让流水线更为复杂,维护难度上升。因此,如果训练太慢,你首先应该尝试的还是继续使用原始数据,然后再考虑数据降维。不过在某些情况下,降低训练数据的维度可能会滤除掉一些不必要的噪声和细节,从而导致性能更好(但通常来说不会,它只会加速训练)。
除了加快训练,降维对于数据可视化(或称DataViz)也非常有用。将维度降到两个(或三个),就可以在图形上绘制出高维训练集,通过视觉来检测模式,常常可以获得一些十分重要的洞察,比如聚类。此外,DataViz对于把你的结论传达给非数据科学家至关重要,尤其是将使用你的结果的决策者。
本章将探讨维度的诅咒,简要介绍高维空间中发生的事情。然后,我们将介绍两种主要的数据降维方法(投影和流形学习),并学习现在最流行的三种数据降维技术:PCA、Kernal PCA以及LLE。
文章目录
8.1 维度的诅咒
我们太习惯三维空间的生活,所以当我们试图去想象一个高维空间时,直觉思维很难成功。即使是一个基本的四维超立方体,我们也很难在脑海中想象出来,更不用说在一个千维空间中弯曲的二百维椭圆体。
事实证明,在高维空间中,许多事物的行为都迥然不同。例如,如果你在一个单位平面(1×1的正方形)内随机选择一个点,那么这个点离边界的距离小于0.001的概率只有约0.4%(也就是说,一个随机的点不大可能刚好位于某个维度的“极端”)。但是,在一个10 000维的单位超立方体(1×1…×1立方体,一万个1)中,这个概率大于99.99999%。高维超立方体中大多数点都非常接近边界。
还有一个更麻烦的区别:如果你在单位平面中随机挑两个点,这两个点之间的平均距离大约为0.52。如果在三维的单位立方体中随机挑两个点,两点之间的平均距离大约为0.66。但是,如果在一个100万维的超立方体中随机挑两个点呢?不管你相信与否,平均距离大约为408.25(约等于 1000000 / 6 \sqrt{1000000/6} 1000000/6)!这是非常违背直觉的:位于同一个单位超立方体中的两个点,怎么可能距离如此之远?这个事实说明高维数据集有很大可能是非常稀疏的:大多数训练实例可能彼此之间相距很远。当然,这也意味着新的实例很可能远离任何一个训练实例,导致跟低维度相比,预测更加不可靠,因为它们基于更大的推测。简而言之,训练集的维度越高,过拟合的风险就越大。
理论上来说,通过增大训练集,使训练实例达到足够的密度,是可以解开维度的诅咒的。然而不幸的是,实践中,要达到给定密度,所需要的训练实例数量随着维度的增加呈指数式上升。仅仅100个特征下(远小于MNIST问题),要让所有训练实例(假设在所有维度上平均分布)之间的平均距离小于0.1,你需要的训练实例数量就比可观察宇宙中的原子数量还要多。
8.2 降维的主要方法
在深入研究特定的降维算法之前,让我们看一下减少维度的两种主要方法:投影和流形学习。
8.2.1 投影
在大多数实际问题中,训练实例并不是均匀地分布在所有维度上。许多特征几乎是恒定不变的,而其他特征则是高度相关的(如之前针对MNIST所述)。结果,所有训练实例都位于(或接近于)高维空间的低维子空间内。这听起来很抽象,所以让我们看一个示例。在下图中,你可以看到由圆圈表示的3D数据集。
请注意,所有训练实例都位于一个平面附近:这是高维(3D)空间的低维(2D)子空间。如果我们将每个训练实例垂直投影到该子空间上(如实例连接到平面的短线所示),我们将获得如下图所示的新2D数据集——我们刚刚将数据集的维度从3D减少到2D。注意,轴对应于新特征z1和z2(平面上投影的坐标)。
但是,投影并不总是降低尺寸的最佳方法。在许多情况下,子空间可能会发生扭曲和转动,例如在下图中所示的著名的瑞士卷小数据集中。
如下图左侧所示,简单地投影到一个平面上(例如,去掉x3维度)会将瑞士卷的不同层挤压在一起。你真正想要的是展开瑞士卷,得到下图右侧的2D数据集。
8.2.2 流形学习
瑞士卷是2D流形的一个示例。简而言之,2D流形是可以在更高维度的空间中弯曲和扭曲的2D形状。更一般而言,d维流形是n维空间(其中d<n)的一部分,局部类似于d维超平面。在瑞士卷的情况下,d=2且n=3时,它局部类似于2D平面,但在第三维中弯曲。
许多降维算法通过对训练实例所在的流形进行建模来工作。这称为流形学习。它依赖于流形假设(也称为流形假说),该假设认为大多数现实世界的高维数据集都接近于低维流形。通常这是根据经验观察到的这种假设。
再次考虑一下MNIST数据集:所有手写数字图像都有一些相似之处。它们由连接的线组成,边界为白色,并且或多或少居中。如果你随机生成图像,那么其中只有一小部分看起来像手写数字。换句话说,如果你试图创建数字图像,可用的自由度大大低于允许你生成任何图像的自由度。这些约束倾向于将数据集压缩为低维流形。
流形假设通常还伴随着另一个隐式假设:如果用流形的低维空间表示,手头的任务(例如分类或回归)将更加简单。例如,在下图的上面一行中,瑞士卷分为两类:在3D空间(左侧)中,决策边界会相当复杂,而在2D展开流形空间中(右侧),决策边界是一条直线。
但是,这种隐含假设并不总是成立。例如,在上图的下面一行中,决策边界位于x1=5处。此决策边界在原始3D空间(垂直平面)中看起来非常简单,但在展开流形中看起来更加复杂(四个独立线段的集合)。
简而言之,在训练模型之前降低训练集的维度肯定可以加快训练速度,但这并不总是会导致更好或更简单的解决方案,它取决于数据集。
希望现在你对于维度的诅咒有了一个很好的理解,也知道降维算法是怎么解决它的,特别是当流形假设成立的时候应该怎么处理。本章剩余部分将逐一介绍几个最流行的算法。
8.3 主成分分析 PCA
主成分分析(PCA)是迄今为止最流行的降维算法。首先,它识别最靠近数据的超平面,然后将数据投影到其上。
8.3.1 保留差异性
将训练集投影到低维超平面之前需要选择正确的超平面。例如下图的左图代表一个简单的2D数据集,沿三条不同的轴(即一维超平面)。右图是将数据集映射到每条轴上的结果。正如你所见,在实线上的投影保留了最大的差异性,而点线上的投影只保留了非常小的差异性,虚线上的投影的差异性居中。
选择保留最大差异性的轴看起来比较合理,因为它可能比其他两种投影丢失的信息更少。要证明这一选择,还有一种方法,即比较原始数据集与其轴上的投影之间的均方距离,使这个均方距离最小的轴是最合理的选择,也就是实线代表的轴。这也正是PCA背后的简单思想。
8.3.2 主要成分
主成分分析可以在训练集中识别出哪条轴对差异性的贡献度最高。在上图中是由实线表示的轴。同时它也找出了第二条轴,与第一条轴垂直,它对剩余差异性的贡献度最高。因为这个示例是二维的,所以除了这条点线再没有其他。如果是在更高维数据集中,PCA还会找到与前两条都正交的第三条轴,以及第四条、第五条,等等——轴的数量与数据集维度数量相同。
第i个轴称为数据的第i个主要成分(PC)。在上图中,第一个PC是向量c1所在的轴,第二个PC是向量c2所在的轴。
对于每个主要成分,PCA都找到一个指向PC方向的零中心单位向量。由于两个相对的单位向量位于同一轴上,因此PCA返回的单位向量的方向不稳定:如果稍微扰动训练集并再次运行PCA,则单位向量可能会指向原始向量的相反方向。但是,它们通常仍位于相同的轴上。在某些情况下,一对单位向量甚至可以旋转或交换(如果沿这两个轴的方差接近),但是它们定义的平面通常保持不变。
那么如何找到训练集的主要成分呢?幸运的是,有一种称为奇异值分解(SVD)的标准矩阵分解技术,该技术可以将训练集矩阵 X X X分解为三个矩阵 U ⋅ Σ V T U·ΣV^T U⋅ΣVT的矩阵乘法,其中 V V V包含定义所有主要成分的单位向量。如公式所示。
以下Python代码使用NumPy的svd()
函数来获取训练集的所有主要成分,然后提取定义前两个PC的两个单位向量:
import numpy as np
# 构建数据集
np.random.seed(4)
m = 60
w1, w2 = 0.1, 0.3
noise = 0.1
angles = np.random.rand(m) * 3 * np.pi / 2 - 0.5
X = np.empty((m, 3))
X[:, 0] = np.cos(angles) + np.sin(angles)/2 + noise * np.random.randn(m) / 2
X[:, 1] = np.sin(angles) * 0.7 + noise * np.random.randn(m) / 2
X[:, 2] = X[:, 0] * w1 + X[:, 1] * w2 + noise * np.random.randn(m)
# 调用奇异值分解SVD
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]
PCA假定数据集以原点为中心。正如我们将看到的,Scikit-Learn的PCA类负责为你居中数据。如果你自己实现PCA(如上例所示),或者使用其他库,请不要忘记首先将数据居中(标准化)。
8.3.3 向下投影到d维度
一旦确定了所有主要成分,你就可以将数据集投影到前d个主要成分定义的超平面上,从而将数据集的维度降低到d维。选择这个超平面可确保投影将保留尽可能多的差异性。例如,在上图(8.2.1)中,将3D数据集投影到由前两个主成分定义的2D平面上,从而保留了数据集大部分的差异性。最终,2D投影看起来非常类似于原始3D数据集。
要将训练集投影到超平面上并得到维度为 d d d的简化数据集 X d − p r o j X_{d-proj} Xd−proj,计算训练集矩阵 X X X与矩阵 W d W_d Wd的矩阵相乘,矩阵 W d W_d Wd定义为包含 V V V的前 d d d列的矩阵,如公式所示。
X d − p r o j = X W d X_{d-proj} = XW_d Xd−proj