begin time:2019-05-14
文章目录
降维
维度的诅咒
当训练实例的特征达到几千甚至上百万时,会导致训练非常缓慢,也让我们难以找到好的解决方案。
解决方法
大量减少特征的数量,将棘手的问题转化成容易解决的问题。
注意:
数据降维确实会丢失一些信息,所以,它虽然能够加速训练,但是也会轻微降低系统性能。同时也让流水线更为复杂,,维护难度上升。所以,如果训练太慢,你首先应该尝试的还是继续使用原始数据,然后再考虑数据降维。不过在某些情况下,降低训练数据的维度可能会滤除掉一些不必要的噪声和细节,从而导致性能更好(但通常来说不会,它只会加速训练)
有利于数据可视化
将维度降到两个(或三个),就可以在图形上绘制出高维训练集,通过视觉检测模式,常常可以获得一些十分重要的洞察,比如说聚类。
当前最流行的三种数据降维技术:PCA、kernal PCA以及LLE。
1 维度的诅咒
单位平面中,随机挑选两个点,两点之间的平均距离大约为0.52。
这个事实说明高为数据集有很大可能是非常稀疏的:大多数训练实例可能彼此之间相距很远。当然,这也意味着新的实例可能远离任何一个训练实例,导致预测跟低纬度相比,更加不可靠,因为它们基于更大的推测。简而言之,训练集的维度越高,过度拟合的风险就越大。
理论上来说,增大训练集,使训练实例达到足够的密度,是可以解开维度的诅咒的。然而不幸的是,实践中,要达到给定密度所需要的训练实例数量随着维度增加呈指数上升。
2 数据降维的主要方法
投影和流行学习。
2.1 投影
在大多数现实世界的问题里,训练实例在所有维度上并不是均匀分布的。许多特征几乎是不变的,也有许多特征是高度相关联的(如前面讨论的MNIST数据集)。
高维空间的所有训练实例实际上(或近似于)受一个低得多的低维子空间所影响
3维空间内的实例特征可能能够用一个二维的平面来表示。
不过投影并不总是降维的最佳方法。在许多情况下,子空间可能会弯曲或转动
著名的瑞士卷玩具数据集
简单的进行平面投影(例如放弃x3)会直接将瑞士卷的不同层压扁在一起,但是你真正想要的是将整个瑞士卷展开铺平以后的2D数据集。
2.2 流行学习
瑞士卷就是二维流形的一个例子。
二维流形,就是一个能够在更高维空间里面弯曲和扭转的2D形状。更概括的说,d维流形就是n(其中,d<n)维空间的一部分,局部类似于一个d维超平面。在瑞士卷的例子中,d=2,n=3:它局部类似于一个2D平面,但是在第三个维度上卷起。
许多降维算法是通过对训练实例进行流形建模来实现的,这被称为流形学习。它依赖于流形假设,也称为流形假说,认为大多数现实世界的高维度数据集存在一个低维度的流形来重新表示。这个假设通常是凭经验观察的。
再次说到MNIST数据集:所有手写的数字图像都有一些相似之处。它们由相连的线条组成,边界都是白色的,或多或少是居中的,等等。如果你随机生成图像,只有少到不能再少的一部分可能看起来像手写数字。也就是说,如果你要创建一个数字图像,你拥有的自由度要远远低于允许你创建任意图像的自由度。而这些限制正倾向于将数据集挤压成更低维度的流形。
一个隐含的假设:
流形假设通常还伴随着一个隐含的假设:如果能用低维空间的流形表示,手头的任务(例如分类或者回归)将变得更简单。
但是,这个假设并不总是成立。
小总结
简而言之,在训练模型之前降低训练集的维度,肯定可以加快训练速度,但这并不总是会导致更好或更简单的解决方案,它取决于数据集。
3 PCA
主成分分析(PCA)是迄今为止最流行的降维算法。它先是识别出最接近数据的超平面,然后将数据投影其上。
这一部分明显比另一本书中将的清楚。
3.1 保留差异性
首先:选择正确的超平面。
将训练集投影到低维超平面之前,需要选择正确的超平面。
例:
一个简单的2D数据集,沿三条不同的轴(即一维超平面)进行投影。
其次:选择保留最大差异性的超平面。
选择保留最大差异性的轴看起来比较合理,因为它可能比其他两种投影丢失的信息更少。要证明这一选择,还有一种方法,比较原始数据集与其轴上的投影之间的均方距离,使这个均方距离最小的轴是最合理的选择,也就是实线代表的轴。这也正是PCA背后的简单思想
3.2 主成分
主成分分析(PCA)可以在训练集中识别出哪条轴对差异性的贡献度最高。在二维数据集中,即是由实线表示的轴。同时它也找出了第二条轴,它对剩余差异性的贡献度最高,与第一条轴垂直。因为这个例子是二维的,所以除了这条点线再没有其他。如果是在更高维数据集中,PCA还会找到与前两条都正交的第三条轴,以及第四条、第五条,等等——轴的数量与数据集维度数量相同。
定义
定义第i条轴的单位向量就叫作第i个主成分(PC)。
主成分的方向是不稳定的:如果你稍微打乱训练集,然后重新运行PCA,部分新的主成分可能指向跟原来的主成分相反的方向。但是,它们通常还是在同一条轴上。在某些情况下,两条主成分可能会旋转甚至互换,但是它们定义的平面还是不变。
两个相交的向量可以确定一个平面。
找到主成分的方法,奇异值分解(SVD)
所以怎么找到训练集的主成分呢?还好有一种标准矩阵分解技术,叫作奇异值分解(SVD)。它可以将训练集矩阵X分解成三个矩阵的点积U·Σ·VT,其中VT正包含我们想要的所有主成分,如公式8-1所示。
公式8-1:主成分矩阵
下面的Python代码使用NumPy的svd()函数来获取训练集中所有的主成分,并提取前两个:
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(比如前面的示例),或者使用其他库时,不要忘记先将数据集中。
4 低纬度投影
一旦确定了所有主成分,就可以将数据集投影到由前d个主成分定义的超平面上,从而将数据集的维度降到d维。这个超平面的选择,能确保投影保留尽可能多的差异性。
要将训练集投影到超平面上,简单地计算训练集矩阵X和矩阵Wd的点积即可,Wd是包含前d个主成分的矩阵(即由矩阵VT的前d列组成的矩阵),参见公式8-2。
公式8-2:将训练集投影到低维度
#以下Python代码将训练集投影到由前两个主成分定义的平面上:
W2 = V.T[:, :2]
X2D = X_centered.dot(W2)
5 使用scikit-learn
Scikit-Learn的PCA类也使用SVD分解来实现主成分分析。以下代码应用PCA将数据集的维度降到二维(注意它会自动处理数据集中):
from sklearn.decomposition import PCA
pca = PCA(n_components = 2)
X2D = pca.fit_transform(X)
将PCA转换器应用到数据集之后,你可以通过变量components_来访问主成分(它包含的主成分是水平向量,因此举例来说,第一个主成分即等于pca.components_.T[:,0])
5.1 方差解释率
通过变量explained_variance_ratio_获得
5.2 选择正确数量的维度
除了武断地选择要降至的维度数量,通常来说更好的办法是将靠前的主成分方差解释率依次相加,直到得到足够大比例的方差(例如95%),这时的维度数量就是很好的选择。当然,除非你正在为了数据可视化而降维——这种情况下,通常会直接降到二维或三维。
下面的代码计算PCA但是没有降维,而是计算若要保留训练集方差的95%所需要的最低维度数量:
pca = PCA()
pca.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) #想要保留方差贡献率大于0.95的成分
X_reduced = pca.fit_transform(X)
另外,还可以将解释方差绘制成关于维度数量的函数(绘制cumsum即可,见图8-8)。曲线通常都会有一个拐点,说明方差停止快速增长。你可以将其视为数据集的本征维数。从本例中可以看出,将维度数量降低至100维,不会损失太多的解释方差。
6 PCA压缩
降维之后训练集占用的空间要小得多。
例如,对MNIST数据集应用主成分分析,然后保留其方差的95%。你会发现,原来每个实例的784个特征变得只有150多个特征。所以这保留了绝大部分差异性的同时,数据集的大小变为不到原始的20%!这是一个合理的压缩比,你可以看看它如何极大提升分类算法(例如SVM分类器)的速度。
在PCA投影上运行投影的逆转换,也可以将缩小的数据集压缩回784维数据集。当然,你得到的并非原始的数据,因为投影时损失了一部分信息(5%被丢弃的方差),但是它很大可能非常接近于原始数据。
原始数据和重建数据(压缩之后解压缩)之间的均方距离,被称为重建误差。
pca = PCA(n_components = 154)
X_mnist_reduced = pca.fit_transform(X_mnist)
X_mnist_recovered = pca.inverse_transform(X_mnist_reduced)
公式8-3:PCA逆转换,回到原始维度
7 增量PCA,增量主成分分析(IPCA)
针对无法装入内存的大型数据集,采用增量的方式,分批放入内存中运行SVD算法。
前面关于主成分分析的种种实现,问题在于,它需要整个训练集都进入内存,才能运行SVD算法。幸运的是,我们有增量主成分分析(IPCA)算法:你可以将训练集分成一个个小批量,一次给IPCA算法喂一个。对于大型训练集来说,这个方法很有用,并且还可以在线应用PCA(也就是新实例产生时,算法开始运行)。
#以下代码将MNIST数据集分成100个小批量(使用NumPy的
#array_split()函数),将它们提供给Scikit-Learn的
#IncrementalPCA(http://goo.gl/FmdhUP)[2],将数据集降到154维(跟
#之前一样)。注意,你必须为每个小批量调用partial_fit()方法,而
#不是之前整个训练集的fit()方法:
from sklearn.decomposition import IncrementalPCA
n_batches = 100 #分批处理,分为100批
inc_pca = IncrementalPCA(n_components=154)
for X_batch in np.array_split(X_mnist, n_batches):
inc_pca.partial_fit(X_batch)
X_mnist_reduced = inc_pca.transform(X_mnist)
或者,你也可以使用NumPy的memmap类,它允许你巧妙地操控一个存储在磁盘二进制文件里的大型数组,就好似它也完全在内存里一样,而这个类(memmap)仅在需要时加载内存中需要的数据。由于IncrementalPCA在任何时间都只使用数组的一小部分,因此内存的使用情况仍然受控,这时可以调用常用的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)
8 随机PCA
Scikit-Learn还提供了另一种实施PCA的选项,称为随机PCA。这是一个随机算法,可以快速找到前d个主成分的近似值。它的计算复杂度是O(m×d2)+O(d3),而不是O(m×n2)+O(n3),所以当d远小于n时,它比前面提到的算法要快得多。
rnd_pca = PCA(n_components=154, svd_solver="randomized")
X_reduced = rnd_pca.fit_transform(X_mnist)
9 核主成分分析(kPCA)
第5章讨论了核技巧,它是一种数学技巧,隐性地将实例映射到非常高维的空间(称为特征空间),从而使支持向量机能够进行非线性分类和回归。回想一下,高维特征空间的线性决策边界如何对应于
原始空间中复杂的非线性决策边界。事实证明,同样的技巧也可应用于PCA,使复杂的非线性投影降
维成为可能。这就是所谓的核主成分分析(kPCA)。它擅长在投影后保留实例的集群,有时甚至也能展开近似于一个扭曲流形的数据集。
例如,下面的代码使用Scikit-Learn的KernelPCA,执行带有RBF核函数的kPCA(有关RBF核和其他核的更多细节,请参阅第5章):
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)
9.1 选择核函数和调整超参数
由于kPCA是一种无监督的学习算法,因此没有明显的性能指标来帮你选择最佳的核函数和超参数值。而降维通常是监督式学习任务(例如分类)的准备步骤,所以可以使用网格搜索,来找到使任务性能最佳的核和超参数。例如,下面的代码创建了一个两步流水线,
首先使用kPCA将维度降至二维,
然后应用逻辑回归进行分类。
接下来使用GridSearchCV为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重建那样容易。
我们来看看原因,因为核技巧,所以这在数学上等同于:通过特征映射函数φ,将训练集映射到无限维度的特征空间,然后用线性PCA将转换后的训练集投影到2D平面。注意,如果我们对一个已经降维的实例进行线性PCA逆转换,重建的点将存在于特征空间,而不是原始空间中。而这里特征空间是无限维度的,所以我们无法计算出重建点,因此也无法计算出真实的重建误差。幸好,我们可以在原始空间中找到一个点,使其映射接近于重建点。这被称为重建原像。一旦有了这个原像,你就可以测量它到原始实例的平方距离。最后,便可以选择使这个重建原像误差最小化的核和超参数。
要怎么执行这个重建呢?方法之一是训练一个监督式回归模型,以投影后的实例作为训练集,并以原始实例作为目标。如果你设置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_transform()方法。只有在设置fit_inverse_transform=True时才会创建该方法。
然后你就可以计算重建原像误差:
>>> from sklearn.metrics import mean_squared_error
>>> mean_squared_error(X, X_preimage)
32.786308795766132
现在,你可以使用交叉验证的网格搜索,来寻找使这个原像重建误差最小的核和超参数。
10 局部线性嵌入(LIE)
局部线性嵌入(LLE)是另一种非常强大的非线性降维(NLDR)技术。不像之前的算法依赖于投影,它是一种流形学习技术。简单来说,LLE首先测量每个算法如何与其最近的邻居(c.n.)线性相关,然后为训练集寻找一个能最大程度保留这些局部关系的低维表示。这使得它特别擅长展开弯曲的流形,特别是没有太多噪声时。
公式8-4:LLE第一步:对局部关系线性建模
公式8-5:LLE第二步:保留关系并降维
其他降维技巧
还有许多其他的降维技术,部分可以在Scikit-Learn中找到。以下是一些最流行的:
·多维缩放(MDS)算法,保持实例之间的距离,降低维度。
·等度量映射(Isomap)算法,将每个实例与其最近的邻居连接起来,创建连接图形,然后保留实例之间的这个测地距离,降低维度。
·t-分布随机近邻嵌入(t-SNE)算法在降低维度时,试图让相似的实例彼此靠近,不相似的实例彼此远离。它主要用于可视化,尤其是将高维空间中的实例集群可视化(例如,对MNIST图像进行二维可视化)。
·线性判别(LDA)实际上是一种分类算法,但是在训练过程中,它会学习类别之间最有区别的轴,而这个轴正好可以用来定义投影数据的超平面。这样做的好处在于投影上的类别之间会尽可能的分开,所以在运行其他分类算法——比如SVM分类器之前,LDA是一个不错的降维手段。
endtime:2019-05-14 中午