PCA(主成分分析)主要用于数据降维。
问题:样本
x i ∈R m
以列向量表示,现将
n
个样本构成的样本集
不难发现,上述问题的核心是:将
X
中的每个样本点由原来的
考虑最简单的二维数据:
import numpy as np
import matplotlib.pyplot as plt
# 绘制数据点
k = 0.3 # 斜率
b = 0.1 # 截距
x1 = np.random.random(30)
x2 = k * x1 + b + np.random.randint(-5, 5, 30) / 100
ax = plt.gca()
ax.set_aspect(1)
plt.scatter(x1, x2, marker='x')
# 中心点
cx = np.mean(x1)
cy = np.mean(x2)
# 绘制第一个维度u1
u1_x = 0.9
u1_y = k * u1_x + b
plt.annotate('', xytext=(cx, cy), xy=(u1_x, u1_y), arrowprops=dict(arrowstyle="->"))
plt.text(u1_x - 0.1, u1_y + 0.01, '$u_1$', fontsize=16)
# 绘制第二个维度u2
u2_x = cx - 0.02
u2_y = (-1 / k) * u2_x + (cy + cx / k)
plt.annotate('', xytext=(cx, cy), xy=(u2_x, u2_y), arrowprops=dict(arrowstyle="->"))
plt.text(u2_x - 0.1, u2_y - 0.01, '$u_2$', fontsize=16)
plt.show()
人能够很直观地看出上述二维数据的2个维度的大致方向,其中最主要的那个方向是
通过上面的分析可知,当所有的数据点在 u 1 方向上的投影绝对值(或方差)之和最大时,此时的 u 1 就是第1主成分。找 u 1 的问题则可以表示为:
其中, proj(x i ,u 1 ) 为 x i 在 u 1 上的投影长度。容易求得:
因为我们只考虑 u 1 的方向,故设 u 1 具有单位长度,即 u 1 的 L 2 范数为1,则:
因为, X=[x 1 x 2 ⋯x n ],X T =⎡ ⎣ ⎢ ⎢ ⎢ ⎢ x T 1 x T 2 ⋮x T n ⎤ ⎦ ⎥ ⎥ ⎥ ⎥ ,所以:
上式即为我们要最大化的目标函数。若能证明矩阵 XX T ∈R m×m 是(半)正定的,则说明 u T 1 XX T u 1 是(半)正定二次型,也就存在最大值。设 λ 是对称矩阵 XX T 的任一特征值,其对应的特征向量是 v∈R m ,则有:
证毕。
下面使用拉格朗日乘子法求 u T 1 XX T u 1 在约束 u T 1 u 1 =1 下的极值,令:
对 u 1 求导,并令其为 0 :
可见,当 u 1 取矩阵 XX T 的某个特征值 α 对应的特征向量时,前述目标函数取得最大值。注意,若选取的特征值 α 不同,其对应的特征向量 u 1 也不同,则前述目标函数的最大值也不同。将上式代入目标函数,可得:
可见,当
u 1
取最大特征值对应的特征向量时,前述目标函数将取得所有最大值中的最大值,此时的
u 1
即为【第1主成分】。若取前
k
大特征值对应的特征向量
主成分矩阵:
降维后的数据集:
将降维后的
k
维数据集还原到原始的
下面通过一个图像压缩的例子来验证PCA。
import math
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# 读取图片
X = np.array(Image.open(".\_data\son.jpg").convert('L'), 'f') # M*N
M = X.shape[0]
N = X.shape[1]
XXT = X.dot(X.T) # shape: M*M
e, V = np.linalg.eig(XXT) #特征值、特征向量
U = V[np.argsort(-e)] # M*M,按特征值从大到小,对特征向量排序
plt.subplots(figsize=(20,10))
r, c, i = (5, 6, 1)
for k in range(M, 0, -math.ceil(M/(r*c))):
Uk = U[:, 0:k] # M*k
X_ = Uk.dot(Uk.T).dot(X) # X_restore: M*N
ratio = 1.0*k*(M+N)/(M*N)
plt.subplot(r, c, i, title='↓k=%d, ratio=%.1f' % (k, ratio)).axis('off')
plt.imshow(Image.fromarray(X_))
i+=1
plt.axis('off')
plt.show()