讀古今文學網 > 機器學習實戰 > 13.2 PCA >

13.2 PCA

主成分分析

優點:降低數據的複雜性,識別最重要的多個特徵。 缺點:不一定需要,且可能損失有用信息。 適用數據類型:數值型數據。

首先我們討論PCA背後的一些理論知識,然後介紹如何通過Python的NumPy來實現PCA。

13.2.1 移動坐標軸

考慮一下圖13-1中的大量數據點。如果要求我們畫出一條直線,這條線要盡可能覆蓋這些點,那麼最長的線可能是哪條?我做過多次嘗試。在圖13-1中,3條直線中B最長。在PCA中,我們對數據的坐標進行了旋轉,該旋轉的過程取決於數據的本身。第一條坐標軸旋轉到覆蓋數據的最大方差位置,即圖中的直線B。數據的最大方差給出了數據的最重要的信息。

在選擇了覆蓋數據最大差異性的坐標軸之後,我們選擇了第二條坐標軸。假如該坐標軸與第一條坐標軸垂直,它就是覆蓋數據次大差異性的坐標軸。這裡更嚴謹的說法就是正交(orthogonal)。當然,在二維平面下,垂直和正交是一回事。在圖13-1中,直線C就是第二條坐標軸。利用PCA,我們將數據坐標軸旋轉至數據角度上的那些最重要的方向。

圖13-1 覆蓋整個數據集的三條直線,其中直線B最長,並給出了數據集中差異化最大的方向

我們已經實現了坐標軸的旋轉,接下來開始討論降維。坐標軸的旋轉並沒有減少數據的維度。考慮圖13-2,其中包含著3個不同的類別。要區分這3個類別,可以使用決策樹。我們還記得決策樹每次都是基於一個特徵來做決策的。我們會發現,在x軸上可以找到一些值,這些值能夠很好地將這3個類別分開。這樣,我們就可能得到一些規則,比如當 (X<4)時,數據屬於類別0。如果使用SVM這樣稍微複雜一點的分類器,我們就會得到更好的分類面和分類規則,比如當(w0*x + w1*y + b) > 0時,數據也屬於類別0。SVM可能比決策樹得到更好的分類間隔,但是分類超平面卻很難解釋。

通過PCA進行降維處理,我們就可以同時獲得SVM和決策樹的優點:一方面,得到了和決策樹一樣簡單的分類器,同時分類間隔和SVM一樣好。考察圖13-2中下面的圖,其中的數據來自於上面的圖並經PCA轉換之後繪製而成的。如果僅使用原始數據,那麼這裡的間隔會比決策樹的間隔更大。另外,由於只需要考慮一維信息,因此數據就可以通過比SVM簡單得多的很容易採用的規則進行區分。

圖13-2 二維空間的3個類別。當在該數據集上應用PCA時,就可以去掉一維,從而使得該分類問題變得更容易處理

在圖13-2中,我們只需要一維信息即可,因為另一維信息只是對分類缺乏貢獻的噪聲數據。在二維平面下,這一點看上去微不足道,但是如果在高維空間下則意義重大。

我們已經對PCA的基本過程做出了簡單的闡述,接下來就可以通過代碼來實現PCA過程。前面我曾提到的第一個主成分就是從數據差異性最大(即方差最大)的方向提取出來的,第二個主成分則來自於數據差異性次大的方向,並且該方向與第一個主成分方向正交。通過數據集的協方差矩陣及其特徵值分析,我們就可以求得這些主成分的值。

一旦得到了協方差矩陣的特徵向量,我們就可以保留最大的N個值。這些特徵向量也給出了N個最重要特徵的真實結構。我們可以通過將數據乘上這N個特徵向量而將它轉換到新的空間。

特徵值分析

特徵值分析是線性代數中的一個領域,它能夠通過數據的一般格式來揭示數據的「真實」結構,即我們常說的特徵向量和特徵值。在等式Av = λv中,v 是特徵向量, λ是特徵值。特徵值都是簡單的標量值,因此Av = λv代表的是:如果特徵向量v被某個矩陣A左乘,那麼它就等於某個標量λ乘以v。幸運的是,NumPy中有尋找特徵向量和特徵值的模塊linalg,它有eig方法,該方法用於求解特徵向量和特徵值。

