写在开头
最近在研究生导师的引导下入了机器学习的坑,开始漫长啃论文作码农的生活。本篇文章主要是记录下我学习pca算法的笔记,更多是对论文的翻译(A TUTORIAL ON PRINCIPAL COMPONENT ANALYSIS,论文链接放在文章最后,很经典的一篇文章),尽量做到逻辑通顺,有错误恳请大家指正,轻喷。
PCA(Principal component analysis 主成分分析)是广泛使用的降维算法,基于线性变换的线性降维方法,即符合PX = Y(P为方阵,描述一种线性变换)。为什么需要降维,当数据样本维度很高时,无论是计算数据间距离,还是做其他计算操作,计算机开销都不小,这种情形被称为“维度灾难”(西瓜书里看的)。而在保留大部分数据信息的情况下,减少数据特征类型,缓解维度灾难。
基础
目标:找到最有‘意义’的基底来重新表示嘈杂、充满混乱的数据集。
The Goal: Principal component analysis computes the most meaningful basis to re-express a noisy, garbled data set.
框架-基变换(原理)
前文中提过,PCA是线性降维方法,满足PX = Y。设X为nxm的矩阵,此时,Y矩阵的列向量可由P矩阵的列向量线性表出,就是说P是一个空间的基底,而X(数据集)是对应的坐标值,Y是X在P空间中的映射。(
p
1
p_1
p1为P矩阵列向量,
y
1
y_1
y1为Y矩阵列向量)
[
p
1
⋯
p
n
]
[
x
11
x
12
⋯
x
1
m
⋮
⋮
⋱
⋮
x
n
1
x
n
2
⋯
x
n
m
]
=
[
y
1
⋯
y
m
]
(1)
\left[ \begin{matrix} p_1 & \cdots & p_n \end{matrix} \right] \left[ \begin{matrix} x_{11} & x_{12} & \cdots & x_{1m}\\ \vdots & \vdots & \ddots & \vdots\\ x_{n1} & x_{n2} &\cdots & x_{nm} \end{matrix} \right] = \left[ \begin{matrix} y_1 & \cdots &y_m \\ \end{matrix} \right] \tag{1}
[p1⋯pn]⎣⎢⎡x11⋮xn1x12⋮xn2⋯⋱⋯x1m⋮xnm⎦⎥⎤=[y1⋯ym](1)
{ y 1 = p 1 x 11 + p 2 x 21 ⋯ + p n x n 1 y 2 = p 1 x 12 + p 2 x 22 ⋯ + p n x n 2 ⋮ y m = p 1 x 1 m + p 2 x 2 m ⋯ + p n x n m (2) \begin{cases} y_1= &p_1x_{11} + p_2x_{21} & \cdots &+p_nx_{n1}\\ y_2= &p_1x_{12} + p_2x_{22} & \cdots &+p_nx_{n2}\\ \vdots\\ y_m= &p_1x_{1m} + p_2x_{2m} & \cdots &+p_nx_{nm}\\ \end{cases}\tag{2} ⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧y1=y2=⋮ym=p1x11+p2x21p1x12+p2x22p1x1m+p2x2m⋯⋯⋯+pnxn1+pnxn2+pnxnm(2)
从式中可以得出,P矩阵的维度(列向量的维数)与Y向量相同,当P维度数小于m(原数据集X的维度)时,新的数据矩阵Y维度也会小于m,即实现了降维。
换句话说,PCA算法的本质是高维数据在低维空间中的投影。
协方差的应用
在一个线性系统中,会造成混乱的数据一般分成两类 n o i s e 、 r e d u n d a n c y . noise、redundancy. noise、redundancy.(噪声和冗余),下面分别进行讨论。
Noise
顾名思义,噪声是指发生错误的数据,也就是无意义的数据,会对数据分析产生影响。
噪声的衡量不存在绝对的尺度,但所有的噪声都是相对于测量值来进行测量的。一个常用的测量方法是信噪比,也就是信息数据方差与噪声方差的比值
There exists no absolute scale for noise but rather all noise is measured relative to the measurement. A common measure is the the signalto-noise ratio (SNR), or a ratio of variances
σ
2
σ^2
σ2
S
N
R
=
σ
s
i
g
n
a
l
2
σ
n
o
i
s
e
2
(3)
SNR = \text{\( {σ^2_{signal}} \above{2pt} {σ^2_{noise}}\)}\tag{3}
SNR=σnoise2σsignal2(3)
信噪比表示出数据的精度,高信噪比的数据精度高,低信噪比的数据说明噪声数据多。
Redundancy
冗余数据是更隐晦的问题。冗余数据可以理解为两种不同特征间存在某种联系,如
y
=
k
x
y=kx
y=kx,那么y数据对应的特征就是非必需的,也就是多余的。而想知道两种测量数据间是否存在某种关系,最简单的方式是将一种数据作为横轴,另一数据作为纵轴,画出平面图观察。
下图中是一系列任意两种测量种类(也就是数据特征,维度)间的图像关系。
从图中可以看出,(c)中表示两种数据存在明显的线性关系
r
2
−
k
r
1
r_2-kr_1
r2−kr1,故在数据测量时仅需收集其中一种数据,即可以使数据集更简洁同时减小数据的采集量,起到良好的数据降维作用。
协方差
只需计算方差便可得出 S N R SNR SNR,而量化数据间的关系则用到了协方差。下面以 A , B A,B A,B为例,设 A , B A,B A,B中数据样本已进行中心化(即每个数据减去平均值,即 ∑ i x i = 0 \sum_{i}x_i=0 ∑ixi=0)
A = { a 1 , a 2 , ⋯ , a n } , B = { b 1 , b 2 , ⋯ , b n } \begin{gathered} A=\{{a_1,a_2,\cdots,a_n}\},\ B=\{{b_1,b_2,\cdots,b_n}\} \end{gathered} A={a1,a2,⋯,an}, B={b1,b2,⋯,bn}
定义方差和协方差
σ
A
2
=
∑
a
i
a
i
,
σ
B
2
=
∑
b
i
b
i
c
o
v
a
r
i
a
n
c
e
o
f
A
a
n
d
B
=
σ
A
B
2
=
∑
a
i
b
i
\begin{gathered} σ^2_A = \sum_{}a_ia_i,\ σ^2_B = \sum_{}b_ib_i\\ covariance\ of\ A\ and\ B\ = \ σ^2_{AB}=\ \sum_{}a_ib_i \end{gathered}
σA2=∑aiai, σB2=∑bibicovariance of A and B = σAB2= ∑aibi
从式中可推出两个重要结论:
1. σ A B 2 = 0 1.\ σ^2_{AB}\ =\ 0 1. σAB2 = 0 时, A , B A,B A,B间完全不相关
2. σ A B 2 = σ A 2 2.\ σ^2_{AB}\ =\ σ^2_A 2. σAB2 = σA2 时, A = B A\ =\ B A = B
将 A 、 B A、B A、B数据作为列向量
a = [ a 1 a 2 ⋯ a n ] b = [ b 1 b 2 ⋯ b n ] \begin{gathered} a\ =\ [a_1\ \ a_2\ \ \cdots\ \ a_n]\\ b\ =\ [b_1\ \ b_2\ \ \cdots\ \ b_n] \end{gathered} a = [a1 a2 ⋯ an]b = [b1 b2 ⋯ bn]
最后可以导出协方差公式
σ
A
B
2
σ^2_{AB}
σAB2矩阵斜对角线上的数据表示方差,而非对角线元素表示不同特征间的协方差。
σ A B 2 = 1 n − 1 a b T \begin{gathered} σ^2_{AB}=\ \text{\(\frac 1 {n-1}\)}ab^T \end{gathered} σAB2= n−11abT
为什么不是1/n?因为1/n是有偏估计的标准化,特别是在n比较小的时候,而1/(n-1)为无偏估计的标准化。(概率论没怎么学,俺不太懂,直接放结论)
The simplest possible normalization is 1/n. However, this provides a biased estimation of variance particularly for small n. It is beyond the scope of this paper to show that the proper normalization for an unbiased estimator is 1/n−1
特征向量
在求出协方差矩阵后,让我们回到对混乱数据的讨论;协方差为0时表示数据间毫无关系,所以在我们进行基变换后的新矩阵Y,我们希望它的协方差矩阵是一个对角矩阵(除对角线元素外,其他元素都为0的矩阵),这样可以大幅度减少数据间的冗余。
而协方差矩阵是实对称矩阵(这个可以自己在稿纸上验算一下),必正交相似于对角矩阵。写到这里我们对PCA降维有了大致思想。
数学公式推导
在推导开始前,先总结一下PCA算法的思路:我们的目标是找到一个更适合的基底,来更好地重新表示我们的数据集。更好的数据集意味着数据集不混乱,于是我们定义了两种会造成混乱的数据——噪声与冗余,而为了量化两种混乱数据,我们引入方差和协方差来衡量数据集中无意义数据的量。最后我们通过对协方差矩阵的优化,得出最合适的基。
接下第一步是求出协方差矩阵。
假定数据集
x
x
x是
m
m
m x
n
n
n的数据矩阵,其中m表示测量类型的数量(也就是特征,维度),n表示数据的个数。首先算出m行数据的平均值,再用原数据矩阵减去平均值矩阵。
x = x − x ‾ (赋值) x = x\ -\ \overline{x} \tag{赋值} x=x − x(赋值)
计算协方差矩阵
S
x
=
1
(
n
−
1
)
x
x
T
S_x\ =\ \frac1 {(n-1)} xx^T
Sx = (n−1)1xxT
注:如果
x
x
x数据集中,m是特征,而n是数据个数,算平均值是每个特征的所有数据求平均,算方差也是每个特征的所有数据做方差,所以这里是
x
∗
x
T
x*x^T
x∗xT(m x m的矩阵)
第二步推导Y的协方差矩阵
S
Y
S_Y
SY
将
Y
=
P
X
Y\ =\ PX
Y = PX带入
S
Y
=
1
(
n
−
1
)
Y
Y
T
S_Y\ =\ \frac 1 {(n-1)} YY^T
SY = (n−1)1YYT
令
A
=
x
∗
x
T
A\ =\ x\ *\ x^T
A = x ∗ xT,进行对角化可得
A
=
E
∗
D
∗
E
T
A = E*D*E^T
A=E∗D∗ET;E为A矩阵特征向量组成的正交矩阵,D是A矩阵特征值作对角线元素的对角矩阵。满足
E
−
1
=
E
T
E^{-1}\ =\ E^T
E−1 = ET
然后我们让P矩阵的每一行都是A矩阵的特征向量,即
P
=
E
T
P\ =\ E^T
P = ET。可得
A
=
P
T
∗
D
∗
P
A\ =\ P^T*D*P
A = PT∗D∗P,于是
这样,我们最后得到了一个协方差矩阵是对角矩阵的新矩阵Y,同时得到了适合的基底P——协方差矩阵的特征向量。
但新的问题出现,协方差矩阵
A
A
A为mxm的矩阵,求出的特征向量矩阵也是mxm的,这样Y=PX得到的P矩阵仍是mxn的矩阵,与原本的维度相同。
这时,我们可以分析特征值矩阵D,由于部分特征值非常小,我们可以舍去这部分特征值,达到降维效果。
假设我们选取
k
k
k个特征值(k<m),得出的P矩阵为kxm(P矩阵的行向量是特征向量),得到的Y矩阵变成kxn,完成降维。
小结
· X X X原数据集矩阵的主成分是协方差矩阵 X ∗ X T X*X^T X∗XT的特征向量。
· 为了达到降维的效果,我们计算了量化数据混乱程度的协方差矩阵,同时使用正交对角化优化协方差矩阵,并舍去部分特征值,最后得到新的数据矩阵 Y Y Y。
和SVD的联系
S V D SVD SVD是奇异值分解的缩写,一般我们做PCA降维时都会使用SVD。SVD简单来说是对非方阵进行的特征值分解(只有方阵可以进行特征值分解,也就是写成特征值与特征向量表示的形式)。其他概念这里不在讲述,可以自行百度(太多了,懒)。
X = U Σ V T \begin{gathered} X\ =\ U\Sigma V^T \end{gathered} X = UΣVT
而奇异值分解中需要求出协方差矩阵的特征值和特征向量(应该是右奇异值),所以在我个人认为使用SVD做PCA降维和直接使用协方差矩阵是一样的。
部分代码
论文中的代码(matlab):
使用SVD:
我自己写的(和sklearn库对比后有些bug,仅供参考):
import numpy as np
def pca_try_nm(self, data, n_com):
"""
:param data: 数据集,为了方便转成了nxm,n为数据个数,m为维度
:param n_com: 降维后数据保留的百分比
:return:
"""
y = np.array(data)
[N, M] = y.shape
y_aver = y.mean(axis=0)
# 平均值处理
a = np.zeros(shape=y.shape)
for i in range(M):
a[:, i] = y[:, i] - y_aver[i]
# SVD分解
Y = a / np.sqrt(N - 1)
U, sigma, VT = np.linalg.svd(Y)
# 根据输入的特征保留比例,进行降维
eige = 0
re_sum = 0
for i in sigma:
eige = eige + i
if eige >= sigma.sum() * n_com:
break
else:
re_sum = re_sum + 1
# 得到新的数据矩阵
signal = np.matmul(a, VT.T[:, 0:(re_sum + 1)])
本人菜鸡一枚,写得不太行,大家随便看看,希望能给大家带来点收获(「・ω・)「
论文:https://www.cs.princeton.edu/picasso/mats/PCA-Tutorial-Intuition_jp.pdf