机器学习:PCA 降维

参考文章:

http://blog.codinglabs.org/articles/pca-tutorial.html

https://zhuanlan.zhihu.com/p/37777074

在学习的过程中发现线性代数的知识非常重要,回顾了一下考研期间看的线性代数的本质:https://www.bilibili.com/video/BV1Ys411k7yQ

选择不同的基可以对同样一组向量给出不同的表示,而且如果基的数量少于向量本身的维数,则可以达到降维的效果。

去均值化(去中心化)

数据中的每一个特征中的数据都减去其平均值。

二维降为一维:投影与方差

以二维降维至一维为例,我们希望在选择这个基后,所有的点在这个基的方向上的投影值尽可能分散。

量化分散的程度:方差,在进行均值化后,由于平均数为 0,计算方差时可以直接使用

Var(a)=1mi=1mai2Var(a) = \frac{1}{m}\sum^m_{i=1}a_i^2

于是乎,寻找基地的依据就是要尽可能地使方差变大。

三维降为二维:协方差与协方差矩阵

协方差

在选择了一个方向(基)后,在选择另一个方向时,我们不能再单纯地选择方差最大的方向,因为这会和第一个方向几乎重合在一起,我们希望他们之前尽可能地线性无关,可以用协方差来量化相关性,当协方差为 0 时,表示两个字段完全独立。为了让协方差为 0,我们选择第二个基时只能在与第一个基正交的方向上选择。因此最终选择的两个方向一定是正交的:

Cov(a,b)=1mi=1m(aiaˉ)(bibˉ)Cov(a,b) = \frac{1}{m}\sum_{i=1}^m({a_i}-\bar{a})(b_i-\bar{b})

优化目标:将一组 n 维向量降为 k 维,其目标是选择 k 个单位正交基,使得原始数据变换到这组基上后,各特征两两间协方差为0,而特征内部的方差则尽可能大

由于每个特征均值均为 0,协方差的计算可以直接使用下面的式子,将所有数据的特征 a 的值构成一个列向量 a\vec{a},等价于计算 a\vec{a}b\vec{b} 的内积然后除以样本的数量:

Cov(a,b)=1mi=1maibiCov(a,b) = \frac{1}{m}\sum_{i=1}^ma_ib_i

协方差矩阵

协方差矩阵本质上是为了能够方便地计算出各个特征之间的协方差:

X=(a1a2a3...anb1b2b3...bn...)C=1mXXTX = \left( \begin{array}{l} a_1 & a_2 & a_3 & ... & a_n \\ b_1 & b_2 & b_3 & ... & b_n \\ ... \end{array} \right) \\ C = \frac{1}{m}XX^T

设矩阵 X 大小为 m*n,即由 m 个 n 维列向量组成,则 C 是一个对称矩阵,对角线为各个字段的方差,而 C(i, j) 为第 i 和第 j 个特征的协方差

对角化

设原始矩阵对应的协方差矩阵为 C,而 P 是一种变换,X 在变换后得到 Y,即 Y=PX,设 Y 的协方差矩阵为 D,由上述的优化目标可得,我们的 D 要求除了对角元素外的元素均为 0,且对角元素从上往下大小递减,易得

D=1mYYT=1m(PX)(PX)T=1mPXXTPT=P(1mXXT)PT=PCPT\begin{aligned} D &= \frac{1}{m}YY^T \\ &= \frac{1}{m}(PX)(PX)^T \\ &= \frac{1}{m}PXX^TP^T\\ &= P(\frac{1}{m}XX^T)P^T\\ &= PCP^T \end{aligned}

由上可知,D 可视为 C 的一个对角矩阵,优化目标变成了寻找一个正交矩阵 P(P1=PT)P (P^{-1}=P^T) ,满足PCPTPCP^T是一个对角矩阵,并且对角元素按从大到小依次排列,那么 P 的前 K 行就是要寻找的基,用 P 的前 K 行组成的矩阵乘以 X 就使得 X 从 N 维降到了 K 维并满足上述优化条件。

由于 C 作为协方差矩阵,必然是实对称的,也就必然可以进行对角化,所以可以直接通过计算特征向量来得到 P,而特征值做为对角元素的矩阵即为 Q

Python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# @author microven @date 2023.3.31
import numpy as np
from sklearn.decomposition import PCA

# 目标维度
target = 1
x = np.array([[-1, -2], [-1, 0], [0, 0], [2, 1], [0, 1]])
# 零均值化
x = x.astype("float64")
x_mean = np.mean(x, axis=0)
x -= x_mean
# 计算协方差矩阵
x_t = np.transpose(x)
cov = np.matmul(x_t, x) / x.shape[0]
# 计算协方差矩阵的特征值
eigen, feature = np.linalg.eig(cov)
# 确定 top k 特征值
sorted_index = np.argsort(eigen)[::-1]
top_k = np.array([feature[:, sorted_index[0]]])
for i in range(1, target):
top_k = np.append(top_k, feature[:, sorted_index[i]])
result_x = np.matmul(x, np.transpose(top_k))
print(result_x)