主成分分析(PCA)
主成分分析(Principal Component Analysis)是一种无监督的降维算法。不同于线性判别分析(LDA),PCA不需要数据集中包含类别标签,其核心思想是将高维特征映射到新的空间后,按照新空间各个特征重要性有序选取坐标轴。新空间构建时,第一个坐标轴选取原始数据映射后方差最大的方向,后续坐标轴均保证与之前坐标轴正交的情况下方差最大,重复该过程直至新空间维度与原始特征数相等,或新空间维度数量达到降维需求。
从线性代数的角度来看,PCA实际上是对原始数据去除平均值后,求协方差矩阵,然后对其特征值进行排序,并根据这些特征值对应的特征向量构建新空间。
PCA的实现
Numpy
使用NumPy中的SVD实现一个简单的PCA:
import numpy as np
from random import randint
X = np.array([randint(-10, 10) for _ in range(40)]).reshape(5, 8)
X_centred = X - X.mean(axis=0)
U, sigma, V = np.linalg.svd(X_centred)
# find the first and the second principal component
c1 = V.T[:, 0]
c2 = V.T[:, 1]
# projecting down to 3 dimensions
# by computing the dot product of
# the X and the first 3 principal components
X3D = X_centred.dot(V.T[:, :3])
Scikit-Learn
在Scikit-Learn中,有可以直接使用的PCA类,使用前不需要对数据进行去除均值的处理:
from sklearn.decomposition import PCA
# projecting down to 3 dimensions
pca = PCA(n_components=3)
X3D = pca.fit_transform(X)
# check the first principal component
c1 = pca.components_.T[:, 0]
Spark
Spark的机器学习库MLlib提供了可以对RDD[Vector]型的数据进行降维的PCA类:
import org.apache.spark.mllib.feature.PCA
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.rdd.RDD
val data = sc.parallelize(Seq(
Vectors.dense(1, 0, 0, 0, 1),
Vectors.dense(1, 1, 0, 1, 0),
Vectors.dense(1, 1, 0, 0, 0),
Vectors.dense(1, 0, 0, 0, 0),
Vectors.dense(1, 1, 0, 0, 0)))
val pca = new PCA(k=3).fit(data)
val projected = data.map(feat => pca.transform(feat))
print(projected.take(2).mkString)
// [0.5206573684395938,-0.4271322870657469,-0.7392387395392244]
// [-1.1529018904707837,-0.7155024798795673,-0.3985893027102969]
print(pca.pc)
/*
0.0 0.0 0.0
-0.7557893406837769 0.17214785894087992 -0.6317812811178025
0.0 -5.551115123125783E-17 0.0
-0.39711254978700694 -0.8876503388204472 0.2331919784075056
0.5206573684395938 -0.4271322870657469 -0.7392387395392244
*/
解释方差占比
具体选取PCA之后的多少个维度构建降维后的低维空间呢?这其实取决于你希望PCA降维后的表示可以包含高维空间中的多少信息。用来衡量携带信息量的常用指标就是解释方差占比(explained variance ratio),其计算就是分别求出各个特征值占所有特征值总和的占比。各个框架的PCA实现中都会提供接口,比如在Scikit-Learn中调用:
# check the explained variance ratio
evr = pca.explained_variance_ratio_
在上面Spark的例子中,我们可以获得对应的3个维度的解释方差占比:
print(pca.explainedVariance)
// [0.6512522620920779,0.2236380402113374,0.12510969769658492]
这意味着,第一个主成分包含了65.13%的方差信息,第二个主成分包含了22.36%。假设降维的目标是保留至少75%的方差信息,那么只保留前两个主成分就够了。
维度选取
既然可以获取解释方差占比,那我们就可以直接通过占比进行降维时的维度选择。比如我们期望保留95%的方差,那就可以对原始数据进行维度不变的PCA,然后寻找累加的解释方差占比高于95%的最少特征数:
pca = PCA()
pca.fit(X)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1
在Scikit-Learn中,同样的操作只需要在n_components的设定时赋一个0到1的浮点数,就可以自动进行:
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X)
当然,也可以像K-means的k值选取一样,在解释方差占比的累加值的曲线图中寻找elbow点,作为降维时的维度选取依据。
数据复原
使用PCA可以在保留足够方差的前提下,显著降低原始数据的维度,因此PCA也可以被看作是一种压缩算法。在Scikit-Learn中,同样提供将低维数据还原为高维数据的方法:
X_recovered = pca.inverse_transform(X_reduced)