主成分分析
優點:降低數據的複雜性,識別最重要的多個特徵。 缺點:不一定需要,且可能損失有用信息。 適用數據類型:數值型數據。
首先我們討論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 原始數據集(三角形點表示)及第一主成分(圓形點表示)