许多机器学习有数以千计甚至百万级别的特征,这不仅会让训练变得很慢,也可能让找到最优解变得很难,这种问题被称为维度灾难(curse of dimensionality)
真实世界中,有时候我们并不需要这么多维度,例如,MINIST的边缘位置常常是空白的,有的两个相邻的位置常常是相关的,扔掉其中的一些也不会损失太多的信息。
降维常常都是以损失信息为代价,类似于压缩一张图片。我们通常可以先试着用原始的数据,如果太慢再考虑降维,偶尔,降维可以去掉一些噪声和无关的细节来让结果变得更好,但这通常不会发生
另外。降维常常可以在方便可视化的角度变得更好,降低到二维或者三维可以方便观察,比如提取聚类的一些信息。
维度灾难
我们总是习惯于观察三维以下的物体,但是物体在高维空间的表现常常不同。
比如,在一个1×1正方形单元中随机取一个点,离所有边界大于 0.001(靠近中间位置)的概率为 0.4%。但是在一个 1,0000 维的单位超正方体,这种可能性超过了 99.999999%。在高维超正方体中,大多数点都分布在边界处。
还有一个更麻烦的区别:如果你在一个平方单位中随机选取两个点,那么这两个点之间的距离平均约为 0.52。如果您在单位 3D 立方体中选取两个随机点,平均距离将大致为 0.66。但是,在一个 1,000,000 维超立方体中随机抽取两点呢?那么,平均距离,信不信由你,大概为 408.25!
那么意味着,我们的一个新样本,距离机器学习的其他样本距离可能非常远,所以预测的可靠性远远不如低维度,过拟合的风险也更高。
理论上,我们可以通过增加样本来填满高维的空间,但是,所需要的样本是指数增长的。如果只有 100 个特征(比 MNIST 问题要少得多)并且假设它们均匀分布在所有维度上,那么如果想要各个临近的训练实例之间的距离在 0.1 以内,需要比宇宙中的原子还要多的训练实例。
降维的主要方法
投影(Projection)
事实上,样本常常不是均匀分布的,许多特征可能是常数,许多特征可能是高度相关的,结果是,可能他们接近于一个低维子空间,比如,图中的点接近于某个二维平面,我们可以投影到这个平面上,如下图,我们刚刚将数据集的维度从 3D 降低到了 2D。
但是,也有一些问题是投影无法解决的,比如,下图是一个类似瑞士蛋糕卷的形状
我们真正希望的是,如下图右,把蛋糕卷展开的形状,而投影,比如沿着x3方向投射,可能会造成重叠在一起的不好结果,如左
流形学习
瑞士卷一个是二维流形的例子。它类似于一个二维平面,不过在三维空间下是卷曲的。
许多降维算法对样本所在的流形进行建模从而达到降维目的;这叫做流形学习。它依赖于流形假设(manifold hypothesis),大多数现实世界的高维数据集大都靠近一个更低维的流形。这种假设经常在实践中被证实。
比如MNIST数据集,如果你只是随机的生成,只有非常少的一部分是满足数字的,换句话说,MNIST里面图片的自由度比完全随机低的多,这些约束把样本压缩到某个流形里面。
流形学习常常有另外一个假设,就是决策在流形中比原来的空间简单了(比如下图上),但这也不是绝对的,图中的例子,决策边界反而变得复杂了(比如下图下)
下面会介绍一些具体的降维方法。
PCA
主成分分析(Principal Component Analysis,PCA)是目前为止最流行的降维算法。首先它找到接近数据集分布的超平面,然后将所有的数据都投影到这个超平面上。
首先的任务就是找到这个超平面,我们希望损失尽可能地小
一种思路是选择方差最大的,因为数据的方差大,噪音的方差小。正如你所看到的,投影到实线上保留了最大方差,证明这种选择的另一种方法是,选择这个轴使得将原始数据集投影到该轴上的均方距离最小。
主成分
PCA 寻找训练集中可获得最大方差的轴。在上图中只是一条直线,但是在一个高维的空间,
将会是相互正交的许多个轴,定义第i个轴的单位矢量被称为第i个主成分(PC)。为c1,c2....cn
那么如何找到训练集的主成分呢?幸运的是,有一种称为奇异值分解(SVD)的标准矩阵分解技术,可以将训练集矩阵X
分解为三个矩阵U·Σ·V^T
的点积,其中V^T
包含我们想要的所有主成分
一旦确定了所有的主成分,你就可以通过将数据集投影到由前d个主成分构成的超平面上,从而将数据集的维数降至d维。
投影到超平面上,可以简单地通过计算训练集矩阵X和wd的点积,wd定义为包含前d
个主成分的矩阵(即由V^T
的前d
列组成的矩阵)
下面是Numpy的实现
X_centered=X-X.mean(axis=0)
U,s,V=np.linalg.svd(X_centered)
c1=V.T[:,0]
c2=V.T[:,1]
W2=V.T[:,:2]
X2D=X_centered.dot(W2)
PCA 假定数据集以原点为中心。Scikit-Learn 的
PCA
类负责为您的数据集中心化处理。但是,如果您自己实现 PCA(如前面的示例所示),或者如果您使用其他库,不要忘记首先要先对数据做中心化处理。
Sklearn库可以自动实现这个过程
可以使用components_
访问每一个主成分,第一个主成分则可以写成pca.components_.T[:,0]
from sklearn.decomposition import PCA
pca=PCA(n_components=2)
X2D=pca.fit_transform(X)
另一个非常有用的信息是每个主成分的方差解释率,explained_variance_ratio_
变量获得。它表示位于每个主成分轴上的数据集方差的比例。
>>> print(pca.explained_variance_ratio_)
array([0.84248607, 0.14631839])
这表明第一个轴占84%,第二个占16%,第三个仅有不到1.2%,第三个包含的信息已经不多了。通常我们倾向于选择加起来到方差解释率能够达到足够占比(例如 95%)的维度的数量。
在sklearn中,可以巧妙地把n_components设置为想要保留的比例(0-1之间),这样可以很容易到达这一点,另外,也可以画图寻找一个合适的拐点
pca=PCA(n_components=0.95)
X_reduced=pca.fit_transform(X)
例如用PCA在保留95%方差下压缩原来784维特征,只有 150 多个特征!大大的缩小了数据集的规模。
可以试着去还原数据集,结果发现,虽然有细微差别,但是损失的信息非常微小
pca=PCA(n_components=154)
X_mnist_reduced=pca.fit_transform(X_mnist)
X_mnist_recovered=pca.inverse_transform(X_mnist_reduced)
增量PCA(Incremental PCA)
普通的PCA在做奇异值分解要把所有的训练集加载到内存中。而增量PCA(IPCA)可以将训练集分批,并一次只对一个批量使用 IPCA 算法。这对大型训练集非常有用,并且可以在线应用 PCA
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) #这里是partial_fit而不是fit
X_mnist_reduced=inc_pca.transform(X_mnist)
或者使用Numpy的memmap。它允许您操作存储在磁盘上二进制文件中的大型数组,就好像它完全在内存中;该类仅在需要时加载内存中所需的数据。
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
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)
在介绍支持向量机的时候,介绍了核技巧,可以帮助隐式的把样本映射到了高维空间,这样SVM可以处理非线性的分类和回归了。同样的技巧可以应用于 PCA,从而可以执行复杂的非线性投影来降低维度。这就是所谓的核 PCA(kPCA)。它通常能够很好地保留投影后的簇,有时甚至可以展开分布近似于扭曲流形的数据集。
在Sklearn中提供了这样的接口。
from sklearn.decomposition import KernelPCA
rbf_pca=KernelPCA(n_components=2,kernel='rbf',gamma=0.04)
X_reduced=rbf_pca.fit_transform(X)
核技巧涉及到了超参数的选择,但是无监督学习不像监督学习那样,可以简单的用交叉验证确定超参数。
一种方法是,如果无监督学习是某个监督学习的准备步骤,可以创建了一个两步的流水线,然后用网格搜索确定超参数,下面的例子是先降维再做逻辑回归:
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)
>>> print(grid_search.best_params_)
{'kpca__gamma': 0.043333333333333335, 'kpca__kernel': 'rbf'}
另一种方法是,选择产生最低重建误差的核和超参数。但是比普通的PCA还原起来要复杂,因为经过了RBF函数映射,重建的点(下图右下)在特征空间而不是原始空间。例如RBF特征空间是无限多维的,因此,无法计算真实的重建误差。幸运的是,可以在原始空间中找到一个贴近重建点的点。这被称为重建前图像(reconstruction pre-image)(下图左下)
在sklearn中,设置了fit_inverse_transform = True
,Scikit-Learn 将自动执行此操作(默认为False,因此要设置)。然后可以寻找最好的超参数了。
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)
>>> from sklearn.metrics import mean_squared_error
>>> mean_squared_error(X, X_preimage) 32.786308795766132
LLE
局部线性嵌入(Locally Linear Embedding)是另一种非常有效的非线性降维方法。这是一种流形学习技术,不依赖于像以前算法那样的投影。它特别擅长展开扭曲的流形,尤其是在没有太多噪音的情况下。
如图,是LLE展开瑞士卷
from sklearn.manifold import LocallyLinearEmbedding
lle=LocallyLinearEmbedding(n_components=2,n_neighbors=10)
X_reduced=lle.fit_transform(X)
LLE 首先识别其最近的k
个邻居(例子中k=10),然后尝试把这个点重构为这些邻居的线性函数,希望这个函数和原来的值距离和最小
如果不是k个邻居里面,权重为0,如下图:
然后是降维到d维度空间里去,同时尽可能的保留这些局部关系。如果
d
维空间的图,公式如下:
简单来说,第一步是找到这个权重,第二步是根据这个权重找到对应的位置。
LLE建立低维表示为O(d m^2)
。不幸的是,最后一项m^2
使得这个算法在处理大数据集的时候表现较差。
其他降维方法
还有很多其他的降维方法,Scikit-Learn 支持其中的好几种。这里是其中最流行的:
- 多维缩放(MDS)在尝试保持实例之间距离的同时降低了维度
- Isomap 通过将每个实例连接到最近的邻居来创建图形,然后在尝试保持实例之间的测地距离时降低维度。
- t-分布随机邻域嵌入(t-Distributed Stochastic Neighbor Embedding,t-SNE)可以用于降低维度,同时试图保持相似的实例临近并将不相似的实例分开。它主要用于可视化,尤其是用于可视化高维空间中的实例(例如,可以将MNIST图像降维到 2D 可视化)。
- 线性判别分析(Linear Discriminant Analysis,LDA)实际上是一种分类算法,但在训练过程中,它会学习类之间最有区别的轴,然后使用这些轴来定义用于投影数据的超平面。LDA 的好处是投影会尽可能地保持各个类之间距离,所以在运行另一种分类算法(如 SVM 分类器)之前,LDA 是很好的降维技术。