背景與原理:
PCA(主成分分析)是將一個數據的特征數量減少的同時盡可能保留最多信息的方法。所謂降維,就是在說對於一個$n$維數據集,其可以看做一個$n$維空間中的點集(或者向量集),而我們要把這個向量集投影到一個$k<n$維空間中,這樣當然會導致信息損失,但是如果這個$k$維空間的基底選取的足夠好,那么我們可以在投影過程中盡可能多地保留原數據集的信息。
數據降維的目的在於使得數據更直觀、更易讀、降低算法的計算開銷、去除噪聲。
接下來我們討論下如何選取$k$維子空間:
假設原數據集有$m$條數據,每條數據有$n$維,那么可以將其拼成一個$n*m$的矩陣M,而我們想投影到的$k$維空間的一個單位正交基底為$(p_{1},...,p_{k})$,那么我們想把這$m$維向量投影到這個空間中實際上就是進行一次矩陣乘法$\begin{pmatrix} p_{1}\\p_{2}\\...\\p_{k} \end{pmatrix} M$
這個道理是簡單易懂的。對於一個向量$\alpha$,其在另一個向量$\beta$方向上的投影是$\dfrac{\alpha \cdot \beta }{|\beta|}$(高中數學)
如果$|\beta|$是一個單位向量,那么這個投影即為$\alpha \cdot \beta=\beta^{T} \alpha$,於是投影到$k$個單位向量為基底的空間中的情況即如上述所示。
因此我們要找到這$k$個單位向量作為基底,然后拼出$P=\begin{pmatrix} p_{1}\\p_{2}\\...\\p_{k} \end{pmatrix}$即可。
那么怎么找呢?我們考慮降維之后我們需要什么,由於我們在降維之后要盡可能多地保留原始信息,因此降維之后的數據要提供最大的信息量,那么這個信息量在這里可以用數據的方差來反映,方差越大,數據的離散程度越高,那么數據的自身特征保留的就越好(個人理解:PCA降維的目的在於突出數據的個體特征,減少信息損失,而如果降維之后數據離散程度低,意味着這些數據全都堆在一起,那數據的特征體現的就不明顯了——所有數據全都差不多,這樣個體信息保留的就不好了)。
對於一個特征,在$m$組數據中的方差為$\sigma^{2}=\dfrac{1}{m}\sum_{i=1}^{m}(x_{i}-\overline{x})^{2}$,為了便於討論,我們對所有特征零均值化(即把每個$x_{i}$預先減去$\overline{x}$),這樣一個特征的方差即為$\sigma^{2}=\dfrac{1}{m}\sum_{i=1}^{m}x_{i}^{2}$
但是降維過程中只考慮方差是不夠的——如果我們發現兩個特征之間有很強的線性相關性,那么這兩個特征其實差別就不大了,我們當然不需要同時保留這兩個特征,因此我們還希望降維之后任意兩個特征的協方差($cov(a,b)=\dfrac{1}{m}\sum_{i=1}^{m}a_{i}b_{i}$,因為已經進行過零均值化了)為零,也就是在說我們選取的子空間的基底一定是正交的。
那么現在的問題就轉化成了:對於一個$n$維$m$組數據的$n*m$數據矩陣$X$,我們希望將其投影到$n$維空間的一個$k$維子空間中,因此我們要找到$k$個單位正交基$(p_{1},...,p_{k})$,而如果這$k$個單位正交基構成的矩陣$P=\begin{pmatrix} p_{1}\\p_{2}\\...\\p_{k} \end{pmatrix}$,那么投影過程即為$Y=PX$,$Y$即為降維后所得的$k$維數據集
而結合上述討論,我們希望$Y$各個特征的方差最大,同時$Y$的兩特征的協方差為零,這怎么操作呢?
對於一個$n$維有$m$組數據的$n*m$矩陣$X$,我們考察$C=\dfrac{1}{m}XX^{T}$,那么我們看到如果$X=\begin{pmatrix} x_{11} & x_{12} & ... & x_{1m}\\...\\x_{n1} & x_{n2} &... & x_{nm}\end{pmatrix}$,我們有:
$XX^{T}=\begin{pmatrix} \sum_{i=1}^{m}x_{1i}^{2} & \sum_{i=1}^{m} x_{1i}x_{2i} &...& \sum_{i=1}^{m}x_{1i}x_{ni}\\...\\ \sum_{i=1}^{m}x_{ni}x_{1i} & \sum_{i=1}^{m} x_{ni}x_{2i} &...& \sum_{i=1}^{m}x_{ni}^{2}\end{pmatrix}$
我們稱$\dfrac{1}{m}XX^{T}$為協方差矩陣,因為我們看到按照我們上面的解釋,這個矩陣是一個實對稱矩陣,其主對角線上的元素是一個特征維度的方差,而其余位置上的元素是兩個對應特征的協方差!
那么我們的目的是要最大化主對角線上的元素,同時讓其余位置上的元素為$0$,那么我們進行的不就是實對稱矩陣的正交相似對角化嘛!
形式化地解釋一下:我們設$Y$的協方差矩陣為$D$,那么我們希望$D$是一個對角矩陣,同時$D$的主對角線上的元素要盡可能大,那么我們有:
$D=\dfrac{1}{m}YY^{T}=\dfrac{1}{m}(PX)(X^{T}P^{T})=P(\dfrac{1}{m}XX^{T})P^{T}$
那么我們實際進行的不就是把$C=\dfrac{1}{m}XX^{T}$這個協方差矩陣正交相似對角化嘛!
至於我們希望主對角線元素盡可能大,那我們就選取$C$的前$k$大的特征值組成$D$就好了嘛,而此時的$P$就對應於前$k$大的特征值對應的$k$個正交的特征向量構成的矩陣。
那么我們的算法步驟如下:
對於$n$行$m$列的矩陣$X$,我們解釋成其有$m$組數據,每組數據有$n$個特征,現在我們欲將其變成$k*m$的矩陣$Y$,表示降維后每組數據只有$k$個特征。
(1)零均值化:對$X$的每個元素,減去自己所在行的均值(即我們是逐特征操作,一行對應於同一個特征,不要搞錯這一點)
(2)計算協方差矩陣$C=\dfrac{1}{\textbf{m}}XX^{T}$
(3)對協方差矩陣對角化$C=P\Sigma P^{T}$,找到其單位正交的特征向量$e_{1},...,e_{n}$
(4)選取最大的$k$個特征值對應的特征向量$e_{i_{1}},...,e_{i_{k}}$,拼成一個變換矩陣$P_{k}=\begin{pmatrix} e_{i_{1}}\\e_{i_{2}}\\...\\e_{i_{k}} \end{pmatrix}$
(5)降維后的數據即為$Y=P_{k}X$
(6)如果希望根據降維后的數據集$Y$近似還原原數據集$\hat{X}$,我們有$\hat{X}=P_{k}^{T}Y$(這里的邏輯是如果我們不降維,那么$P_{k}=P$就是一個正交矩陣,那么$P^{T}P=I$,相當於此時數據集沒有損失,那么類比這個過程就能導出近似還原方法$\hat{X}=P_{k}^{T}Y$)
代碼實現:
import numpy as np from sympy.matrices import Matrix,GramSchmidt np.random.seed(1) x = 7*np.random.rand(100) y = 0.5*x + 1 + 3*np.random.rand(100) X = np.hstack([x.reshape(100, 1), x.reshape(100, 1), y.reshape(100, 1), x.reshape(100, 1)]) def centerData(X): X = X.copy() X -= np.mean(X, axis=0) return X X = centerData(X)print(X[7][2]) C= (np.transpose(X)@X)/100 val,fea=np.linalg.eig(C) dic=dict() for i in range(0,4): dic[val[i]]=fea[:,i] val=abs(np.sort(-val)) P=np.vstack([dic[val[0]],dic[val[1]]]) Y=X@P.T reconstruct_X=Y@P print(reconstruct_X[7][2])
值得注意的問題是這段代碼中生成的數據每組數據是一個行向量,每列對應於一個特征,因此所有的計算和上面的推導都構成一個轉置。
此外這里使用了numpy里面的linalg.eig方法用來求一個實對稱矩陣的特征值和特征向量,返回的val是特征值,fea是特征向量,要特別說明的是不出意外的情況下這里的val都是按從大到小排序的,而fea實際上是一個矩陣,這個矩陣每個列向量對應於一個特征值,因此一定要注意選取的方法。上面代碼使用前兩個特征向量作為主成分恢復原矩陣,可以看到恢復效果還是不錯的。
import numpy as np from sklearn.decomposition import PCA import matplotlib.pyplot as plt from sklearn.datasets import load_iris data=load_iris() X=data.data Y=data.target pca=PCA(n_components=2) X_2d=pca.fit_transform(X) plt.scatter(X_2d[:,0],X_2d[:,1],c=Y) plt.show()
當然,PCA也可以直接使用sklearn里面的包,上述代碼加載了經典的鳶尾花數據集,然后進行PCA降維(降成二維,這個n_components參數給出了要降到的維度),然后能清楚看到三個鳶尾花的類別,效果很好。
PCA的另類用法:
在著名的神經網絡論文AlexNet中曾提出了一種使用PCA進行數據增強的算法,稱之為PCA jitter。所謂數據增強,就是通過對訓練數據人為的加噪聲,提高模型魯棒性與預測能力的方法。從觀感來講,PCA jitter近似對光效的變化,對比直接對RGB通道進行加噪聲會獲得更少的顏色的失真。
而這個方法的主要流程就是:對於一張圖片,我們把每個像素看做一個數據,其有三個特征(即R,G,B三通道顏色),把每個特征標准化(均值為0,方差為1,即對每個數據減去均值后除以標准差即可),然后計算出這個矩陣的協方差矩陣,把協方差矩陣對角化,依據協方差矩陣對角化的結果對原圖進行抖動,具體原理可以參考論文,這里給出一種實現:
def solve(val,fea): ran=np.random.normal(0,50,3) tval=val*ran D=tval@fea re=np.zeros((1080,1080,3)) for i in range(0,1080): for j in range(0,1080): for k in range(0,3): re[i][j][k]=np.clip(img[i][j][k]+D[k],0,255) plt.imshow(re/255) l=[] for i in img: for j in i: l.append(j) data=(np.array(l)).T data=data.astype(np.float64) for i in range(0,3): ave=data[i,:].mean() std=data[i,:].std() for j in range(0,len(data[i,:])): data[i][j]=(data[i][j]-ave)/std Q=data@data.T/len(data[i,:]) val,fea=np.linalg.eig(Q) solve(val,fea)
這里的抖動是從均值為0,方差為50的正態分布里抽樣得到的,這個方差可以自己設定,根據效果選取。實際上抖動的過程就是如果設特征值為$\alpha=(\lambda_{1},\lambda_{2},\lambda_{3})$,特征向量構成的矩陣為$P$,隨機數為$r$,那么我們計算$\beta=r\alpha P$得到一個$1*3$的向量即為RGB三通道各自的抖動值。
(上述過程可能有誤,歡迎大佬指出,不勝感激!)
而實現結果:如果原圖是這樣的
那么一個隨機的情況是這樣的:
類似於加了清晨朦朧的光效?可以看到還是有一定效果的。