寫在前面的話:這部分基本上就是學(xué)習(xí)筆記,主要參考里面的博客已經(jīng)寫得很好,只是按自己的復(fù)習(xí)學(xué)習(xí)思路整理了下。
一、數(shù)學(xué)基礎(chǔ)
1. 特征值和特征向量
特征值是線性代數(shù)中的一個(gè)重要概念。設(shè) A 是n階方陣,如果存在數(shù)m和非零n維列向量使得下式成立:
則稱 是A的一個(gè)特征值。非零n維列向量
稱為矩陣A對(duì)應(yīng)于特征值
的特征向量,簡(jiǎn)稱A的特征向量。
幾何意義:通常來說,左乘一個(gè)矩陣會(huì)使得 在方向和長度上發(fā)生變化,但對(duì)于特征向量來說,卻可以保持方向不變,只進(jìn)行比例為
的伸縮。
2. SVD奇異值分解
由于特征值和特征向量只對(duì)方陣才有意義,所以針對(duì)不是方陣的情況提出了SVD。
假設(shè)給定了某一個(gè)矩陣A,其維度是m×n。那么,定義SVD為如下式:
其中,U是一個(gè)m×m的矩陣,Σ是一個(gè)m×n的矩陣,V是一個(gè)n×n的矩陣。Σ中,除了對(duì)角線上的元素之外,其余元素的值都為0,主對(duì)角線上的每個(gè)元素都被稱為奇異值。另外,U和V都是經(jīng)過標(biāo)準(zhǔn)化之后的,即滿足:
推導(dǎo)過程可以參考:奇異值分解(SVD)原理詳解及推導(dǎo)
3. 協(xié)方差矩陣

二、PCA推導(dǎo)
PCA的主要作用是降維,找出數(shù)據(jù)里最主要的方面,用數(shù)據(jù)里最主要的方面來代替原始數(shù)據(jù):

例如,我們的數(shù)據(jù)集是n維的,共有m個(gè)數(shù)據(jù)(x(1),x(2),...,x(m))。我們希望將這m個(gè)數(shù)據(jù)的維度從n維降到k維,希望這m個(gè)k維的數(shù)據(jù)集盡可能的代表原始數(shù)據(jù)集。我們知道數(shù)據(jù)從n維降到k維肯定會(huì)有損失,但是我們希望損失盡可能的小。那么如何讓這k維的數(shù)據(jù)盡可能表示原來的數(shù)據(jù)呢?
在信號(hào)處理中認(rèn)為信號(hào)具有較大的方差,噪聲有較小的方差,信噪比就是信號(hào)與噪聲的方差比,越大越好。借用信號(hào)的觀點(diǎn),最好的k維特征是將n維樣本點(diǎn)轉(zhuǎn)換為k維后,每一維上的樣本方差都很大。
具體推導(dǎo)過程:
給定一組數(shù)據(jù):(如無說明,以下推導(dǎo)中出現(xiàn)的向量都是默認(rèn)是列向量)

將其中心化后表示如下,即得到了樣本偏離的程度:


計(jì)算投影的方法就是將x與u1做內(nèi)積,由于只需要求u1的方向,所以設(shè)u1是單位向量:

也就是最大化下式:

由矩陣代數(shù)相關(guān)知識(shí)可知,可以對(duì)絕對(duì)值符號(hào)項(xiàng)進(jìn)行平方處理,比較方便。所以進(jìn)而就是最大化下式:

兩個(gè)向量做內(nèi)積,可以轉(zhuǎn)化成矩陣乘法:

所以目標(biāo)函數(shù)可以表示為:

括號(hào)里面就是矩陣乘法表示內(nèi)積,轉(zhuǎn)置以后的行向量乘以列向量得到一個(gè)數(shù)。因?yàn)橐粋€(gè)數(shù)的轉(zhuǎn)置還是其本身,所以又可以將目標(biāo)函數(shù)化為:

這樣就可以把括號(hào)去掉,去掉以后變成:

由于u1和i無關(guān),可以把它拿到求和符外面:

注意,其實(shí)括號(hào)里面是一個(gè)矩陣乘以自身的轉(zhuǎn)置,這個(gè)矩陣形式如下:


其實(shí)將這個(gè)矩陣表示出來的話就會(huì)發(fā)現(xiàn)其實(shí)就是協(xié)方差矩陣,我們?cè)趦?yōu)化降維后方差最大化的過程中折騰出協(xié)方差矩陣非常合理,也有從協(xié)方差矩陣角度出發(fā)講解PCA的,比較好懂:PCA的數(shù)學(xué)原理
所以目標(biāo)函數(shù)最后化為:

