特徵值與特徵向量
矩陣乘法對應一個線性變換,把輸入的任意一個向量,變成另一個方向或長度都改變的新向量,在這個變換的過程中,原來的向量主要發生了旋轉、伸縮的變換。
特別情況,存在某些向量使得變換矩陣作用於 TA 們時,只對長度做了伸縮的變換,卻不對輸入向量產生旋轉的變換(不改變原來向量的方向),這些向量就稱爲這個矩陣的特徵向量,伸縮的比例就叫特徵值。
如上圖所示,輸出向量只是輸入向量的 倍,只是改變了長度(模)。
- 在數學上,稱爲 矩陣 的【特徵值】;
- 輸出向量 在數學上,稱爲 矩陣 的特徵值對應的【特徵向量】。
本科的線性代數主要研究的是方陣(行數等於列數),【特徵值】、【特徵向量】也是隻有方陣纔有的。
舉個例子。
可以把 提取出來,變成:
就是 矩陣 的特徵值, 是 矩陣 的特徵向量。
矩陣 不變,如果把 改成 代進入算,發現都是 矩陣 特徵值 的特徵向量。
只是通過一個 特徵值 ,就找到了好多特徵向量,這是爲什麼呢?
特徵值 對應的這些特徵向量是有規律的,呈比例,在幾何表示上,只是伸縮的比例不同。
所以說,矩陣 任意一個特徵值對應無數多個特徵向量。
要求某一個特徵值的某一個特徵向量,只要找到這個特徵值對應的一個特徵向量即可。
比如,,其 TA 的特徵向量都等於 ( 爲非零常數)。
特徵值、特徵向量是把矩陣看成一種對向量的變換來看方陣,當我們把方陣理解爲是一個變換的時候,這個變換擁有一些特徵,這些特徵會被特徵值、特徵向量表徵出來。
我們可以用 python
算矩陣的特徵值、特徵向量。
import numpy as np
A = np.array([[5, 1],
[3, 3]])
V_eig = np.linalg.eig(A);
print("eigen value:> ", V_eig[0]); # 特徵值
print("eigen vector:> \n", V_eig[1]); # 特徵值
運行結果:
eigen value:> [6. 2.]
eigen vector:>
[[ 0.70710678 -0.31622777]
[ 0.70710678 0.9486833 ]]
矩陣乘法是一種線性變換,變換矩陣定義了變換的規則,也就是定義了要往哪些方向伸縮或旋轉(即特徵向量),伸縮或旋轉的程度是多少(即特徵值)。
求矩陣的特徵值和特徵向量,相當於找出變換規則中的變化方向。
變換方向找到後,也就能把變換過程描述清楚。
特徵值分解
那會計算矩陣的特徵值、特徵向量有什麼作用嗎?
可對矩陣做分解。
以 矩陣 爲例,,將 分解成 個矩陣的乘積。
矩陣分解的步驟:
-
求得矩陣的特徵值和對應的特徵向量;
特徵值 對應的特徵向量 ,特徵值 對應的特徵向量 。
-
將這些特徵向量,橫向拼成一個矩陣;
相當於倆個列向量拼在一起,左邊的列向量是特徵值 對應的特徵向量,右邊的列向量是特徵值 對應的特徵向量。
-
按照順序,將特徵值拼成一個對角矩陣;
-
求出矩陣 的逆;
import numpy as np P = np.array([[1, 1], [1, -3]]) P_inv = np.linalg.inv(P) # 求逆 print("P_inv:> \n", P_inv)
運行結果:
P_inv:> [[ 0.75 0.25] [ 0.25 -0.25]]
-
最後,從左到右乘一起。
算來算去,最後發現結果等於變換 矩陣!!
當然,這不是巧合。這告訴我們,一個方陣如果按照上面的步驟分解,就可以分解爲 個矩陣的乘積(),因爲用到了特徵值,所以我們把這種分解方法稱爲【特徵值分解】。
特徵值分解:.
調換一下, 的位置,就是另外一個術語啦,這倆是等價的。
矩陣對角化:。
實對稱矩陣
矩陣可以通過求特徵值、特徵向量分解爲 個矩陣的乘積,特別的, 爲實對稱矩陣,有很重要的性質。
什麼是實對稱矩陣?
實對稱矩陣:矩陣等於TA的轉置
比如:
實對稱矩陣的性質:
- 性質:一定可以做特徵值分解:;
- 性質:不同特徵值對應的特徵向量必然正交(正交:內積爲 )
比如:
- 的特徵值和特徵向量 —
倆組特徵值和特徵向量滿足 性質,又因爲 (倆組特徵值不相等),所以說倆組特徵向量的內積爲 。
根據 性質 將 矩陣 做特徵值分解:
發現 逆就是 的轉置( )。
如果一個矩陣的逆等於這個矩陣的轉置,就是正交矩陣(滿足 )。
所以,這種情況下 也可以寫成 .
因爲 矩陣的轉置 比 矩陣的逆 好算得多。
再來看一個三階方陣:
矩陣等於TA的轉置 ,所以這也是一個實對稱矩陣。
而後呢,TA 有 組特徵值和特徵向量:
這個矩陣比較怪,因爲第 個特徵值是一樣噠,根據 性質(不同特徵值對應第特徵向量正交)來看,那 TA 的特徵向量就不一定正交了。
不能正交,就不能用 來分解 矩陣,所以就不能用矩陣的轉置代替求矩陣的逆。
後來,人們提供了一種【施密特正交化】方法,將重複特徵值不正交的特徵向量變成正交,而後再把這些特徵向量【單位化】,就變成了倆倆正交的單位向量,再把他們拼起來就是正交矩陣!
這樣就滿足了 .
工程應用:主成分分析
矩陣的特徵值分解,在工程上也有很廣泛的應用,比如主成分分析算法(Principal Component Analysis)。
主成分分析算法:通過線性變換,將原始數據變換爲一組各維度線性無關的表示,可以用來提取數據的主要數據分量,多用於高維數據的降維。
這種降維的算法,在儘可能保留原有信息前提下,將一組 維 降成 維 (),可以說就是刪除重複、相關性強的數據,所以有許多問題需要考慮:
- 刪除哪個維度,信息損失最小?
- 通過怎樣的變換對原始數據降維,信息損失最小?
- 信息損失最小,如何度量?
那有這樣的算法嗎?
有的,這個算法就是 啊,具體的算法步驟說一個 維 降成 維的栗子。
幾何表示爲:
爲了後續處理方便,對每個維度去【均值】,讓每個維度均值都爲 。
去均值:每一個維度(行)的每一個值,都減去該維度的均值。
比如第一維(第一行)的均值是 :
-
去均值前:
-
去均值後:
整體去均值後:
去均值後,幾何表示爲:
去均值讓數據平移:
發現數據點在這個方向👇最分散(方差最大):
如果將原始數據投影到這個方向上,就可以將數據的二維表達降至 維,也儘可能的保留了原始信息 — 方差最大的方向,最大限度的保留了數據與數據之間的差異性。
維 降到 維的步驟:
-
找一個方差最大的方向;
-
原數據點往該方向投影,得到一維表達。
-
但計算機又沒有眼睛,怎麼知道完成這倆個步驟呢?
我們再來看一個 維 降爲 維 的栗子。
假設我們找到了方差最大的方向,在把原始的數據點往這個方向投影。
如果是降到 維,這樣就已經搞定了 — 但我們是要降到 維,所以還需要找第二個方向,第二個方向要與第一個方向正交。
因爲倆個方向正交,意味着倆個方向完全獨立、完全無關,最有效的表示信息。
但有無數個方向與第一個方向正交,到底選哪一個呢?
條件就是方差,在正交的方向中選擇方差最大的方向。
而後投影到這倆個方向所代表的平面。
維 降到 維的步驟:
- 找一個方差最大的方向,作爲第一個方向(第一維);
- 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第二個方向(第二維);
- 原數據點往該方向投影,得到二維表達。
一般化,將一組 維 降成 維 ():
- 找到方差最大的方向,作爲第一個方向(第一維);
- 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第二個方向(第二維);
- 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第三個方向(第三維);
- … …
- 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第 個方向(第 維);
降維的思考過程就是這樣,而後就是把這些過程給數學化,數學化的這個算法就是 算法。
以 維 降到 維 爲栗子,第一、二步用數學表達爲:
- 在三維空間中,找到一個二維標準正交基,使得原始數據投影到這組新基上時,各維度方差必須儘可能大。
倆個條件:
- 投影方向是正交基;
- 正交基約束下,各維度的方差要儘可能大。
數學推導:主成分分析算法
主成分分析的數學推導,本文最複雜的地方。
從方差說起。
- 方差:
:這個維度的每一個值
:均值
:樣本總數
因爲在降維前,將每一個維度的數據都去均值(均值變爲 ,數據在原點周圍),所以每一個維度的數據方差就可簡化爲:
- 去均值方差:
去均值後,大大的簡化了計算量。
另外,數學上用協方差來表示數據維度之間的相關性。
- 協方差:
去均值後,倆個維度之間的協方差就可以簡化爲:
- 去均值協方差:
如果倆個維度之間的協方差爲 ,代表這倆個維度完全無關,正是 算法需要的。
觀察方差、協方差的表達式,方差就是向量自身的內積(相乘再相加),只不過在內積的前面乘了 ,協方差就是倆個向量的內積,也是前面多乘了一個 。
假設原始數據矩陣 :
每條數據都有 三個維度,總共 條數據。
我們把 矩陣 轉置一下:
再將 。
這是一個矩陣乘法,再給這個結果乘以 。
得出的矩陣,TA的主對角線元素爲各維度方差,其他元素爲各維度間協方差,而且這個矩陣是實對稱矩陣,這樣的矩陣稱爲【協方差矩陣】。
上述的推導過程:
基於現在掌握的知識,將一組 維 降成 維 ()的數學任務,就可以做進一步的數學描述。
- 在 維空間中,找到一個 維標準正交基(),使得原始數據投影到這組新基上時,數據協方差矩陣主對角線原始儘可能大,其他元素爲 。
降維任務的描述越來越數學化,但還是沒有找到合適的、最關鍵的降維工具。
記得,我們可以把矩陣看成一種變換,但只是換了基底,並沒有降維。
- 原始數據協方差矩陣
因爲這個矩陣:
- 主對角線元素爲各維度的方差,不全爲零;
- 其他元素爲各維度的協方差,全爲零。
我們要求協方差爲零,相當於要求經過變換後新的基底是標準正交基。
這樣就使得協方差矩陣發生了對角化… …
變換的公式爲:。
把經過變換的協方差矩陣稱爲:。
我們把變換的公式()代進入,
令 ,那這個公式就變成了,
這說明,要想使得數據的協方差矩陣對角化爲,就要求出 的特徵值和特徵向量。
的特徵值,其實就是新基底下各維度的數據方差,如果把這些方差排序好,拼成對角矩陣,又因爲 ,所以就可以把新的特徵向量以行向量的形式,縱向拼成一個矩陣。
最後,取 的前 個行向量,構成一個新的矩陣 ,這 個行向量就是我們要找的 個正交的基向量,這 個正交的基向量就構成了一個 維的標準的正交的基。
而 維的標準的正交的基,就是我們的目標。
原始數據矩陣,通過 維的標準的正交的基投影,就在變換時降成 維了。
至此, 算法已經設計完畢~
基於上述的討論,將一組 維 降成 維 ()的任務,就可以描述爲:
- 求一個變換矩陣,當 時,有 ,取 的前 行構成矩陣,則 即降維後的數據。
算法步驟:
- 求原始數據協方差矩陣的特徵值和特徵向量;
- 將特徵值(方差)排好序,從上到下拼成矩陣;
- 將特徵向量從上到下,遵循特徵值順序縱向拼成矩陣;
- 取出 中前 行構成降維矩陣,作用於原始數據,即完成降維。
舉個荔枝
任務:將二維數據降到一維。
原始數據:
-
去均值
-
求協方差矩陣
-
求特徵值和特徵向量
倆組:
-
將特徵向量縱向拼成矩陣
-
取出 的第 行構成降維矩陣
-
將 作用於 (投影),得到一維表達
的本質是將方差最大的方向作爲主要特徵,並且在各個正交方向上將數據“離相關”,也就是讓他們在不同正交方向上沒有相關性。
只能解除數據的線性相關性,對於高階相關性不起作用,可以改進爲【核PCA】,通過核函數將非線性相關轉爲線性相關,再使用 。
是一種無參技術,即沒有主觀參數的介入,所以 作爲一種通用實現,本身無法做個性優化。
import numpy as np
import matplotlib.pyplot as plt
def myPCA(dataMat, K=2): # K爲目標維度
# 2. 對數據矩陣去均值
meanVals = np.mean(dataMat, axis=1) #axis=0,求列的均值,axis=1,求行的均值
meanRemoved = dataMat - meanVals #python的廣播機制
# 3. 計算協方差矩陣
# rowvar:默認爲True,此時每一行代表一個維度,每一列代表一條數據;爲False時,則反之。
# bias:默認爲False,此時標準化時除以m-1(無偏估計);反之爲m(有偏估計)。其中m爲數據總數。
covMat = np.cov(meanRemoved,rowvar=True,bias=True)
print("\n-----covMat-----\n",covMat)
# 4. 計算協方差矩陣的特徵值和特徵向量
eigVals, eigVects = np.linalg.eig(np.mat(covMat))
print("-----eigVals-----\n",eigVals)
print("-----eigVects-----\n",eigVects)
# 5. 將特徵值由小到大排序,並返回對應的索引
eigValInd = np.argsort(eigVals) #argsort函數返回的是數組值從小到大的索引 [ 2. 0.4] -> [ 0.4. 2] ->
print("-----eigValInd 1-----\n",eigValInd)
# 6. 根據eigValInd後K個特徵值索引,找出eigVects中對應的特徵向量,拼成降維矩陣
eigValInd = eigValInd[-1:-(K+1):-1]
print("-----eigValInd 2-----\n",eigValInd)
redEigVects=eigVects[:,eigValInd].T #取出特徵向量(即降維矩陣)
print("-----redEigVects-----\n",redEigVects)
# 7. 執行降維操作(矩陣乘法)
lowDMat = np.dot(redEigVects , meanRemoved)
# 8. 重構數據矩陣
resturctMat = np.dot(redEigVects.I , lowDMat) + meanVals
return lowDMat,resturctMat
if __name__ == '__main__':
dataMat = np.mat([[1,1,2,2,4],
[1,3,3,4,4]])
lowDMat , resturctMat = myPCA(dataMat,K=1)
print("-----lowDmat-----\n",lowDMat)
print("-----resturctMat-----\n",resturctMat)
# 繪製數據點
fig = plt.figure()
plt.xlim(-1, 5)
plt.ylim(-1, 5)
plt.grid(color='gray')
plt.axvline(x=0, color='gray')
plt.axhline(y=0, color='gray')
ax = fig.add_subplot(111)
ax.scatter(dataMat[0, :].flatten().A[0], dataMat[1, :].flatten().A[0], marker='*', s=300, c='red') #描原數據點
ax.scatter(resturctMat[0, :].flatten().A[0], resturctMat[1, :].flatten().A[0], marker='o', s=50, c='green') #描重建數據點
plt.show()
工程應用:人臉識別
下圖中,有兩張照片是同一個人的:
對於這個問題,人是很容易分辨出來的,但計算機應該怎麼辦呢?
其中一種方法就是將之線性化。首先,給出此人更多的照片:
將其中某張照片分爲眼、鼻、嘴三個部位,這是人臉最重要的三個部位。通過某種算法,可以用三個實數來分別表示這三個部位,比如下圖得到的分別是、、:
將所有這些照片分別算出來,用三維座標來表示得到的結果,比如上圖得到的結果就是。
將這些三維座標用點標註在直角座標系中,發現這些點都落在某平面上,或該平面的附近。因此,可認爲此人的臉線性化爲了該平面。
將人臉線性化爲平面後,再給出一張新的照片,按照剛纔的方法算出這張照片的三維座標,發現不在平面上或者平面附近,就可以判斷不是此人的照片:
將人臉識別這種複雜的問題線性化,就可以用使用線性代數解決TA。
線性代數要學習的內容就是 如何解決線性問題,而如何把 複雜問題線性化 是別的學科的內容,比如《微積分》、《信號與系統》、《概率與統計》等。
基於矩陣對角化理論的 算法在人臉識別上,能用更少的計算量取得不亞於卷積神經網絡的性能。
數據集:https://download.csdn.net/download/qq_41739364/12575178(可下載)
數據集裏有 個人,每個都有 張照片,分別存儲在 個文件夾裏,,每個文件夾下面都有 張 照片,每張照片的尺寸 (長 * 寬)。
把數據集,分成:
- 訓練集:50%
- 測試集:50%
def loadDataSet(m):
dataSetDir = input('att_faces path:>' ) # 輸入訓練集文件位置
train_face = np.zeros((40*m,112*92)) # 訓練集矩陣,40個人,取前 m 張照片;每張照片有 112*92 個維度
train_face_number = np.zeros(40*m)
test_face = np.zeros((40*(10-m),112*92)) # 測試集矩陣,40個人,取後 m 張照片;每張照片有 112*92 個維度
test_face_number = np.zeros(40*(10-m))
把訓練集送入 算法,構成人臉數據庫,同時也得到一個訓練集均值和降維矩陣。
降維矩陣,可以提取數據的主要信息,去除冗餘 — 所以也可以稱爲【主成分提取器】。
從測試集裏,隨機抽出一張照片,送入【主成分提取器】將該照片的主要信息提取出來,而後與數據庫中所有信息做比對檢索,計算歐式距離,找出最相似的一張照片,即完成我們的人臉識別。
上述架構:
代碼:
def myPCA(dataMat, K=2):
# 1. 對數據矩陣去均值
meanVals = np.mean(dataMat, axis=1) # axis=0,求列的均值,axis=1,求行的均值
meanRemoved = dataMat - meanVals
# 2. 計算協方差矩陣
covMat = np.cov(meanRemoved, rowvar=True, bias=True)
# 3. 計算協方差矩陣的特徵值和特徵向量
eigVals, eigVects = np.linalg.eig(np.mat(covMat))
# 4. 將特徵值由小到大排序,並返回對應的索引
eigValInd = np.argsort(eigVals)
# 5. 根據eigValInd後K個特徵值索引,找出eigVects中對應的特徵向量,拼成降維矩陣
eigValInd = eigValInd[-1:-(K+1):-1]
Extractor = eigVects[:,eigValInd].T # 取出特徵向量(降維矩陣)
# 6. 執行降維(矩陣乘法)
lowDMat = np.dot(Extractor, meanRemoved)
# 返回 降維後的數據、訓練集的均值、降維矩陣(主成分提取器)
return lowDMat, meanRemoved, Extractor
把 加進去,就是一份完整的代碼。
import os, cv2
# cv2 是 opencv-python 模塊
import numpy as np
import matplotlib.pyplot as plt
def myPCA(dataMat, K=2):
# 加進來吧!
def img2vector(filename):
img = cv2.imread(filename,0) # 讀取圖片
rows,cols = img.shape
imgVector = np.zeros((1,rows*cols))
imgVector = np.reshape(img,(1,rows*cols)) # 圖片2D -> 1D
return imgVector
def loadDataSet(m):
print ("--------- Getting data set ---------")
dataSetDir = input('att_faces path:>' ) # 輸入訓練集文件位置
train_face = np.zeros((40*m,112*92)) # 訓練集矩陣,40個人,取前 m 張照片;每張照片有 112*92 個維度
train_face_number = np.zeros(40*m)
test_face = np.zeros((40*(10-m),112*92)) # 測試集矩陣,40個人,取後 m 張照片;每張照片有 112*92 個維度
test_face_number = np.zeros(40*(10-m))
choose = np.array([1,2,3,4,5,6,7,8,9,10])
# 創建1->10的數組。(因爲圖片編號是1.bgm~10.bgm)
for i in range(40): # 總共有40個人
people_num = i+1 # 個體編號
for j in range(10): # 每個人都有10張臉部照片
if j < m: # 先從10張圖片中選m張作爲訓練集
filename = dataSetDir+'/s'+str(people_num)+'/'+str(choose[j])+'.pgm'
# 人臉數據的路徑,路徑是字符串類型,所以要把數字轉換爲字符串
img = img2vector(filename)
train_face[i*m+j,:] = img
train_face_number[i*m+j] = people_num #記錄這張圖片所屬之人
else: # 餘下10-m張作爲測試集
filename = dataSetDir+'/s'+str(people_num)+'/'+str(choose[j])+'.pgm'
img = img2vector(filename)
test_face[i*(10-m)+(j-m),:] = img
test_face_number[i*(10-m)+(j-m)] = people_num
return np.mat(train_face.T), train_face_number, np.mat(test_face.T), test_face_number
# 訓練集train_face、測試集test_face 做一個轉置.T,原來一個圖片對應一個行向量,現在每一列都代表一張圖片
if __name__=='__main__':
# 1 .從文件夾中加載圖片數據
train_face, train_face_number, test_face, test_face_number = loadDataSet(5)
print("train_face shape:", train_face.shape)
print("train_face_number shape:", train_face_number.shape, "\n", train_face_number)
print("test_face shape:", test_face.shape)
print("test_face_number shape:", test_face_number.shape, "\n" , test_face_number)
第 步,計算協方差矩陣的特徵值和特徵向量是最耗費計算資源的。
查看一下這個協方差矩陣:
print("covMat shape:> ", covMat.shape)
輸出:(10304, 10304)
每一個像素點都是一個維度,這麼大的協方差矩陣,協方差矩陣 非常大,計算TA的特徵值和特徵向量,這一天可能都算不出來。
爲了求大規模特徵值和特徵向量,其實有倆組方案:
- 安裝 這種組合庫,調包即可,計算一萬維大概是 秒左右;
- 通過某個小規模的矩陣來求(採用)。
數學推導:任何求大規模矩陣的特徵值?
現實生活裏,我們拍照都是高清配置,數據樣本的維度就很高,計算量也非常的大。
來看一段推導。
假設 爲 的數據矩陣,則對應的協方差矩陣: 爲 階方陣。
現在有一個矩陣:(和 相反), 爲 階方陣。
接着,由特徵值和特徵向量的定義可以得到:, 是 的特徵值, 是 關於 特徵值 的 特徵向量。
把 換成 的轉置,就有這樣一個式子:
等式左右倆邊,都乘以 :
等式變形:
因爲 是標量,可以提到等號左邊。
又因爲 就是協方差矩陣:
令
這個式子說明了,協方差矩陣 和 矩陣 的特徵值是一樣的。
所以只要求出矩陣 的特徵值就求出了 的特徵值,而協方差矩陣 的特徵向量,就是矩陣 的特徵向量 乘 。
這樣,求 階的矩陣 間接求出了 階矩陣,但矩陣的特徵值只有 個,而矩陣 有 個,也就是說,通過這種方式只能求出 矩陣 的前 個特徵值。
但沒關係,前 是方差最大的 個,足以反應整個矩陣了。
# 2. 計算協方差矩陣
covMat = np.cov(meanRemoved, rowvar=True, bias=True)
優化後:
# 2. 計算協方差矩陣(1/m 是一個標量所以不必加進來)
T = meanRemoved.T * meanRemoved # (200, 200)
print("T shape:", T.shape) # (10304, 10304)
特徵向量也需要改
# 5. 取出特徵向量(降維矩陣)
Extractor = eigVects[:,eigValInd].T
優化後:
# 5. auto爲真,自動計算目標維數K
if auto==True: # 是否自動計算目標維數
num = 0 # 需要保存的特徵值個數
for i in range(len(eigVals)):
if (np.linalg.norm(eigVals[:i + 1]) / np.linalg.norm(eigVals)) > 0.999: # 看看前i個特徵值有沒有達到總方差的99.9%
num = i + 1
break
print("The num:", num)
K = num
優化特徵值和特徵向量後:
import os, cv2
# cv2 是 opencv-python 模塊
import numpy as np
import matplotlib.pyplot as plt
def myPCA(dataMat, K=2, auto=False): # K爲目標維度
# 1. 對數據矩陣去均值
meanVals = np.mean(dataMat, axis=1) # axis=0,求列的均值,axis=1,求行的均值
meanRemoved = dataMat - meanVals
# 2. 計算協方差矩陣,1/m 是一個標量所以不必加進來
T = meanRemoved.T * meanRemoved # (200, 200)
print("T shape:", T.shape) # (10304, 10304)
# 3. 計算協方差矩陣的特徵值和特徵向量
# 計算大規模協方差矩陣的特徵值和特徵向量,有兩種方案:
# (1). 安裝“numpy+mkl”這種組合庫(調包)。
# (2). 通過某個小規模的矩陣來求(採用)。
eigVals, eigVects = np.linalg.eig(np.mat(T))
# 4. 將特徵值由小到大排序,並返回對應的索引
eigValInd = np.argsort(eigVals)
# 5. 自動計算目標維數K
if auto==True: # 是否自動計算目標維數
num = 0 # 需要保存的特徵值個數
for i in range(len(eigVals)):
if (np.linalg.norm(eigVals[:i + 1]) / np.linalg.norm(eigVals)) > 0.999: # 看看前i個特徵值有沒有達到總方差的99.9%
num = i + 1
break
print("The num:", num)
K = num
# 6. 根據eigValInd後K個特徵值索引,找出eigVects中對應的特徵向量,拼成降維矩陣
eigValInd = eigValInd[-1:-(K+1):-1]
Extractor0 = meanRemoved * eigVects[:,eigValInd] #求出協方差矩陣的前K個特徵向量(即降維矩陣)
# 7. 單位化特徵向量
for i in range(K):
Extractor0[:,i] = Extractor0[:,i]/np.linalg.norm(Extractor0[:,i])
# 8. 執行降維操作(矩陣乘法)
Extractor = Extractor0.T
lowDMat = np.dot(Extractor, meanRemoved)
# 返回 降維後的數據、訓練集的均值、降維矩陣(主成分提取器)
return lowDMat, meanVals, Extractor
def img2vector(filename):
img = cv2.imread(filename,0) # 讀取圖片
rows,cols = img.shape
imgVector = np.zeros((1,rows*cols))
imgVector = np.reshape(img,(1,rows*cols)) # 圖片2D -> 1D
return imgVector
def loadDataSet(m):
print ("--------- Getting data set ---------")
dataSetDir = input('att_faces path:>' ) # 輸入訓練集文件位置
train_face = np.zeros((40*m,112*92)) # 訓練集矩陣,40個人,取前 m 張照片;每張照片有 112*92 個維度
train_face_number = np.zeros(40*m)
test_face = np.zeros((40*(10-m),112*92)) # 測試集矩陣,40個人,取後 m 張照片;每張照片有 112*92 個維度
test_face_number = np.zeros(40*(10-m))
choose = np.array([1,2,3,4,5,6,7,8,9,10]) # 創建1->10的數組。(因爲圖片編號是1.bgm~10.bgm)
for i in range(40): # 總共有40個人
people_num = i+1 # 個體編號
for j in range(10): # 每個人都有10張臉部照片
if j < m: # 先從10張圖片中選m張作爲訓練集
filename = dataSetDir+'/s'+str(people_num)+'/'+str(choose[j])+'.pgm'
img = img2vector(filename)
train_face[i*m+j,:] = img
train_face_number[i*m+j] = people_num # 記錄這張圖片所屬之人
else: # 餘下10-m張作爲測試集
filename = dataSetDir+'/s'+str(people_num)+'/'+str(choose[j])+'.pgm'
img = img2vector(filename)
test_face[i*(10-m)+(j-m),:] = img
test_face_number[i*(10-m)+(j-m)] = people_num
return np.mat(train_face.T), train_face_number, np.mat(test_face.T), test_face_number
# 訓練集train_face、測試集test_face 做一個轉置.T,原來一個圖片對應一個行向量,現在每一列都代表一張圖片
if __name__=='__main__':
# 1. 從文件夾中加載圖片數據
train_face, train_face_number, test_face, test_face_number = loadDataSet(5)
print("train_face shape:", train_face.shape)
print("train_face_number shape:", train_face_number.shape, "\n", train_face_number)
print("test_face shape:", test_face.shape)
print("test_face_number shape:", test_face_number.shape, "\n", test_face_number)
# 2. 利用PCA進行降維(提取數據的主要信息)-> 利用訓練集訓練“主成分提取器”
DataBase, train_meanVals, Extractor= myPCA(train_face, 40, auto=True)
print("Train OK!!!")
# 3. 將主成分提取器作用於測試集圖片
test_face_new = Extractor * (test_face - train_meanVals)
# 4. 逐一遍歷測試集圖片,同時檢索數據庫,計算識別正確的個數
true_num = 0 # 正確個數
num_test = test_face.shape[1] # 測試集圖片數
for i in range(num_test):
testFace = test_face_new[:,i] # 取出一張測試圖片
EucDist = np.sqrt(np.sum(np.square(DataBase - testFace), axis=0))# 求出該測試圖片與數據庫中所有圖片的歐式距離
DistIdx = EucDist.argsort() # 距離由小到大排序,返回索引
idxMin = DistIdx[:,0] # 取出最小距離所在索引
if train_face_number[idxMin] == test_face_number[i]:
true_num += 1
accu = float(true_num)/num_test
print ('The classify accuracy is: %.2f%%' %(accu * 100))
# 顯示匹配成功率
輸出:
The classify accuracy is: 89.00%
裏面的 算法,是奇異值分解()實現的,因爲用數據矩陣的奇異值分解代替協方差矩陣的特徵值分解,速度更快。
馬爾可夫過程
您是否也有過這樣的感覺:不管付出了多少努力,事情總會回到老樣子,就好像冥冥之中有個無法擺脫的宿命一樣。
數學模型能告訴您其中的原理,這個數學模型就是馬爾可夫過程。
想要一次性地採取一個行動去改變某件事,結果徒勞無功,其實,這就是一個馬爾可夫過程,滿足馬爾可夫過程有四個條件。
- 第一,系統中存在有限多個狀態。
- 第二,狀態之間切換的概率是固定的。
- 第三,系統要具有遍歷性,也就是從任何一個狀態出發,都能找到一條路線,切換到任何一個其他的狀態。
- 第四,其中沒有循環的情況,不能說幾個狀態形成閉環,把其他狀態排斥在外。
舉個例子,某位老師,發現課堂上總有學生無法集中注意力,會溜號。
所謂馬爾可夫過程,就是假設學生在 “認真” 和 “溜號” 這兩個狀態之間的切換概率,是 固定的。
我們設定,今天認真聽講的學生,明天依舊認真的概率是 90%,還有 10% 的概率會溜號。
而今天溜號的學生,明天繼續溜號的可能性是 70%,剩下 30% 的可能性會變得認真。
咱們看看這個模型怎麼演化。假設總共有 100 個學生,第一天認真和溜號的各佔一半。
- 第二天,根據概率的設定,50 個認真的學生中會有 5 人變成溜號;
- 而溜號的學生中,會有 15 人變成認真;
所以,第二天是有 60 (50-5+15) 個人認真,剩下 40 個人溜號。
第三天,有 66 個認真的,34 個溜號的……
以此類推,最後有一天,您會發現有 75 個認真,25 個溜號的。
而到了這一步,模型就進入了一個穩定的狀態,數字就不變了。
因爲下一天會有 7.5 個學生從認真變成溜號,同時恰好有 7.5 個學生從溜號變成認真!
而老師對這個穩定態很不滿意,爲什麼只有 75 個認真的呢 ?
TA 安排了一場無比精彩的公開課,還請了別的老師來幫 TA 監督學生。
這一天,100 個學生都是認真的。
但這樣的干預對馬爾可夫過程是無效的。
第二天認真的學生就變成了 90 個,第三天就變成了 84 個,……直到某一天,還是 75 個認真和 25 個溜號。
馬爾可夫過程最重要的就是第二個過程,狀態之間切換的概率是固定的 。
對應到人的身上,是人的習慣、環境、認知、本性等等影響的,只要是馬爾可夫過程,不管 初始值/狀態 如何,也不管在這個過程中有什麼一次性的干預,ta 終究會演化到一個統計的 平衡態:其中每個狀態所佔的比例是不變的。
就好像終究會有 75% 的學生認真,25% 的學生溜號。馬爾可夫過程,都有一個宿命般的結局。
對於馬爾可夫過程,得知道狀態之間切換的概率是固定的 。
工程應用:PageRank網頁排序
PageRank 是網頁排序算法,Page 是網頁的意思,Rank 是權重的意思。
已知某個用戶,在搜索框中輸入一個關鍵詞,假設搜索到 個網頁。
工程師要做的就是設計好,這 個網頁的排列順序,把用戶認爲重要的網頁擺在最前面。
- 目標:將 按照重要性排序;
- 參考:學術界判斷學術論文的重要性,看論文的引用次數。
PageRank 的核心思想:被越多網頁指向的網頁,優秀的越大,權重就應該更高。
這 網頁是這麼指向的:
- 指向的網頁有
- 指向的網頁有
- 指向的網頁有
- 指向的網頁有
這種相互指向的關係,形成一幅有向圖:
而後,我們用一個二維數組存儲網頁間跳轉的概率。
Page | A | B | C | D |
---|---|---|---|---|
A | ||||
B | ||||
C | ||||
D |
這是用來存儲圖的鄰接矩陣,比如:
- 座標,因爲 不能跳轉本身;
- 座標,因爲 一共指向倆個網頁,每個網頁的跳轉概率都是一樣的。
假設有一個上網者,先隨機打開 任意一個網頁,而後跳轉到該網頁指向的鏈接,不斷的來回跳轉,這樣上網者分佈在網頁上的概率是多少?
各個網頁的最終概率,就是我們想要的網頁權重,概率等同權重。
而上網者的行爲,就是馬爾可夫過程(狀態之間切換的概率是固定的)。
這個過程有倆部分:
- 初始概率向量
- 狀態轉移矩陣
馬爾可夫過程:
PageRank發明人拉里·佩奇證明了,這個馬爾可夫過程是收斂的,最終的收斂爲:
也就是說,無論 作用於 多少次, 都不會變。
而這個穩定下了的 ,就是我們要的 網頁的權重。
是 矩陣 關於特徵值 的特徵向量。
演示過程:
得出, 概率(權重)最高, 權重相同。
求這個最終的網頁概率分佈,有倆種方法:
- 特徵向量
- 暴力迭代,反覆做矩陣乘法,最後就會得到收斂的
import numpy as np
def init_transfer_array():
A = np.array([[0.000000, 0.50, 1.00, 0.00],
[0.333333, 0.00, 0.00, 0.50],
[0.333333, 0.00, 0.00, 0.50],
[0.333333, 0.50, 0.00, 0.00]], dtype=float)
return A
def init_first_pr(d):
first_pr = np.zeros((d,1),dtype=float)
for i in range(d):
first_pr[i] = float(1) / d
return first_pr
def compute_pagerank0(transfer_array,v0):
# 用特徵向量法,來求解最終的pagerank值Vn
eigVals, eigVects = np.linalg.eig(transfer_array)
print("eigVals:\n",eigVals)
print("eigVects:\n",eigVects)
idx = 0
for i in range(transfer_array.shape[0]):
if abs(eigVals[i]-1.0) < 1e-5: # 若當前特徵值近似等於1,則爲我們要找的特徵值1,返回索引
idx = i
break
print("idx=",idx)
return eigVects[:,i]/2
def compute_pagerank1(transfer_array,v0):
# 用“迭代”法,來求解最終的pagerank值Vn
iter_num = 0 # 記錄迭代次數
pagerank = v0
while np.sum(abs(pagerank - np.dot(transfer_array,pagerank))) > 1e-6: # 若當前值與上一次的值誤差小於某個值,則認爲已經收斂,停止迭代
pagerank = np.dot(transfer_array,pagerank)
iter_num += 1
print("iter_num=",iter_num)
return pagerank
if __name__ == '__main__':
# 1.定義狀態轉移矩陣A
transfer_array = init_transfer_array()
# 2.定義初始概率矩陣V0
v0 = init_first_pr(transfer_array.shape[0])
# 3.計算最終的PageRank值
# 方法一:特徵向量
PageRank0 = compute_pagerank0(transfer_array,v0)
print("PageRank0:\n", PageRank0)
# 方法二:暴力迭代
PageRank1 = compute_pagerank1(transfer_array,v0)
print("PageRank1:\n", PageRank1)
終止點和陷阱問題
上述網頁瀏覽者的行爲是一個馬爾科夫過程,但滿足收斂性於 ,需要具備一個條件:
- 圖是強連通的,即從任意網頁可以到達其他任意網頁。
這就有倆個問題,會讓馬爾科夫過程的收斂於 ,而是 :
-
【終止點問題】:有一些網頁不指向任何網頁,上網者就會永恆的停止在 裏。
設置終止點:把 到 的鏈接丟掉, 沒有指向任何網頁,變成了一個終止點。
按照對應的轉移矩陣:
P.S. 第三列全爲 。
不斷迭代下去,最終所有元素都爲 :
-
【陷阱問題】:有些網頁不指向別的網頁,但指向自己,上網者就會陷入困境,再也不能從 裏出來。
設置陷阱:把 到 的鏈接丟掉, 指向自己,變成了一個陷阱。
按照對應的轉移矩陣:
不斷迭代下去,最終只有 :
那怎麼解決終止點問題和陷阱問題呢?
其實這個過程忽略了一個問題 — 網頁瀏覽者瀏覽的隨意性。
瀏覽者會隨機地選擇網頁,而當遇到一個結束網頁或者一個陷阱網頁(比如兩個示例中的 )時,TA可能會在瀏覽器的地址中隨機輸入一個地址,當然這個地址可能又是原來的網頁,但這有可能逃離這個陷阱。
根據現實網頁瀏覽者的瀏覽行爲,對算法進行改進。
假設每一步,網頁瀏覽者離開當前網頁跳轉到各個網頁的概率是 ,查看當前網頁的概率爲,那麼TA從瀏覽器地址欄跳轉的概率爲,於是原來的迭代公式轉化爲:
現在我們來計算帶陷阱的網頁圖的概率分佈:
不斷迭代下去,得到:
這就是 PageRank網頁排序算法,MapReduce上有 java
版的源碼。