我们主要主要介绍PCA、LDA、LLE方法。
主成分分析(PCA)是另一种常用的数据降维方法,它属于无监督学习算法。PCA旨在找到数据的主成分,并利用这些主成分表征原始数据,从而达到降维的目的。
(1)PCA的推导
1)最大方差理论
在信号处理领域,我们认为信号具有较大方差,噪声具有较小方差,信号与噪声之比称为信噪比。信噪比越大意味着数据质量越好,反之,信噪比越小意味着数据的质量越差。由此,我们引出PCA的目标,即最大化投影方差,也就是让数据在主轴上投影的方差最大。
2)最小平方误差理论
从求解直线的思路出发,很容易联想到数学中的线性回归问题,其目标是求解一个线性函数使得对应直线能够更好的拟合样本点集合。
(2)PCA算法流程
LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的。这点和PCA不同。PCA是不考虑样本类别输出的无监督降维技术。LDA的思想可以用一句话概括,就是“投影后类内方差最小,类间方差最大”。什么意思呢? 我们要将数据在低维度上进行投影,投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大。
(1)LDA推导
(2)LDA算法流程
局部线性嵌入(Locally Linear Embedding,以下简称LLE)。LLE属于流形学习(Manifold Learning)的一种。因此我们首先看看什么是流形学习。流形学习是一大类基于流形的框架。数学意义上的流形比较抽象,不过我们可以认为LLE中的流形是一个不闭合的曲面。这个流形曲面有数据分布比较均匀,且比较稠密的特征,有点像流水的味道。基于流行的降维算法就是将流形从高维到低维的降维过程,在降维的过程中我们希望流形在高维的一些特征可以得到保留。
(1)LLE思想
(2)LLE推导
(3)LLE算法流程
LLE算法主要分为三步,第一步是求K近邻的过程,这个过程使用了和KNN算法一样的求最近邻的方法。第二步,就是对每个样本求它在邻域里的K个近邻的线性关系,得到线性关系权重系数W,具体过程在第三节第一部分。第三步就是利用权重系数来在低维里重构样本数据,具体过程在第三节第二部分。
(1)PCA
class sklearn.decomposition.PCA(n_components=None, copy=True, whiten=False,
svd_solver=’auto’, tol=0.0, iterated_power=’auto’, random_state=None)[source]¶
现在我们对sklearn.decomposition.PCA的主要参数做一个介绍:
1)n_components:这个参数可以帮我们指定希望PCA降维后的特征维度数目。最常用的做法是直接指定降维到的维度数目,此时n_components是一个大于等于1的整数。当然,我们也可以指定主成分的方差和所占的最小比例阈值,让PCA类自己去根据样本特征方差来决定降维到的维度数,此时n_components是一个(0,1]之间的数。当然,我们还可以将参数设置为"mle", 此时PCA类会用MLE算法根据特征的方差分布情况自己去选择一定数量的主成分特征来降维。我们也可以用默认值,即不输入n_components,此时n_components=min(样本数,特征数)。
2)whiten :判断是否进行白化。所谓白化,就是对降维后的数据的每个特征进行归一化,让方差都为1.对于PCA降维本身来说,一般不需要白化。如果你PCA降维后有后续的数据处理动作,可以考虑白化。默认值是False,即不进行白化。
3)svd_solver:即指定奇异值分解SVD的方法,由于特征分解是奇异值分解SVD的一个特例,一般的PCA库都是基于SVD实现的。有4个可以选择的值:{‘auto’, ‘full’, ‘arpack’, ‘randomized’}。randomized一般适用于数据量大,数据维度多同时主成分数目比例又较低的PCA降维,它使用了一些加快SVD的随机算法。 full则是传统意义上的SVD,使用了scipy库对应的实现。arpack和randomized的适用场景类似,区别是randomized使用的是scikit-learn自己的SVD实现,而arpack直接使用了scipy库的sparse SVD实现。默认是auto,即PCA类会自己去在前面讲到的三种算法里面去权衡,选择一个合适的SVD算法来降维。一般来说,使用默认值就够了。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets.samples_generator import make_blobs
from sklearn.decomposition import PCA
#生成数据
# X为样本特征,Y为样本簇类别, 共1000个样本,每个样本3个特征,共4个簇
X, y = make_blobs(n_samples=10000, n_features=3, centers=[[3,3, 3], [0,0,0], [1,1,1], [2,2,2]], cluster_std=[0.2, 0.1, 0.2, 0.2],
random_state =9)
fig = plt.figure()
ax = Axes3D(fig, rect=[0, 0, 1, 1], elev=30, azim=20)
plt.scatter(X[:, 0], X[:, 1], X[:, 2],marker='o')
plt.show()
#建立PCA模型
pca = PCA(n_components=2)
pca.fit(X)
print (pca.explained_variance_ratio_)
print (pca.explained_variance_)
#生产PCA降维后的数据
X_new = pca.transform(X)
plt.scatter(X_new[:, 0], X_new[:, 1],marker='o')
plt.show()
(2)LDA
class sklearn.discriminant_analysis.LinearDiscriminantAnalysis(solver=’svd’,
shrinkage=None, priors=None, n_components=None, store_covariance=False, tol=0.0001)
我们这里对LinearDiscriminantAnalysis类的参数做一个基本的总结。
1)solver : 即求LDA超平面特征矩阵使用的方法。可以选择的方法有奇异值分解"svd",最小二乘"lsqr"和特征分解"eigen"。一般来说特征数非常多的时候推荐使用svd,而特征数不多的时候推荐使用eigen。主要注意的是,如果使用svd,则不能指定正则化参数shrinkage进行正则化。默认值是svd
2)shrinkage:正则化参数,可以增强LDA分类的泛化能力。如果仅仅只是为了降维,则一般可以忽略这个参数。默认是None,即不进行正则化。可以选择"auto",让算法自己决定是否正则化。当然我们也可以选择不同的[0,1]之间的值进行交叉验证调参。注意shrinkage只在solver为最小二乘"lsqr"和特征分解"eigen"时有效。
3)priors :类别权重,可以在做分类模型时指定不同类别的权重,进而影响分类模型建立。降维时一般不需要关注这个参数。
4)n_components:即我们进行LDA降维时降到的维数。在降维时需要输入这个参数。注意只能为[1,类别数-1)范围之间的整数。如果我们不是用于降维,则这个值可以用默认的None。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets.samples_generator import make_classification
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
#生成数据
X, y = make_classification(n_samples=1000, n_features=3, n_redundant=0, n_classes=3, n_informative=2,
n_clusters_per_class=1,class_sep =0.5, random_state =10)
fig = plt.figure()
ax = Axes3D(fig, rect=[0, 0, 1, 1], elev=30, azim=20)
ax.scatter(X[:, 0], X[:, 1], X[:, 2],marker='o',c=y)
plt.show()
#PCA方法
pca = PCA(n_components=2)
pca.fit(X)
print (pca.explained_variance_ratio_)
X_new = pca.transform(X)
plt.scatter(X_new[:, 0], X_new[:, 1],marker='o',c=y)
plt.show()
#LDA方法
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(X,y)
X_new = lda.transform(X)
plt.scatter(X_new[:, 0], X_new[:, 1],marker='o',c=y)
plt.show()
(3)LLE
在scikit-learn中,流形学习库在sklearn.manifold包中。里面实现的流形学习算法有:
1)多维尺度变换MDS算法:这个对应的类是MDS。MDS算法希望在降维时在高维里样本之间的欧式距离关系在低维可以得到保留。由于降维时它需要考虑了样本的全局欧式距离关系,因此降维计算量很大,现在一般较少使用了。
2)等距映射ISOMAP算法:这个对应的类是Isomap。 ISOMAP算法使用了样本间的测地距离来代替欧式距离,此外基本和MDS算法相同。由于降维时它仍然需要考虑了样本的全局测地距离关系,因此降维计算量很大。
3)局部线性嵌入LLE算法:这个对应的类是LocallyLinearEmbedding。这个就是我们LLE原理篇里面的算法、除了包含我们原理篇里讲到的标准的LLE实现以外,它还支持改进版的LLE算法,包括MLLE,HLLE和LTSA。这三个算法我们在原理篇的第五节有介绍。后面我们会详细讲这个类的参数使用。
4)拉普拉斯特征映射LE算法:这个对应的类是SpectralEmbedding。这个算法使用了图论的方法,用样本构成的无向图对应的拉普拉斯矩阵作特征分解来降维。具体方法和我们在谱聚类(spectral clustering)原理总结里面讲到的基本相同。
5)t-distributed Stochastic Neighbor Embedding(t-SNE)算法:这个对应的类是TSNE。这个是一个比较新的降维方法。t-SNE希望样本间的在高维对应的高斯核函数相似度在低维可以得到保留,即低维和高维有尽量一样的相似度矩阵。
这些算法基本原理很类似,都基于流形降维后保持样本之间的某一个特定的关系而产生。下面我们重点讲述LLE算法的使用,即LocallyLinearEmbedding的使用。
class sklearn.manifold.LocallyLinearEmbedding(n_neighbors=5, n_components=2, reg=0.001,
eigen_solver=’auto’, tol=1e-06, max_iter=100, method=’standard’, hessian_tol=0.0001,
modified_tol=1e-12, neighbors_algorithm=’auto’, random_state=None, n_jobs=None)
LLE算法类LocallyLinearEmbedding使用起来并不复杂,一般来说,需要调参的参数只有样本近邻的个数。下面我们对LocallyLinearEmbedding的主要参数做一个介绍。
1)n_neighbors:即我们搜索样本的近邻的个数,默认是5。 n_neighbors个数越大,则建立样本局部关系的时间会越大,也就意味着算法的复杂度会增加。当然n_neighbors个数越大,则降维后样本的局部关系会保持的更好。在下一节我们可以通过具体的例子看出这一点。一般来说,如果算法运行时间可以接受,我们可以尽量选择一个比较大一些的n_neighbors。
2)n_components:即我们降维到的维数。如果我们降维的目的是可视化,则一般可以选择2-5维。
3) reg :正则化系数,在n_neighbors大于n_components时,即近邻数大于降维的维数时,由于我们的样本权重矩阵不是满秩的,LLE通过正则化来解决这个问题。默认是0.001。一般不用管这个参数。当近邻数远远的大于降维到的维数时可以考虑适当增大这个参数。
4)eigen_solver:特征分解的方法。有‘arpack’和‘dense’两者算法选择。当然也可以选择'auto'让scikit-learn自己选择一个合适的算法。‘arpack’和‘dense’的主要区别是‘dense’一般适合于非稀疏的矩阵分解。而‘arpack’虽然可以适应稀疏和非稀疏的矩阵分解,但在稀疏矩阵分解时会有更好算法速度。当然由于它使用一些随机思想,所以它的解可能不稳定,一般需要多选几组随机种子来尝试。
5)method: 即LLE的具体算法。LocallyLinearEmbedding支持4种LLE算法,分别是'standard'对应我们标准的LLE算法,'hessian'对应原理篇讲到的HLLE算法,'modified'对应原理篇讲到的MLLE算法,‘ltsa’对应原理篇讲到的LTSA算法。默认是'standard'。一般来说HLLE/MLLE/LTSA算法在同样的近邻数n_neighbors情况下,运行时间会比标准的LLE长,当然降维的效果会稍微好一些。如果你对降维后的数据局部效果很在意,那么可以考虑使用HLLE/MLLE/LTSA或者增大n_neighbors,否则标准的LLE就可以了。需要注意的是使用MLLE要求n_neighbors > n_components,而使用HLLE要求n_neighbors > n_components * (n_components + 3) / 2
6)neighbors_algorithm:这个是k近邻的搜索方法,和KNN算法的使用的搜索方法一样。算法一共有三种,第一种是蛮力实现,第二种是KD树实现,第三种是球树实现。对于这个参数,一共有4种可选输入,‘brute’对应第一种蛮力实现,‘kd_tree’对应第二种KD树实现,‘ball_tree’对应第三种的球树实现, ‘auto’则会在上面三种算法中做权衡,选择一个拟合最好的最优算法。需要注意的是,如果输入样本特征是稀疏的时候,无论我们选择哪种算法,最后scikit-learn都会去用蛮力实现‘brute’。个人的经验,如果样本少特征也少,使用默认的 ‘auto’就够了。 如果数据量很大或者特征也很多,用"auto"建树时间会很长,效率不高,建议选择KD树实现‘kd_tree’,此时如果发现‘kd_tree’速度比较慢或者已经知道样本分布不是很均匀时,可以尝试用‘ball_tree’。而如果输入样本是稀疏的,无论你选择哪个算法最后实际运行的都是‘brute’。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import manifold, datasets
from sklearn.utils import check_random_state
#生成数据
n_samples = 500
random_state = check_random_state(0)
p = random_state.rand(n_samples) * (2 * np.pi - 0.55)
t = random_state.rand(n_samples) * np.pi
#让球体不闭合,符合流形定义
indices = ((t < (np.pi - (np.pi / 8))) & (t > ((np.pi / 8))))
colors = p[indices]
x, y, z = np.sin(t[indices]) * np.cos(p[indices]),np.sin(t[indices]) * np.sin(p[indices]), np.cos(t[indices])
fig = plt.figure()
ax = Axes3D(fig, elev=30, azim=-20)
ax.scatter(x, y, z, c=p[indices], marker='o', cmap=plt.cm.rainbow)
plt.show()
#用LLE将其从三维降为2维并可视化,近邻数设为30,用标准的LLE算法。
train_data = np.array([x, y, z]).T
trans_data = manifold.LocallyLinearEmbedding(n_neighbors =30, n_components = 2,method='standard').fit_transform(train_data)
plt.scatter(trans_data[:, 0], trans_data[:, 1], marker='o', c=colors)
plt.show()
#用不同的近邻数时,LLE算法降维的效果图
for index, k in enumerate((10,20,30,40)):
plt.subplot(2,2,index+1)
trans_data = manifold.LocallyLinearEmbedding(n_neighbors = k, n_components = 2,
method='standard').fit_transform(train_data)
plt.scatter(trans_data[:, 0], trans_data[:, 1], marker='o', c=colors)
plt.text(.99, .01, ('LLE: k=%d' % (k)),
transform=plt.gca().transAxes, size=10,
horizontalalignment='right')
plt.show()
#用HLLE的效果
for index, k in enumerate((10,20,30,40)):
plt.subplot(2,2,index+1)
trans_data = manifold.LocallyLinearEmbedding(n_neighbors = k, n_components = 2,
method='hessian').fit_transform(train_data)
plt.scatter(trans_data[:, 0], trans_data[:, 1], marker='o', c=colors)
plt.text(.99, .01, ('HLLE: k=%d' % (k)),
transform=plt.gca().transAxes, size=10,
horizontalalignment='right')
plt.show()
(1)PCA
PCA算法的主要优点有:
1)仅仅需要以方差衡量信息量,不受数据集以外的因素影响。
2)各主成分之间正交,可消除原始数据成分间的相互影响的因素。
3)计算方法简单,主要运算是特征值分解,易于实现。
PCA算法的主要缺点有:
1)主成分各个特征维度的含义具有一定的模糊性,不如原始样本特征的解释性强。
2)方差小的非主成分也可能含有对样本差异的重要信息,因降维丢弃可能对后续数据处理有影响。
(2)LDA
LDA算法的主要优点有:
1)在降维过程中可以使用类别的先验知识经验,而像PCA这样的无监督学习则无法使用类别先验知识。
2)LDA在样本分类信息依赖均值而不是方差的时候,比PCA之类的算法较优。
LDA算法的主要缺点有:
1)LDA不适合对非高斯分布样本进行降维,PCA也有这个问题。
2)LDA降维最多降到类别数k-1的维数,如果我们降维的维度大于k-1,则不能使用LDA。当然目前有一些LDA的进化版算法可以绕过这个问题。
3)LDA在样本分类信息依赖方差而不是均值的时候,降维效果不好。
4)LDA可能过度拟合数据。
(3)LLE
LLE算法的主要优点有:
1)可以学习任意维的局部线性的低维流形
2)算法归结为稀疏矩阵特征分解,计算复杂度相对较小,实现容易。
LLE算法的主要缺点有:
1)算法所学习的流形只能是不闭合的,且样本集是稠密均匀的。
2)算法对最近邻样本数的选择敏感,不同的最近邻数对最后的降维结果有很大影响。
1.LDA vs PCA
LDA用于降维,和PCA有很多相同,也有很多不同的地方,因此值得好好的比较一下两者的降维异同点。
相同点:
1)两者均可以对数据进行降维。
2)两者在降维时均使用了矩阵特征分解的思想。
3)两者都假设数据符合高斯分布。
不同点:
1)LDA是有监督的降维方法,而PCA是无监督的降维方法
2)LDA降维最多降到类别数k-1的维数,而PCA没有这个限制。
3)LDA除了可以用于降维,还可以用于分类。
4)LDA选择分类性能最好的投影方向,而PCA选择样本点投影具有最大方差的方向。