调用sklearn库时,使用decomposition.PCA(n_components=2)就可以把多维的数据降至两维,里面的过程一概不知,看起来像个黑盒,心里面很没有底。今天小王同学就来尝试解答以下问题
1)主成分分析法PCA里的分解过程是什么样的?基于什么原理
2)相对原数据,它保留了哪些信息,又损失了什么?怎么科学选择成分的数量?
代码部分参考自Sebastian Raschka的博客pca_in_3_steps,感谢原作大神
作为其他学习资料的一个补充,概念部分不多赘述。概括来说,我们对一个D维的数据集运用PCA,希望最大化M维子空间的保留信息,同时最小化(D-M)维空间上的损失信息。今天以代码层层递进的形式,结合图像对分解过程和主成分选取进行说明。
首先,我们准备经典数据集iris,在sklearn数据库中即可加载
from sklearn import datasets
# 加载数据
iris = datasets.load_iris()
# help(datasets.load_iris) # Check the data struture
X = iris.data # Each row is an observation, each col is a variable
y = iris.target
print(" The shape of X and y ", X.shape, y.shape,"\n",
"Features are ", iris.feature_names, "\n", "Classes are ", iris. target_names)
The shape of X and y (150, 4) (150,)
Features are ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
Classes are ['setosa' 'versicolor' 'virginica']
这一步,自己尝试了MinMax scaling,由于petal length特征的值域过大,最大最小化缩放后,特征三和其余四个特征不在一个数量级。考虑到数量级的差异对PCA这种距离敏感型算法,容易造成影响。故采用了原作者的Z-score归一化方法,其他方法笔者未试验效果。
from sklearn.preprocessing import StandardScaler
X_std = StandardScaler().fit_transform(X)
对于调库的细节,特别补充说明下,查阅资料时发现一些数据大神们会强调不同归一化方法之间的区别。比如最容易和StandardScaler方法混淆的Normalizer,这里整理如下(描述引用自sklearn官方文档)
这里,样本协方差矩阵(Sample covariance matrix)采用其原始定义进行求解。它的第i行第j列的单个元素可表示为Cov(X_i,X_j),其中X_i,X_j分别对应着样本集的第i列和第j列特征数据,k代表观察值序号(k=1,2,…,n),则有矩阵元素:
C o v ( X i , X j ) = 1 n ∑ k = 1 n ( x i , k − E ( X i ) ) ( x j , k − E ( X j ) ) Cov(X_{i}, X_{j}) = \frac{1}{n}\sum^{n}_{k=1}(x_{i,k} - E(X_{i}))(x_{j,k} - E(X_{j})) Cov(Xi,Xj)=n1k=1∑n(xi,k−E(Xi))(xj,k−E(Xj))
import numpy as np
X_train = X_std # Create a reference to the origin
X_mean = np.mean(X_train, axis=0) # A vector with size (4,)
n = X_train.shape[0]
X_cov = (X_train - X_mean).T @ (X_train - X_mean) / n
X_cov # A symmetric matrix with size (4,4)
可以通过np.cov(X_train.T, bias=True)实现同样的结果,但要注意输入样本矩阵的形状。同时,更直观的,我们把协方差矩阵可视化:
图1)鸢尾花数据集的样本协方差矩阵,绘制代码可参考附录资料2
对于一个对称矩阵来说,对应于不同特征值的特征向量,必定彼此具有正交性。如果再将这一组特征向量分别做归一化(normalization)处理,就能得到一组单位正交向量,进而组成正交矩阵(orthogonal matrix)。
# 计算协方差矩阵的特征值、特征向量
import numpy as np
eig_val, eig_vec = np.linalg.eig(X_cov)
# 检验得到的是否为单位正交矩阵,即每一列的norm和为1
for ev in eig_vec.T:
np.testing.assert_array_almost_equal(1.0, np.linalg.norm(ev))
# 这里直接用assert,标准过高,浮点误差会报错
# numpy自带的assert almost,默认精度为六位,可设置参数decimal修改
# 方法二,也可使用u,s,v = np.linalg.svd(X_std.T),eig_vec = u
print("Eigen values\n", eig_val, "\nEigen vectors\n", eig_vec)
Eigen values
[2.93808505 0.9201649 0.14774182 0.02085386]
Eigen vectors
[[ 0.52106591 -0.37741762 -0.71956635 0.26128628]
[-0.26934744 -0.92329566 0.24438178 -0.12350962]
[ 0.5804131 -0.02449161 0.14212637 -0.80144925]
[ 0.56485654 -0.06694199 0.63427274 0.52359713]]
回到我们开头提出的问题,已经得到了特征值和特征向量,但到底选用多少维度合适,是我们这次希望了解的。
对于鸢尾花数据集来说,维度小还能直观看出,但面对以后的研究和工作,我们需要掌握一定的分析方法。下面介绍其中一种——利用降序排列的特征值累计求和(Cumulative sum)的方法:
tot = sum(eig_val)
var_exp = [(i / tot)*100 for i in sorted(eig_val, reverse=True)]
cum_var_exp = np.cumsum(var_exp) # Return a cumulative sum
print("Cumulative sum \n", cum_var_exp)
图2)鸢尾花数据集的特征值累计求和,绘制代码可参考附录资料1
从图像中可以看到绝大多数方差(72.96%)能被第一个主成分解释,再加上第二个主成分,两者共能解释95%以上的方差。
我们知道降维方法在追求简化的过程中,希望尽可能大的保留所需的原始信息。前两项主成分已经能解释绝大多数的数据变化(通常需要在80%以上),这时其余主成分被舍弃,就不至于损失太多信息。因此,在该案例下,选择降维至二维子空间是一个合理的决定。
# 根据上述的分析,选用前两个主成分进行降维
proj_mat = np.hstack((eig_vec[:, 0].reshape(-1,1),
eig_vec[:, 1].reshape(-1,1)))
print('Projection matrix\n', proj_mat)
Y = X_std @ proj_mat # Y即是我们最终需要的降维结果
Projection matrix
[[ 0.52106591 -0.37741762]
[-0.26934744 -0.92329566]
[ 0.5804131 -0.02449161]
[ 0.56485654 -0.06694199]]
target_names = ['setosa','versicolor','virginica']
with plt.style.context('seaborn-whitegrid'):
plt.figure(figsize=(6, 4))
for lab, col in zip((0, 1, 2),
('blue', 'red', 'green')):
plt.scatter(Y[y==lab, 0],
Y[y==lab, 1],
label=target_names[lab],
c=col)
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.legend(loc='lower center')
plt.tight_layout()
plt.show()
本次,我们尝试拆解了PCA的实现过程,并知道如何合理筛选维度数。但依旧有一些未尽的工作。包括以下部分:
参考资料:
经验有限,如有错误,欢迎指正