上式到底有沒有最大值呢?如果沒有前面的1/n,那就是就是一個(gè)標(biāo)準(zhǔn)的二次型,并且得到的矩陣是一個(gè)半正定的對(duì)稱陣。為什么?首先
是對(duì)稱陣,因?yàn)?img class="math-inline" src="https://math.jianshu.com/math?formula=(XX%5ET)%20%5ET" alt="(XX^T) ^T" mathimg="1">=
,下面證明它是半正定,什么是半正定?就是所有特征值大于等于0:

證明半正定陣的二次型的目標(biāo)函數(shù)存在最大值:
對(duì)于向量x,其二范數(shù)(也就是模長)的平方為:

所以有:

使用矩陣范數(shù)不等式可以得到:

表示矩陣A的最大奇異值
既然證明了有最大值,后面就簡(jiǎn)單了,使用拉格朗日乘數(shù)法即可求出:
目標(biāo)函數(shù)和約束條件構(gòu)成了一個(gè)最大化問題:

構(gòu)造拉格朗日函數(shù):

對(duì)u1求導(dǎo):

顯然,u1即為特征值
對(duì)應(yīng)的特征向量,則
的所有特征值和特征向量都滿足上式,那么將上式代入目標(biāo)函數(shù)表達(dá)式即可得到:

所以,如果取最大的那個(gè)特征值,那么得到的目標(biāo)值就最大。有可能你會(huì)有疑問,為什么一階導(dǎo)數(shù)為0就是極大值呢?那么再求二階導(dǎo)數(shù):