13.2.2 在NumPy中實現PCA

將數據轉換成前N個主成分的偽碼大致如下:

去除平均值  
計算協方差矩陣  
計算協方差矩陣的特徵值和特徵向量  
將特徵值從大到小排序  
保留最上面的N個特徵向量  
將數據轉換到上述N個特徵向量構建的新空間中  
 

建立一個名為pca.py的文件並將下列代碼加入用於計算PCA。

程序清單13-1 PCA算法

from numpy import *

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 mat(datArr)

def pca(dataMat, topNfeat=9999999):
    meanVals = mean(dataMat, axis=0)
    #❶ 去平均值 
    meanRemoved = dataMat - meanVals
    covMat = cov(meanRemoved, rowvar=0)
    eigVals,eigVects = linalg.eig(mat(covMat))
    eigValInd = argsort(eigVals)
    #❷ 從小到大對N個值排序
    eigValInd = eigValInd[:-(topNfeat+1):-1]
    redEigVects = eigVects[:,eigValInd]
    #❸ 將數據轉換到新空間
    lowDDataMat = meanRemoved * redEigVects
    reconMat = (lowDDataMat * redEigVects.T) + meanVals
    return lowDDataMat, reconMat
  

程序清單13-1中的代碼包含了通常的NumPy導入和loadDataSet函數。這裡的loadDataSet函數和前面章節中的版本有所不同,因為這裡使用了兩個list comprehension來構建矩陣。

pca函數有兩個參數:第一個參數是用於進行PCA操作的數據集,第二個參數topNfeat則是一個可選參數,即應用的N個特徵。如果不指定topNfeat的值,那麼函數就會返回前9 999 999個特徵,或者原始數據中全部的特徵。

首先計算並減去原始數據集的平均值❶。然後,計算協方差矩陣及其特徵值,接著利用argsort函數對特徵值進行從小到大的排序。根據特徵值排序結果的逆序就可以得到topNfeat個最大的特徵向量❷。這些特徵向量將構成後面對數據進行轉換的矩陣,該矩陣則利用N個特徵將原始數據轉換到新空間中❸。最後,原始數據被重構後返回用於調試,同時降維之後的數據集也被返回了。

一切都看上去不錯,是不是?在進入規模更大的例子之前,我們先看看上面代碼的運行效果以確保其結果正確無誤。

>>> import pca
  

我們在testSet.txt文件中加入一個由1000個數據點組成的數據集,並通過如下命令將該數據集調入內存:

>>> dataMat = pca.loadDataSet(\'testSet.txt\')   
  

於是,我們就可以在該數據集上進行PCA操作:

>>> lowDMat, reconMat = pca.pca(dataMat, 1)
  

lowDMat包含了降維之後的矩陣,這裡是個一維矩陣,我們通過如下命令進行檢查:

>>> shape(lowDMat)
(1000, 1)  
  

我們可以通過如下命令將降維後的數據和原始數據一起繪製出來:

>>> import matplotlib
>>>import matplotlib.pyplot as plt
fig = plt.figure
ax = fig.add_subplot(111)
>>> ax.scatter(dataMat[:,0].flatten.A[0], dataMat[:,1].flatten.A[0],marker=\'^\', s=90)
<matplotlib.collections.PathCollection object at 0x029B5C50>
>>> ax.scatter(reconMat[:,0].flatten.A[0], reconMat[:,1].flatten.A[0],marker=\'o\', s=50, c=\'red\')
<matplotlib.collections.PathCollection object at 0x0372A210>plt.show  
  

我們應該會看到和圖13-3類似的結果。使用如下命令來替換原來的PCA調用,並重複上述過程:

>>> lowDMat, reconMat = pca.pca(dataMat, 2) 
  

既然沒有剔除任何特徵,那麼重構之後的數據會和原始的數據重合。我們也會看到和圖13-3類似的結果(不包含圖13-3中的直線)。

圖13-3 原始數據集(三角形點表示)及第一主成分(圓形點表示)