主成分分析
简述
主成分分析意在学习一个映射 U r e d u c e U_{reduce} Ureduce,能将高维数据投射到低维空间上(在矩阵表示中即将数据的列变少),PCA后的低维空间数据可通过映射 U r e d u c e T U_{reduce}^T UreduceT还原成高维数据的近似。PCA意在使得在指定维度的低维表示中,投射误差总是最小。在ML中即用于将 m m m个样本的特征数目从 n n n减少到 k k k。
归一化
设样本的特征矩阵
A
A
A是
m
m
m行
n
n
n列的,第
i
i
i行第
j
j
j列的
a
j
(
i
)
a_j^{(i)}
aj(i)即是第
i
i
i个样本的第
j
j
j个特征的值。数据归一化是使样本的特征的均值为0,第
j
j
j个特征在
m
m
m个样本的样本集中的均值
μ
j
=
1
m
∑
i
=
1
m
a
j
(
i
)
\mu_j=\frac{1}{m} \sum_{i=1}^m a_j^{(i)}
μj=m1i=1∑maj(i)
数据归一化即是使
x
j
(
i
)
=
a
j
(
i
)
−
μ
j
x_j^{(i)}=a_j^{(i)}-\mu_j
xj(i)=aj(i)−μj
缩放
不同的特征值很可能取值范围相差很大,缩放即使特征的最大最小值之差恰为1。取每个特征的最大最小值之差
s
j
=
m
a
x
(
a
j
(
i
)
)
−
m
i
n
(
a
j
(
i
)
)
≡
m
a
x
(
x
j
(
i
)
)
−
m
i
n
(
x
j
(
i
)
)
s_j=max(a_j^{(i)})-min(a_j^{(i)}) \equiv max(x_j^{(i)})-min(x_j^{(i)})
sj=max(aj(i))−min(aj(i))≡max(xj(i))−min(xj(i))
对归一化后的数据进行缩放,即
x
j
(
i
)
=
x
j
(
i
)
s
j
=
a
j
(
i
)
−
μ
j
s
j
x_j^{(i)}=\frac{x_j^{(i)}}{s_j}=\frac{a_j^{(i)}-\mu_j}{s_j}
xj(i)=sjxj(i)=sjaj(i)−μj
PCA降维
前面的归一化和缩放属于数据预处理的过程,得到的还是
m
×
n
m\times n
m×n的矩阵
X
X
X,先计算样本协方差矩阵
Σ
\Sigma
Σ
Σ
=
1
m
X
T
X
\Sigma=\frac{1}{m}X^TX
Σ=m1XTX
对其做SVD,得到
[
U
,
S
,
V
]
=
s
v
d
(
Σ
)
[U,S,V]=svd(\Sigma)
[U,S,V]=svd(Σ)
因为前面得到的
Σ
\Sigma
Σ是
n
×
n
n\times n
n×n的,行数为
n
n
n,所以
U
U
U是
n
n
n阶方阵,其列空间由左奇异向量组成
U
=
[
u
1
,
u
2
,
.
.
.
,
u
n
]
U=[\pmb{u}_1,\pmb{u}_2,...,\pmb{u}_n]
U=[uuu1,uuu2,...,uuun]
样本协方差矩阵
Σ
\Sigma
Σ可以由这些特征向量线性表出。在约定奇异值在主对角线上从大到小排列的要求下,只要选取前
k
k
k个列向量,组成主成分特征矩阵
U
r
e
d
u
c
e
=
[
u
1
,
u
2
,
.
.
.
,
u
k
]
U_{reduce}=[\pmb{u}_1,\pmb{u}_2,...,\pmb{u}_k]
Ureduce=[uuu1,uuu2,...,uuuk]
其维度是
n
×
k
n\times k
n×k,则可以用其对样本矩阵
X
X
X的特征做降维
Z
=
X
U
r
e
d
u
c
e
Z=XU_{reduce}
Z=XUreduce
其中,
Z
Z
Z的行向量
z
(
i
)
\pmb{z}^{(i)}
zzz(i)就是第
i
i
i个样本的特征向量
x
(
i
)
\pmb{x}^{(i)}
xxx(i)在主成分特征矩阵
U
r
e
d
u
c
e
U_{reduce}
Ureduce表示的线性空间上的投影
z
(
i
)
=
U
r
e
d
u
c
e
T
x
(
i
)
\pmb{z}^{(i)}=U_{reduce}^T \pmb{x}^{(i)}
zzz(i)=UreduceTxxx(i)
PCA恢复
由上式,对
x
(
i
)
\pmb {x}^{(i)}
xxx(i)的近似
x
a
p
p
r
o
x
(
i
)
=
U
r
e
d
u
c
e
z
(
i
)
\pmb{x}_{approx}^{(i)}=U_{reduce} \pmb{z}^{(i)}
xxxapprox(i)=Ureducezzz(i)
所以
X
a
p
p
r
o
x
=
Z
U
r
e
d
u
c
e
T
X_{approx}=ZU_{reduce}^T
Xapprox=ZUreduceT
numpy实现PCA
主要是numpy就已经实现了SVD,所以可以很方便的实现PCA。
数据预处理和降维
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rcParams['axes.unicode_minus'] = False
# 原始矩阵
A = np.array([
[3, 2000],
[2, 3000],
[4, 5000],
[5, 8000],
[1, 2000]
])
# m行n列
m, n = A.shape
# 数据归一化:先计算每一列(特征)的均值,再用自动广播让每行(样本)减去均值
mean = np.mean(A, axis=0)
norm = A - mean
# 数据缩放:先计算每一列的最大最小值之差,再用自动广播让每一行除以这个差
scope = np.max(norm, axis=0) - np.min(norm, axis=0)
X = norm / scope
# 计算协方差矩阵Sigma(n阶方阵)
Sigma = (1.0 / m) * np.dot(X.T, X)
# 对协方差矩阵做奇异值分解
U, S, V = np.linalg.svd(Sigma)
# 因为是将二维(列)的数据变成一维(列),所以k=1
k = 1
# 取特征矩阵U(n阶方阵)的前k列构造主成分特征矩阵U_reduce(n行k列)
U_reduce = U[:, 0:k].reshape(n, k)
# 对数据进行降维,降维后的Z(m行k列)=X(m行n列)乘以U_reduce(n行k列)
Z = np.dot(X, U_reduce)
print(Z)
[[ 0.2452941 ]
[ 0.29192442]
[-0.29192442]
[-0.82914294]
[ 0.58384884]]
数据恢复
# 还原得到近似数据
X_approx = np.dot(Z, U_reduce.T)
A_approx = X_approx * scope + mean # 这里书上用np.multiply也可以
print(A_approx)
[[2.33563616e+00 2.91695452e+03]
[2.20934082e+00 2.71106794e+03]
[3.79065918e+00 5.28893206e+03]
[5.24568220e+00 7.66090960e+03]
[1.41868164e+00 1.42213588e+03]]
绘制降维及恢复示意图
# 绘图面板
plt.figure(figsize=(8, 8), dpi=100)
plt.title("降维及恢复示意图")
# 坐标最小最大值
mm = (-1, 1)
plt.xlim(*mm)
plt.ylim(*mm)
# 右/上坐标轴透明
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# 绘制归一化之后的数据点
plt.scatter(X[:, 0], X[:, 1], marker='s', c='b')
# 绘制还原回来的数据点
plt.scatter(X_approx[:, 0], X_approx[:, 1], marker='o', c='r')
# 绘制主成分特征向量,即主成分特征矩阵U的n个列向量
plt.arrow(0, 0, U[0, 0], U[1, 0], color='r', linestyle='-')
plt.arrow(0, 0, U[0, 1], U[1, 1], color='r', linestyle='--')
# 接下来要在图上绘制标注,下面是提取出来的要给plt.annotate()用的一些参数
tit = [ # 标题
r"$U_{reduce}=\mathbf{u}^{(1)}$",
r"$\mathbf{u}^{(2)}$",
"原始数据点",
"投影点"
]
xy = [ # xy指定剪头尖端位置
(U[0, 0], U[1, 0]),
(U[0, 1], U[1, 1]),
(X[0, 0], X[0, 1]),
(X_approx[0, 0], X_approx[0, 1])
]
# xytest指定标注文本位置,用这个函数来从xy生成
pian = lambda a, b: (a + 0.2, b - 0.1)
# xycoords='data'表示使用轴坐标系,arrowprops指定箭头的属性.在这个嵌套dict中可以看到两种定义字典的写法
kw = {'xycoords': 'data', 'fontsize': 10, 'arrowprops': dict(arrowstyle="->", connectionstyle="arc3,rad=.2")}
# 绘制四个标注
for i in range(len(tit)):
plt.annotate(tit[i], xy=xy[i], xytext=pian(*xy[i]), **kw)
plt.show()