PCA演算法的數學原理以及Python實現
部落格中的筆記:
降維當然意味著資訊的丟失,不過鑑於實際資料本身常常存在的相關性,我們可以想辦法在降維的同時將資訊的損失儘量降低。
根據相關性來講資訊的損失量降到最低
更正式的說,向量(x,y)實際上表示線性組合:
x(1,0)?+y(0,1)?
不難證明所有二維向量都可以表示為這樣的線性組合。此處(1,0)和(0,1)叫做二維空間中的一組基。
一般的,如果我們有M個N維向量,想將其變換為由R個N維向量表示的新空間中,那麼首先將R個基按行組成矩陣A,然後將向量按列組成矩陣B,那麼兩矩陣的乘積AB就是變換結果,其中AB的第m列為A中第m列變換後的結果。
協方差(Covariance)在概率論
那麼如何選擇這個方向(或者說基)才能儘量保留最多的原始資訊呢?一種直觀的看法是:希望投影后的投影值儘可能分散。
方差
上面的問題(儘可能投影的分散)被形式化表述為:尋找一個一維基,使得所有資料變換為這個基上的座標表示後,方差值最大。
協方差
從直觀上說,讓兩個欄位儘可能表示更多的原始資訊,我們是不希望它們之間存在(線性)相關性的,因為相關性意味著兩個欄位不是完全獨立,必然存在重複表示的資訊。
數學上可以用兩個欄位的協方差表示其相關性,由於已經讓每個欄位均值為0,則:
Cov(a,b)=1m∑i=1maibi
當協方差為0時,表示兩個欄位完全獨立。為了讓協方差為0,我們選擇第二個基時只能在與第一個基正交的方向上選擇。因此最終選擇的兩個方向一定是正交的
至此,我們得到了降維問題的優化目標:將一組N維向量降為K維(K大於0,小於N),其目標是選擇K個單位(模為1)正交基,使得原始資料變換到這組基上後,各欄位兩兩間協方差為0,而欄位的方差則儘可能大(在正交的約束下,取最大的K個方差
)。
協方差矩陣求法:
接下來講解一下我對於PCA演算法的理解:
協方差矩陣對角化
現在事情很明白了!我們要找的P不是別的,而是能讓原始協方差矩陣對角化的P。換句話說,優化目標變成了尋找一個矩陣P,滿足PCP?
協方差矩陣C是一個是對稱矩陣,線上性代數上,實對稱矩陣有一系列非常好的性質:
1)實對稱矩陣不同特徵值對應的特徵向量必然正交。
2)設特徵向量λ
重數為r,則必然存在r個線性無關的特徵向量對應於λ,因此可以將這r個特徵向量單位正交化。
PCA演算法
總結一下PCA的演算法步驟:
設有m條n維資料。
1)將原始資料按列組成n行m列矩陣X
2)將X的每一行(代表一個屬性欄位)進行零均值化,即減去這一行的均值
3)求出協方差矩陣C=1mXX?
4)求出協方差矩陣的特徵值及對應的特徵向量
5)將特徵向量按對應特徵值大小從上到下按行排列成矩陣,取前k行組成矩陣P
6)Y=PX
即為降維到k維後的資料
PCA本質上是將方差最大的方向作為主要特徵,並且在各個正交方向上將資料“離相關”,也就是讓它們在不同正交方向上沒有相關性。
Python程式碼:
''' @author: Garvin ''' from numpy import * import matplotlib.pyplot as plt def loadDataSet(fileName, delim='\t'): fr = open(fileName) dataArr=[] for line in fr.readlines(): stringArr= line.strip().split(delim) colArr=[] for numString in stringArr: colArr.append(float(numString)) dataArr.append(colArr) return mat(dataArr) def pca(dataMat, topNfeat=9999999): meanVals = mean(dataMat, axis=0)#獲取平均值。 meanRemoved = dataMat - meanVals #remove mean covMat = cov(meanRemoved, rowvar=0)#協方差矩陣 eigVals,eigVects = linalg.eig(mat(covMat))#求特徵值,特徵向量 eigValInd = argsort(eigVals) #特徵值排序 #sort, sort goes smallest to largest print(eigValInd) eigValInd = eigValInd[:-(topNfeat+1):-1] #cut off unwanted dimensions減掉不想要的特徵值 print(eigValInd) redEigVects = eigVects[:,eigValInd] #識別最大到最小的特徵向量 #reorganize eig vects largest to smallest lowDDataMat = meanRemoved * redEigVects #將資料轉到新的維度中#transform data into new dimensions reconMat = (lowDDataMat * redEigVects.T) + meanVals return lowDDataMat, reconMat #轉化回來的矩陣,以及最終的降維矩陣 ##畫出最合適的方向。 def plotBestFit(dataSet1,dataSet2): dataArr1 = array(dataSet1) dataArr2 = array(dataSet2) n = shape(dataArr1)[0] n1=shape(dataArr2)[0] xcord1 = []; ycord1 = [] xcord2 = []; ycord2 = [] xcord3=[];ycord3=[] j=0 for i in range(n): xcord1.append(dataArr1[i,0]); ycord1.append(dataArr1[i,1]) xcord2.append(dataArr2[i,0]); ycord2.append(dataArr2[i,1]) fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(xcord1, ycord1, s=30, c='red', marker='s') ax.scatter(xcord2, ycord2, s=30, c='green') plt.xlabel('X1'); plt.ylabel('X2'); plt.show() if __name__=='__main__': mata=loadDataSet('testSet.txt') #print(mata) a,b= pca(mata, 2) plotBestFit(a,b)