二階導(dǎo)數(shù)半負(fù)定,所以,目標(biāo)函數(shù)在最大特征值所對(duì)應(yīng)的特征向量上取得最大值。所以,第一主軸方向即為第一大特征值對(duì)應(yīng)的特征向量方向。第二主軸方向?yàn)榈诙筇卣髦祵?duì)應(yīng)的特征向量方向,以此類推,證明類似。
三、PCA算法流程
求樣本x(i)的k維的主成分其實(shí)就是求樣本集的協(xié)方差矩陣 的前k個(gè)特征值對(duì)應(yīng)特征向量矩陣W,然后對(duì)于每個(gè)樣本x(i),做如下變換
,即達(dá)到降維的PCA目的。
輸入:n維樣本集D=(x(1),x(2),...,x(m)),要降維到的維數(shù)k
輸出:降維后的樣本集
對(duì)所有的樣本進(jìn)行中心化:
計(jì)算樣本的協(xié)方差矩陣
對(duì)矩陣
進(jìn)行特征值分解
取出最大的k個(gè)特征值對(duì)應(yīng)的特征向量(w1,w2,...,wn′), 將所有的特征向量標(biāo)準(zhǔn)化后,組成特征向量矩陣W
對(duì)樣本集中的每一個(gè)樣本x(i),轉(zhuǎn)化為新的樣本
得到輸出樣本集D′=(z(1),z(2),...,z(m))
現(xiàn)在主流的做法其實(shí)是使用SVD(奇異值分解)來求出主成分,因?yàn)楫?dāng)樣本數(shù)和樣本特征數(shù)都較多的時(shí)候,計(jì)算協(xié)方差矩陣的計(jì)算量會(huì)很大。SVD在不求解協(xié)方差矩陣的情況下,得到右奇異矩陣V。也就是說PCA算法可以不用做特征分解,而是做SVD來完成,這個(gè)方法在樣本量很大的時(shí)候很有效。具體的求法可以參考:SVD與PCA之間的關(guān)系詳解
四、機(jī)器學(xué)習(xí)實(shí)戰(zhàn)中的代碼
#coding= utf-8
import numpy as np
def loadDataSet(fileName, delim='\t'):
fr = open(fileName)
stringArr = [line.strip().split(delim) for line in fr.readlines()]
datArr = [map(float,line) for line in stringArr]
return datArr
#利用協(xié)方差最大的方向進(jìn)行降維
#:數(shù)據(jù)集;降維后的維度
#r :利用降維后的矩陣反構(gòu)出原數(shù)據(jù)矩陣;降維后的數(shù)據(jù)
def pca(dataMat,topNfeat=9999999):
dataMat = np.mat(dataMat)
meanVals = np.mean(dataMat,axis=0)
meanRemoved = dataMat - meanVals
covMat = np.cov(meanRemoved,rowvar=0) #得到協(xié)方差矩陣
eigVals,eigVects = np.linalg.eig(np.mat(covMat)) #得到基于協(xié)方差矩陣的特征值和特征向量(一列表示一個(gè)特征向量)
eigValInd = np.argsort(eigVals) #對(duì)特征值進(jìn)行排序(從小到大),返回其對(duì)應(yīng)的數(shù)組索引(按從小到大順序)
eigValInd = eigValInd[:-(topNfeat + 1):-1] #從排序后的矩陣最后一個(gè)開始自下而上選取最大的N個(gè)特征值,返回其對(duì)應(yīng)的數(shù)組索引
redEigVects = eigVects[:,eigValInd] #從特征向量數(shù)組中取出特征值較大的特征向量組成數(shù)組
lowDDataMat = meanRemoved * redEigVects #將去除均值后的數(shù)據(jù)矩陣*壓縮矩陣,轉(zhuǎn)換到新的空間,使維度降低為N
# 利用降維后的矩陣反構(gòu)出原數(shù)據(jù)矩陣(用作測(cè)試,可跟未壓縮的原矩陣比對(duì))
reconMat = (lowDDataMat * redEigVects.T) + meanVals
# 返回壓縮后的數(shù)據(jù)矩陣即該矩陣反構(gòu)出原始數(shù)據(jù)矩陣
return lowDDataMat,reconMat
import matplotlib
import matplotlib.pyplot as plt
def Pcaplot(dataMat,reconMat):
dataMat = np.mat(dataMat)
fig=plt.figure()
ax=fig.add_subplot(111)
#三角形表示原始數(shù)據(jù)點(diǎn)
ax.scatter(dataMat[:,0].flatten().A[0],dataMat[:,1].flatten().A[0],marker='^',s=90)
#圓形點(diǎn)表示第一主成分點(diǎn),點(diǎn)顏色為紅色
ax.scatter(reconMat[:,0].flatten().A[0],reconMat[:,1].flatten().A[0], marker='o',s=90,c='red')
plt.show()
五、sklearn實(shí)現(xiàn)
class sklearn.decomposition.PCA(n_components=None, copy=True, whiten=False, svd_solver='auto', tol=0.0, iterated_power='auto', random_state=None)
Parameters:
n_components : 其取不同類型時(shí)的意義不一樣,
默認(rèn)為n_components=min(樣本數(shù),特征數(shù))
int 類型:直接指定降維到的維度數(shù)目
float 類型:主成分的方差和所占的最小比例閾值,讓PCA類自己去根據(jù)樣本特征方差來決定降維到的維度數(shù)
str 類型:參數(shù)設(shè)置為'mle'(極大似然估計(jì)), 此時(shí)PCA類會(huì)用MLE算法根據(jù)特征的方差分布情況自己去選擇一定數(shù)量的主成分特征來降維copy : bool類型,默認(rèn)值為True
表示是否在運(yùn)行算法時(shí),將原始數(shù)據(jù)復(fù)制一份。默認(rèn)為True,則運(yùn)行PCA算法后,原始數(shù)據(jù)的值不會(huì)有任何改變。whiten : bool類型,默認(rèn)值為False
白化,就是對(duì)降維后的數(shù)據(jù)的每個(gè)特征進(jìn)行標(biāo)準(zhǔn)化,讓方差都為1。對(duì)于PCA降維本身來說,一般不需要白化。如果你PCA降維后有后續(xù)的數(shù)據(jù)處理動(dòng)作,可以考慮白化svd_solver : string類型,默認(rèn)值為'auto'
'auto': PCA類會(huì)自己去在前面講到的三種算法里面去權(quán)衡,選擇一個(gè)合適的SVD算法來降維
'full' :則是傳統(tǒng)意義上的SVD,使用了scipy庫對(duì)應(yīng)的實(shí)現(xiàn)
'randomized' 一般適用于數(shù)據(jù)量大,數(shù)據(jù)維度多同時(shí)主成分?jǐn)?shù)目比例又較低的PCA降維,它使用了一些加快SVD的隨機(jī)算法
'arpack' :和randomized的適用場(chǎng)景類似,區(qū)別是randomized使用的是scikit-learn自己的SVD實(shí)現(xiàn),而arpack直接使用了scipy庫的sparse SVD實(shí)現(xiàn)。當(dāng)svd_solve設(shè)置為'arpack'時(shí),保留的成分必須少于特征數(shù),即不能保留所有成分
tol : float類型,默認(rèn)值為0
當(dāng)svd_solver選擇‘a(chǎn)rpack’時(shí),其運(yùn)行SVD算法的容錯(cuò)率iterated_power : int類型或者str類型,默認(rèn)值為‘a(chǎn)uto’
當(dāng)svd_solver選擇‘randomized’時(shí),其運(yùn)行SVD算法的的迭代次數(shù)random_state: int類型,默認(rèn)為None
偽隨機(jī)數(shù)發(fā)生器的種子,在混洗數(shù)據(jù)時(shí)用于概率估計(jì)
Attributes:
- components_: 主成分
- explained_variance_:降維后的各主成分的方差值
- explained_variance_ratio_:降維后的各主成分的方差值占總方差值的比例
- singular_values_:對(duì)應(yīng)的每個(gè)成分的奇異值
- mean_: X.mean(axis = 1)
- n_components_:主成分的個(gè)數(shù)
- noise_variance_:可參考: met-mppca
import numpy as np
from sklearn.decomposition import PCA
X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
pca = PCA(n_components=2)
pca.fit(X)
>> PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
svd_solver='auto', tol=0.0, whiten=False)
X_pca = pca.transform(x)