特徵值分解實驗:人臉識別與PageRank網頁排序

 


特徵值與特徵向量

矩陣乘法對應一個線性變換,把輸入的任意一個向量,變成另一個方向或長度都改變的新向量,在這個變換的過程中,原來的向量主要發生了旋轉、伸縮的變換。

特別情況,存在某些向量使得變換矩陣作用於 TA 們時,只對長度做了伸縮的變換,卻不對輸入向量產生旋轉的變換(不改變原來向量的方向),這些向量就稱爲這個矩陣的特徵向量,伸縮的比例就叫特徵值

如上圖所示,輸出向量只是輸入向量的 λ\lambda 倍,只是改變了長度(模)。

  • λ\lambda 在數學上,稱爲 矩陣AA 的【特徵值】;
  • 輸出向量 vv 在數學上,稱爲 矩陣AA 的特徵值λ\lambda對應的【特徵向量】。

本科的線性代數主要研究的是方陣(行數等於列數),【特徵值】、【特徵向量】也是隻有方陣纔有的。

舉個例子。

  • A=[5133]                       v=[11]A = \left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]~~~~~~~~~~~~~~~~~~~~~~~v=\left[ \begin{matrix} 1\\ 1 \end{matrix} \right]

 

  • Av=[5133][11]=[66]A·v= \left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]·\left[ \begin{matrix} 1\\ 1 \end{matrix} \right]=\left[ \begin{matrix} 6\\ 6 \end{matrix} \right]

可以把 66 提取出來,變成:

  • 6[11]6*\left[ \begin{matrix} 1\\ 1 \end{matrix} \right]

66 就是 矩陣AA 的特徵值, [11]\left[ \begin{matrix} 1\\ 1 \end{matrix} \right] 是 矩陣AA 的特徵向量。

矩陣AA 不變,如果把 vv 改成 [33][11][44]\left[ \begin{matrix} 3\\ 3 \end{matrix} \right]、\left[ \begin{matrix} -1\\ -1 \end{matrix} \right]、\left[ \begin{matrix} 4\\ 4 \end{matrix} \right] 代進入算,發現都是 矩陣AA 特徵值 66 的特徵向量。

  • [5133][33]=6[33]\left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]·\left[ \begin{matrix} 3\\ 3 \end{matrix} \right]=6*\left[ \begin{matrix} 3\\ 3 \end{matrix} \right]

  • [5133][44]=6[44]\left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]·\left[ \begin{matrix} 4\\ 4 \end{matrix} \right]=6*\left[ \begin{matrix} 4\\ 4 \end{matrix} \right]

  • [5133][11]=6[11]\left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]·\left[ \begin{matrix} -1\\ -1 \end{matrix} \right]=6*\left[ \begin{matrix} -1\\ -1 \end{matrix} \right]

只是通過一個 特徵值 66,就找到了好多特徵向量,這是爲什麼呢?

特徵值 66 對應的這些特徵向量是有規律的,呈比例,在幾何表示上,只是伸縮的比例不同。

所以說,矩陣AA 任意一個特徵值對應無數多個特徵向量。

要求某一個特徵值的某一個特徵向量,只要找到這個特徵值對應的一個特徵向量即可。

比如,v=[11]v=\left[ \begin{matrix} -1\\ -1 \end{matrix} \right],其 TA 的特徵向量都等於 k[11]k\left[ \begin{matrix} -1\\ -1 \end{matrix} \right](kk 爲非零常數)。

  • k=4k[11]=[44]k =-4,k\left[ \begin{matrix} -1\\ -1 \end{matrix} \right]=\left[ \begin{matrix} 4\\ 4 \end{matrix} \right]
     
  • k=3k[11]=[33]k =-3,k\left[ \begin{matrix} -1\\ -1 \end{matrix} \right]=\left[ \begin{matrix} 3\\ 3 \end{matrix} \right]

特徵值、特徵向量是把矩陣看成一種對向量的變換來看方陣,當我們把方陣理解爲是一個變換的時候,這個變換擁有一些特徵,這些特徵會被特徵值、特徵向量表徵出來。

我們可以用 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 ]]

矩陣乘法是一種線性變換,變換矩陣定義了變換的規則,也就是定義了要往哪些方向伸縮或旋轉(即特徵向量),伸縮或旋轉的程度是多少(即特徵值)。

求矩陣的特徵值和特徵向量,相當於找出變換規則中的變化方向。

變換方向找到後,也就能把變換過程描述清楚。

 


特徵值分解

那會計算矩陣的特徵值、特徵向量有什麼作用嗎?

可對矩陣做分解。

以 矩陣AA 爲例,A=[5133]A = \left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right],將 AA 分解成 33 個矩陣的乘積。

矩陣分解的步驟:

  1. 求得矩陣的特徵值和對應的特徵向量;

    6[11]                 2[13]6*\left[ \begin{matrix} 1\\ 1 \end{matrix} \right]~~~~~~~~~~~~~~~~~2*\left[ \begin{matrix} 1\\ -3 \end{matrix} \right]

    特徵值 66 對應的特徵向量 [11]\left[ \begin{matrix} 1\\ 1 \end{matrix} \right],特徵值 22 對應的特徵向量 [13]\left[ \begin{matrix} 1\\ -3 \end{matrix} \right]

  2. 將這些特徵向量,橫向拼成一個矩陣;

    P=[1113]P=\left[ \begin{matrix} 1 &1 \\ 1 & -3 \end{matrix} \right]

    相當於倆個列向量拼在一起,左邊的列向量是特徵值 66 對應的特徵向量,右邊的列向量是特徵值 22 對應的特徵向量。

  3. 按照順序,將特徵值拼成一個對角矩陣;

    Λ=[6002]\Lambda=\left[ \begin{matrix} 6 & 0 \\ 0 & 2 \end{matrix} \right]

  4. 求出矩陣 PP 的逆;

    P1=[34141414]P^{-1}=\left[ \begin{matrix} \frac{3}{4} & \frac{1}{4} \\ \frac{1}{4} & -\frac{1}{4} \end{matrix} \right]

    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]]
    
  5. 最後,從左到右乘一起。

    PΛP1=[1113][6002][34141414]=[5133]=AP·\Lambda·P^{-1}=\left[ \begin{matrix} 1 &1 \\ 1 & -3 \end{matrix} \right]\left[ \begin{matrix} 6 &0 \\ 0 & 2 \end{matrix} \right]\left[ \begin{matrix} \frac{3}{4} & \frac{1}{4} \\ \frac{1}{4} & -\frac{1}{4} \end{matrix} \right]=\left[ \begin{matrix} 5 &1 \\ 3 & 3 \end{matrix} \right]=A

    算來算去,最後發現結果等於變換 矩陣AA!!

當然,這不是巧合。這告訴我們,一個方陣如果按照上面的步驟分解,就可以分解爲 33 個矩陣的乘積(PΛP1P、\Lambda、P^{-1}),因爲用到了特徵值,所以我們把這種分解方法稱爲【特徵值分解】。

特徵值分解A=PΛP1A = P·\Lambda ·P^{-1}.

調換一下,AΛA、\Lambda 的位置,就是另外一個術語啦,這倆是等價的。

矩陣對角化Λ=PAP1\Lambda=P·A ·P^{-1}

 


實對稱矩陣

矩陣可以通過求特徵值、特徵向量分解爲 33 個矩陣的乘積,特別的,AA 爲實對稱矩陣,有很重要的性質。

什麼是實對稱矩陣?

實對稱矩陣:矩陣等於TA的轉置 A=ATA=A^{T}

比如:

  • A=[1.50.50.51.0]A = \left[ \begin{matrix} 1.5 & 0.5 \\ 0.5 & 1.0 \end{matrix} \right]

實對稱矩陣的性質

  • 性質11:一定可以做特徵值分解:A=PΛP1A = P·\Lambda ·P^{-1}
  • 性質22:不同特徵值對應的特徵向量必然正交(正交:內積爲 00

比如:

  • A=[1.50.50.51.0]A = \left[ \begin{matrix} 1.5 & 0.5 \\ 0.5 & 1.0 \end{matrix} \right] 的特徵值和特徵向量 — 1.81[0.850.53]0.69[0.530.85]1.81\left[ \begin{matrix} 0.85\\ 0.53 \end{matrix} \right]、0.69\left[ \begin{matrix} -0.53\\ 0.85 \end{matrix} \right]

倆組特徵值和特徵向量滿足 性質11,又因爲 1.810.691.81 \neq 0.69(倆組特徵值不相等),所以說倆組特徵向量的內積爲 00

根據 性質11 將 矩陣AA 做特徵值分解A=PΛP1A = P·\Lambda ·P^{-1}

發現 PP 逆就是 PP 的轉置( P1=PTP^{-1} = P^{T} )。

如果一個矩陣的逆等於這個矩陣的轉置,就是正交矩陣(滿足 Q1=QTQ^{-1}=Q^{T})。

所以,這種情況下 A=PΛP1A = P·\Lambda ·P^{-1} 也可以寫成 A=QΛQTA = Q·\Lambda ·Q^{T}.

因爲 QTQ^{T} 矩陣的轉置 比 Q1Q^{-1}矩陣的逆 好算得多。

再來看一個三階方陣:

  • A=[324262423]A = \left[ \begin{matrix} 3 & -2 & -4 \\ -2 & 6 & -2 \\ -4 & -2 & 3 \end{matrix} \right]

矩陣等於TA的轉置 A=ATA=A^{T},所以這也是一個實對稱矩陣。

而後呢,TA 有 33 組特徵值和特徵向量:

  • 7[120],                  2[212],                  7[101]7\left[ \begin{matrix} -1\\ 2\\ 0 \end{matrix} \right],~~~~~~~~~~~~~~~~~~-2\left[ \begin{matrix} 2\\ 1\\ 2 \end{matrix} \right],~~~~~~~~~~~~~~~~~~7\left[ \begin{matrix} -1\\ 0\\ 1 \end{matrix} \right]

這個矩陣比較怪,因爲第 131、3 個特徵值是一樣噠,根據 性質22(不同特徵值對應第特徵向量正交)來看,那 TA 的特徵向量就不一定正交了。

不能正交,就不能用 A=QΛQTA = Q·\Lambda ·Q^{T} 來分解 矩陣AA,所以就不能用矩陣的轉置代替求矩陣的逆。

後來,人們提供了一種【施密特正交化】方法,將重複特徵值不正交的特徵向量變成正交,而後再把這些特徵向量【單位化】,就變成了倆倆正交的單位向量,再把他們拼起來就是正交矩陣!


這樣就滿足了 A=QΛQTA = Q·\Lambda ·Q^{T}.
 


工程應用:主成分分析

矩陣的特徵值分解,在工程上也有很廣泛的應用,比如主成分分析算法(Principal Component Analysis)。

主成分分析算法:通過線性變換,將原始數據變換爲一組各維度線性無關的表示,可以用來提取數據的主要數據分量,多用於高維數據的降維。

這種降維的算法,在儘可能保留原有信息前提下,將一組 NN維 降成 KK維 (0<K<N0<K<N),可以說就是刪除重複、相關性強的數據,所以有許多問題需要考慮:

  • 刪除哪個維度,信息損失最小?
  • 通過怎樣的變換對原始數據降維,信息損失最小?
  • 信息損失最小,如何度量?

那有這樣的算法嗎?

有的,這個算法就是 PCAPCA 啊,具體的算法步驟說一個 22 維 降成 11 維的栗子。

  • [1122413344]\left[ \begin{matrix} 1 & 1 & 2 & 2 & 4 \\ 1 & 3 & 3 & 4 & 4 \end{matrix} \right]

幾何表示爲:

爲了後續處理方便,對每個維度去【均值】,讓每個維度均值都爲 00

去均值:每一個維度(行)的每一個值,都減去該維度的均值。

比如第一維(第一行)的均值是 22

  • 去均值前:[11224]\left[ \begin{matrix} 1 & 1 & 2 & 2 & 4 \end{matrix} \right]

  • 去均值後:[11002]\left[ \begin{matrix} -1 & -1 & 0 & 0 & 2 \end{matrix} \right]

整體去均值後:

  • [1100220011]\left[ \begin{matrix} -1 & -1 & 0 & 0 & 2\\ -2 & 0 & 0 & 1 & 1 \end{matrix} \right]

去均值後,幾何表示爲:


去均值讓數據平移:

發現數據點在這個方向👇最分散(方差最大):


如果將原始數據投影到這個方向上,就可以將數據的二維表達降至 11 維,也儘可能的保留了原始信息 — 方差最大的方向,最大限度的保留了數據與數據之間的差異性。

22 維 降到 11 維的步驟:

  • 找一個方差最大的方向;

  • 原數據點往該方向投影,得到一維表達。

  • [1122413344]>[321201232]\left[ \begin{matrix} 1 & 1 & 2 & 2 & 4 \\ 1 & 3 & 3 & 4 & 4 \end{matrix} \right] ->\left[ \begin{matrix} -\frac{3}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0 & \frac{1}{\sqrt{2}} & \frac{3}{\sqrt{2}} \end{matrix} \right]

但計算機又沒有眼睛,怎麼知道完成這倆個步驟呢?

我們再來看一個 33 維 降爲 22 維 的栗子。

假設我們找到了方差最大的方向,在把原始的數據點往這個方向投影。

如果是降到 11 維,這樣就已經搞定了 — 但我們是要降到 22 維,所以還需要找第二個方向,第二個方向要與第一個方向正交。

因爲倆個方向正交,意味着倆個方向完全獨立、完全無關,最有效的表示信息。

但有無數個方向與第一個方向正交,到底選哪一個呢?

條件就是方差,在正交的方向中選擇方差最大的方向。


而後投影到這倆個方向所代表的平面。


33 維 降到 22 維的步驟:

  • 找一個方差最大的方向,作爲第一個方向(第一維);
  • 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第二個方向(第二維);
  • 原數據點往該方向投影,得到二維表達。

一般化,將一組 NN維 降成 KK維 (0<K<N0<K<N):

  • 找到方差最大的方向,作爲第一個方向(第一維);
  • 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第二個方向(第二維);
  • 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第三個方向(第三維);
  • … …
  • 選取與已有方向相互正交(各維度完全獨立,沒有相關性),且方差最大的方向作爲第 KK 個方向(第 KK 維);

降維的思考過程就是這樣,而後就是把這些過程給數學化,數學化的這個算法就是 PCAPCA 算法。

33 維 降到 22 維 爲栗子,第一、二步用數學表達爲:

  • 在三維空間中,找到一個二維標準正交基,使得原始數據投影到這組新基上時,各維度方差必須儘可能大。

倆個條件:

  • 投影方向是正交基;
  • 正交基約束下,各維度的方差要儘可能大。

 


數學推導:主成分分析算法

主成分分析的數學推導,本文最複雜的地方。

從方差說起。

  • 方差:var(a)=1mi=0m(aiμ)2var(a) = \frac{1}{m}\displaystyle \sum^{m}_{i=0}{(a_{i}-\mu})^{2}

aia_{i}:這個維度的每一個值

μ\mu:均值

mm:樣本總數

因爲在降維前,將每一個維度的數據都去均值(均值變爲 00,數據在原點周圍),所以每一個維度的數據方差就可簡化爲:

  • 去均值方差:var(a)=1mi=0mai2var(a) = \frac{1}{m}\displaystyle \sum^{m}_{i=0}{a_{i}}^{2}

去均值後,大大的簡化了計算量。

另外,數學上用協方差來表示數據維度之間的相關性。

  • 協方差:cov(a, b)=1mi=0m(aiμ)(biμ)cov(a,~b) = \frac{1}{m}\displaystyle \sum^{m}_{i=0}{(a_{i}-\mu})(b_{i}-\mu)

去均值後,倆個維度之間的協方差就可以簡化爲:

  • 去均值協方差:cov(a, b)=1mi=0maibicov(a,~b) = \frac{1}{m}\displaystyle \sum^{m}_{i=0}{a_{i}}b_{i}

如果倆個維度之間的協方差爲 00,代表這倆個維度完全無關,正是 PCAPCA 算法需要的。

觀察方差、協方差的表達式,方差就是向量自身的內積(相乘再相加),只不過在內積的前面乘了 1m\frac{1}{m},協方差就是倆個向量的內積,也是前面多乘了一個 1m\frac{1}{m}

假設原始數據矩陣 XX

  • X=[a1a2amb1b2bmc1c2cm]X = \left[ \begin{matrix} a_{1} & a_{2} & ··· & a_{m} \\ b_{1} & b_{2} & ··· & b_{m} \\ c_{1} & c_{2} & ··· & c_{m} \end{matrix} \right]

每條數據都有 a, b, ca,~b,~c 三個維度,總共 mm 條數據。

我們把 矩陣XX 轉置一下:

  • XT=[a1b1c1a2b2c2ambmcm]X^{T} = \left[ \begin{matrix} a_{1} & b_{1} & c_{1} \\ a_{2} & b_{2} & c_{2} \\ ··· & ··· & ···\\ a_{m} & b_{m} & c_{m} \end{matrix} \right]

再將 XXTX*X^{T}

  • XXT=[a1a2amb1b2bmc1c2cm][a1b1c1a2b2c2ambmcm]=[i=1mai2i=1maibii=1maicii=1mbiaii=1mbi2i=1mbicii=1mciaii=1mcibii=1mci2]X*X^{T} = \left[ \begin{matrix} a_{1} & a_{2} & ··· & a_{m} \\ b_{1} & b_{2} & ··· & b_{m} \\ c_{1} & c_{2} & ··· & c_{m} \end{matrix} \right]·\left[ \begin{matrix} a_{1} & b_{1} & c_{1} \\ a_{2} & b_{2} & c_{2} \\ ··· & ··· & ···\\ a_{m} & b_{m} & c_{m} \end{matrix} \right]=\left[ \begin{matrix} \displaystyle \sum^{m}_{i=1}{a_{i}}^{2} & \displaystyle \sum^{m}_{i=1}{a_{i}b_{i}} & \displaystyle \sum^{m}_{i=1}{a_{i}c_{i}} \\ \displaystyle \sum^{m}_{i=1}{b_{i}a_{i}} & \displaystyle \sum^{m}_{i=1}{b_{i}}^{2} & \displaystyle \sum^{m}_{i=1}{b_{i}c_{i}} \\ \displaystyle \sum^{m}_{i=1}{c_{i}a_{i}} & \displaystyle \sum^{m}_{i=1}{c_{i}b_{i}} & \displaystyle \sum^{m}_{i=1}{c_{i}}^{2} \end{matrix} \right]

這是一個矩陣乘法,再給這個結果乘以 1m\frac{1}{m}

  • [i=1mai2i=1maibii=1maicii=1mbiaii=1mbi2i=1mbicii=1mciaii=1mcibii=1mci2]1m=1mXXT=[1mi=1mai21mi=1maibi1mi=1maici1mi=1mbiai1mi=1mbi21mi=1mbici1mi=1mciai1mi=1mcibi1mi=1mci2]\left[ \begin{matrix} \displaystyle \sum^{m}_{i=1}{a_{i}}^{2} & \displaystyle \sum^{m}_{i=1}{a_{i}b_{i}} & \displaystyle \sum^{m}_{i=1}{a_{i}c_{i}} \\ \displaystyle \sum^{m}_{i=1}{b_{i}a_{i}} & \displaystyle \sum^{m}_{i=1}{b_{i}}^{2} & \displaystyle \sum^{m}_{i=1}{b_{i}c_{i}} \\ \displaystyle \sum^{m}_{i=1}{c_{i}a_{i}} & \displaystyle \sum^{m}_{i=1}{c_{i}b_{i}} & \displaystyle \sum^{m}_{i=1}{c_{i}}^{2} \end{matrix} \right]*\frac{1}{m}=\frac{1}{m}XX^{T}=\left[ \begin{matrix} \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}}^{2} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}b_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}c_{i}} \\ \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}a_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}}^{2} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}c_{i}} \\ \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}a_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}b_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}}^{2} \end{matrix} \right]

得出的矩陣,TA的主對角線元素爲各維度方差,其他元素爲各維度間協方差,而且這個矩陣是實對稱矩陣,這樣的矩陣稱爲【協方差矩陣】。

上述的推導過程:

基於現在掌握的知識,將一組 NN維 降成 KK維 (0<K<N0<K<N)的數學任務,就可以做進一步的數學描述。

  • NN 維空間中,找到一個 KK 維標準正交基(0<K<N0<K<N),使得原始數據投影到這組新基上時,數據協方差矩陣主對角線原始儘可能大,其他元素爲 00

降維任務的描述越來越數學化,但還是沒有找到合適的、最關鍵的降維工具。

記得,我們可以把矩陣看成一種變換,但只是換了基底,並沒有降維。

  • 原始數據協方差矩陣 [1mi=1mai21mi=1maibi1mi=1maici1mi=1mbiai1mi=1mbi21mi=1mbici1mi=1mciai1mi=1mcibi1mi=1mci2] >[1mi=1mai20001mi=1mbi20001mi=1mci2]\left[ \begin{matrix} \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}}^{2} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}b_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}c_{i}} \\ \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}a_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}}^{2} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}c_{i}} \\ \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}a_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}b_{i}} & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}}^{2} \end{matrix} \right] 希望變成 ~->\left[ \begin{matrix} \frac{1}{m}\displaystyle \sum^{m}_{i=1}{a_{i}}^{2} & 0 & 0\\ 0 & \frac{1}{m}\displaystyle \sum^{m}_{i=1}{b_{i}}^{2} &0\\ 0 & 0& \frac{1}{m}\displaystyle \sum^{m}_{i=1}{c_{i}}^{2} \end{matrix} \right]

因爲這個矩陣:

  • 主對角線元素爲各維度的方差,不全爲零;
  • 其他元素爲各維度的協方差,全爲零。

我們要求協方差爲零,相當於要求經過變換後新的基底是標準正交基。

這樣就使得協方差矩陣發生了對角化… …


變換的公式爲:Y=PXY=PX


把經過變換的協方差矩陣稱爲:C=1mYYTC` = \frac{1}{m}YY^{T}

我們把變換的公式(Y=PXY=PX)代進入,

  • C=1mYYT=1m(PX)(PX)T=1m(PX)(PTXT)=P(1mXXT)PT=PCPTC` = \frac{1}{m}YY^{T}=\frac{1}{m}(PX)(PX)^{T}=\frac{1}{m}(PX)(P^{T}X^{T})=P(\frac{1}{m}XX^{T})P^{T}=PCP^{T}

PT=QP^{T} = Q,那這個公式就變成了,

  • C=QTCQC` =Q^{T}CQ

這說明,要想使得數據的協方差矩陣CC對角化爲CC`,就要求出 CC 的特徵值和特徵向量。

CC 的特徵值,其實就是新基底下各維度的數據方差,如果把這些方差排序好,拼成對角矩陣CC`,又因爲 QT=PQ^{T}=P,所以就可以把新的特徵向量以行向量的形式,縱向拼成一個矩陣PP

最後,取 PP 的前 KK 個行向量,構成一個新的矩陣 AA,這 KK 個行向量就是我們要找的 KK 個正交的基向量,這 KK 個正交的基向量就構成了一個 KK 維的標準的正交的基。

KK 維的標準的正交的基,就是我們的目標。


原始數據矩陣,通過 KK 維的標準的正交的基投影,就在變換時降成 KK 維了。

至此,PCAPCA 算法已經設計完畢~

基於上述的討論,將一組 NN維 降成 KK維 (0<K<N0<K<N)的任務,就可以描述爲:

  • 求一個變換矩陣PP,當 Y=PXY=PX 時,有 C=PCPTC` =PCP^{T},取 PP 的前 KK 行構成矩陣AA,則 Z=AXZ=AX 即降維後的數據。

PCAPCA 算法步驟:

  • 求原始數據協方差矩陣CC的特徵值和特徵向量;
  • 將特徵值(方差)排好序,從上到下拼成矩陣CC`;
  • 將特徵向量從上到下,遵循特徵值順序縱向拼成矩陣PP
  • 取出 PP 中前 KK 行構成降維矩陣AA,作用於原始數據,即完成降維。

 


舉個荔枝

任務:將二維數據降到一維。

原始數據:[1122413344]\left[ \begin{matrix} 1 & 1 & 2 & 2 & 4 \\ 1 & 3 & 3 & 4 & 4 \end{matrix} \right]

  1. 去均值

    [1122413344]>[1100220011]\left[ \begin{matrix} 1 & 1 & 2 & 2 & 4 \\ 1 & 3 & 3 & 4 & 4 \end{matrix} \right]去均值->\left[ \begin{matrix} -1 & -1 & 0 & 0 & 2\\ -2 & 0 & 0 & 1 & 1 \end{matrix} \right]

  2. 求協方差矩陣

    1mXXT=15[1100220011][1210000121]=[65454565]\frac{1}{m}XX^{T}=\frac{1}{5}\left[ \begin{matrix} -1 & -1 & 0 & 0 & 2\\ -2 & 0 & 0 & 1 & 1 \end{matrix} \right]\left[ \begin{matrix} -1 & -2\\ -1 & 0\\ 0 & 0\\ 0 & 1\\ 2 & 1 \end{matrix} \right]=\left[ \begin{matrix} \frac{6}{5} & \frac{4}{5} \\ \frac{4}{5} & \frac{6}{5} \end{matrix} \right]

  3. 求特徵值和特徵向量

    倆組:2[1212]25[1212]2\left[ \begin{matrix} \frac{1}{\sqrt{2}} \\ \frac{1}{\sqrt{2}} \end{matrix} \right]、\frac{2}{5}\left[ \begin{matrix} -\frac{1}{\sqrt{2}} \\ \frac{1}{\sqrt{2}} \end{matrix} \right]

  4. 將特徵向量縱向拼成矩陣PP

    P=[12121212]P=\left[ \begin{matrix} \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \\ -\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \end{matrix} \right]

  5. 取出 PP 的第 11 行構成降維矩陣AA

    A=[1212]A=\left[ \begin{matrix} \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \end{matrix} \right]

  6. AA 作用於 XX(投影),得到一維表達

    Y=AX=[1212][1100220011]=[321201232]Y=AX=\left[ \begin{matrix} \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \end{matrix} \right]\left[ \begin{matrix} -1 & -1 & 0 & 0 & 2\\ -2 & 0 & 0 & 1 & 1 \end{matrix} \right]=\left[ \begin{matrix} -\frac{3}{\sqrt{2}} & -\frac{1}{\sqrt{2}} & 0 & \frac{1}{\sqrt{2}} & \frac{3}{\sqrt{2}} \end{matrix} \right]

PCAPCA 的本質是將方差最大的方向作爲主要特徵,並且在各個正交方向上將數據“離相關”,也就是讓他們在不同正交方向上沒有相關性。

PCAPCA 只能解除數據的線性相關性,對於高階相關性不起作用,可以改進爲【核PCA】,通過核函數將非線性相關轉爲線性相關,再使用 PCAPCA

PCAPCA 是一種無參技術,即沒有主觀參數的介入,所以 PCAPCA 作爲一種通用實現,本身無法做個性優化。

 


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

 


工程應用:人臉識別

下圖中,有兩張照片是同一個人的:


對於這個問題,人是很容易分辨出來的,但計算機應該怎麼辦呢?

其中一種方法就是將之線性化。首先,給出此人更多的照片:


將其中某張照片分爲眼、鼻、嘴三個部位,這是人臉最重要的三個部位。通過某種算法,可以用三個實數來分別表示這三個部位,比如下圖得到的分別是15015030302020

將所有這些照片分別算出來,用三維座標來表示得到的結果,比如上圖得到的結果就是(150,30,20)(150,30,20)

將這些三維座標用點標註在直角座標系中,發現這些點都落在某平面上,或該平面的附近。因此,可認爲此人的臉線性化爲了該平面。


將人臉線性化爲平面後,再給出一張新的照片,按照剛纔的方法算出這張照片的三維座標,發現不在平面上或者平面附近,就可以判斷不是此人的照片:


將人臉識別這種複雜的問題線性化,就可以用使用線性代數解決TA。

線性代數要學習的內容就是 如何解決線性問題,而如何把 複雜問題線性化 是別的學科的內容,比如《微積分》、《信號與系統》、《概率與統計》等。

基於矩陣對角化理論的 PCAPCA 算法在人臉識別上,能用更少的計算量取得不亞於卷積神經網絡的性能。

數據集:https://download.csdn.net/download/qq_41739364/12575178(可下載)


數據集裏有 4040 個人,每個都有 1010 張照片,分別存儲在 4040 個文件夾裏,s1s40s1-s40,每個文件夾下面都有 1010.pgm.pgm 照片,每張照片的尺寸 11292112*92(長 * 寬)。

把數據集,分成:

  • 訓練集: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))

把訓練集送入 PCAPCA 算法,構成人臉數據庫,同時也得到一個訓練集均值和降維矩陣。

降維矩陣,可以提取數據的主要信息,去除冗餘 — 所以也可以稱爲【主成分提取器】。

從測試集裏,隨機抽出一張照片,送入【主成分提取器】將該照片的主要信息提取出來,而後與數據庫中所有信息做比對檢索,計算歐式距離,找出最相似的一張照片,即完成我們的人臉識別。

上述架構:


PCAPCA 代碼:

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 

PCAPCA 加進去,就是一份完整的代碼。

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)

33 步,計算協方差矩陣的特徵值和特徵向量是最耗費計算資源的。

查看一下這個協方差矩陣:

print("covMat shape:>  ", covMat.shape)

輸出:(10304, 10304)

每一個像素點都是一個維度,這麼大的協方差矩陣,協方差矩陣 \sum 非常大,計算TA的特徵值和特徵向量,這一天可能都算不出來。

爲了求大規模特徵值和特徵向量,其實有倆組方案:

  • 安裝 numpy+mklnumpy+mkl 這種組合庫,調包即可,計算一萬維大概是 11 秒左右;
  • 通過某個小規模的矩陣來求(採用)。

 


數學推導:任何求大規模矩陣的特徵值?

現實生活裏,我們拍照都是高清配置,數據樣本的維度就很高,計算量也非常的大。

來看一段推導。

假設 XX1000010010000*100 的數據矩陣,則對應的協方差矩陣:=XXT\sum=X·X^{T}1000010000 階方陣。

現在有一個矩陣:T=XTXT = X^{T}·X(和 \sum 相反),TT100100 階方陣。

接着,由特徵值和特徵向量的定義可以得到:Tv=λvTv=\lambda vλ\lambdaTT 的特徵值,vvTT 關於 特徵值λ\lambda 的 特徵向量。

TT 換成 XX 的轉置,就有這樣一個式子:

  • XTXv=λvX^{T}·X_{v}=\lambda v

等式左右倆邊,都乘以 XX

  • XXTXv=XλvXX^{T}·X_{v}=X\lambda v

等式變形:

  • (XXT)(Xv)=λ(Xv)(X·X^{T})(Xv)=\lambda(Xv)

因爲 λ\lambda 是標量,可以提到等號左邊。

又因爲 (XXT)(X·X^{T}) 就是協方差矩陣\sum

  • (Xv)=λ(Xv)\sum(Xv)=\lambda(Xv)

v=Xvv`=Xv

  • (v)=λv\sum(v)=\lambda v

這個式子說明了,協方差矩陣\sum 和 矩陣TT 的特徵值是一樣的。

所以只要求出矩陣TT 的特徵值就求出了 \sum 的特徵值,而協方差矩陣 \sum 的特徵向量,就是矩陣TT 的特徵向量 乘 XX

這樣,求 100100 階的矩陣TT 間接求出了 1000010000 階矩陣\sum,但矩陣TT的特徵值只有 100100 個,而矩陣\sum1000010000 個,也就是說,通過這種方式只能求出 矩陣\sum 的前 100100 個特徵值。

但沒關係,前 100100 是方差最大的 100100 個,足以反應整個矩陣了。

# 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%

scikitlearnscikit−learn 裏面的 PCAPCA 算法,是奇異值分解(SVDSVD)實現的,因爲用數據矩陣的奇異值分解代替協方差矩陣的特徵值分解,速度更快。

 


馬爾可夫過程

您是否也有過這樣的感覺:不管付出了多少努力,事情總會回到老樣子,就好像冥冥之中有個無法擺脫的宿命一樣。

數學模型能告訴您其中的原理,這個數學模型就是馬爾可夫過程。

想要一次性地採取一個行動去改變某件事,結果徒勞無功,其實,這就是一個馬爾可夫過程,滿足馬爾可夫過程有四個條件。

  • 第一,系統中存在有限多個狀態。
  • 第二,狀態之間切換的概率是固定的。
  • 第三,系統要具有遍歷性,也就是從任何一個狀態出發,都能找到一條路線,切換到任何一個其他的狀態。
  • 第四,其中沒有循環的情況,不能說幾個狀態形成閉環,把其他狀態排斥在外。

舉個例子,某位老師,發現課堂上總有學生無法集中注意力,會溜號。

所謂馬爾可夫過程,就是假設學生在 “認真” 和 “溜號” 這兩個狀態之間的切換概率,是 固定的。

我們設定,今天認真聽講的學生,明天依舊認真的概率是 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 是權重的意思。

已知某個用戶,在搜索框中輸入一個關鍵詞,假設搜索到 44 個網頁。

工程師要做的就是設計好,這 44 個網頁的排列順序,把用戶認爲重要的網頁擺在最前面。

  • 目標:將 ABCDA、B、C、D 按照重要性排序;
  • 參考:學術界判斷學術論文的重要性,看論文的引用次數。

PageRank 的核心思想:被越多網頁指向的網頁,優秀的越大,權重就應該更高。

44 網頁是這麼指向的:

  • AA 指向的網頁有 BCDB、C、D
  • BB 指向的網頁有 ADA、D
  • CC 指向的網頁有 AA
  • DD 指向的網頁有 BCB、C

這種相互指向的關係,形成一幅有向圖:

而後,我們用一個二維數組存儲網頁間跳轉的概率。

Page A B C D
A 00 12\frac{1}{2} 11 00
B 13\frac{1}{3} 00 00 12\frac{1}{2}
C 13\frac{1}{3} 00 00 12\frac{1}{2}
D 13\frac{1}{3} 12\frac{1}{2} 00 00

這是用來存儲圖的鄰接矩陣,比如:

  • 座標(A,A)=0(A, A) = 0,因爲 AA 不能跳轉本身;
  • 座標(B,A)=12(B, A) = \frac{1}{2},因爲 AA 一共指向倆個網頁,每個網頁的跳轉概率都是一樣的。

假設有一個上網者,先隨機打開 ABCDA、B、C、D 任意一個網頁,而後跳轉到該網頁指向的鏈接,不斷的來回跳轉,這樣上網者分佈在網頁上的概率是多少?

各個網頁的最終概率,就是我們想要的網頁權重,概率等同權重。

而上網者的行爲,就是馬爾可夫過程(狀態之間切換的概率是固定的)。

這個過程有倆部分:

  • 初始概率向量 v0=[1/41/41/41/4]v_{0}=\left[ \begin{matrix} 1/4 \\ 1/4 \\ 1/4 \\ 1/4 \end{matrix} \right]
  • 狀態轉移矩陣 A=[01/2101/3001/21/3001/21/31/200]A=\left[ \begin{matrix} 0 & 1/2 & 1 & 0 \\ 1/3 & 0 & 0 & 1/2\\ 1/3 & 0 & 0 & 1/2 \\ 1/3 & 1/2 & 0 & 0 \end{matrix} \right]

馬爾可夫過程:

  • v1=Av0v_{1}=A·v_{0}
  • v2=Av1v_{2}=A·v_{1}
  • \cdots
  • vn=Avn1v_{n}=A·v_{n-1}

PageRank發明人拉里·佩奇證明了,這個馬爾可夫過程是收斂的,最終的收斂爲:

  • vn=Avnv_{n}=A·v_{n}

也就是說,無論 AA 作用於 vnv_{n} 多少次,vnv_{n} 都不會變。

而這個穩定下了的 vnv_{n},就是我們要的 ABCDA、B、C、D 網頁的權重。

vnv_{n} 是 矩陣AA 關於特徵值11 的特徵向量。

演示過程:

  • v1=[01/2101/3001/21/3001/21/31/200][1/41/41/41/4]=[9/245/245/245/24]v_{1}=\left[ \begin{matrix} 0 & 1/2 & 1 & 0 \\ 1/3 & 0 & 0 & 1/2\\ 1/3 & 0 & 0 & 1/2 \\ 1/3 & 1/2 & 0 & 0 \end{matrix} \right]\left[ \begin{matrix} 1/4 \\ 1/4 \\ 1/4 \\ 1/4 \end{matrix} \right]=\left[ \begin{matrix} 9/24 \\ 5/24 \\ 5/24 \\ 5/24 \end{matrix} \right]

  • v2=[01/2101/3001/21/3001/21/31/200][9/245/245/245/24]=[15/4811/4811/4811/48]v_{2}=\left[ \begin{matrix} 0 & 1/2 & 1 & 0 \\ 1/3 & 0 & 0 & 1/2\\ 1/3 & 0 & 0 & 1/2 \\ 1/3 & 1/2 & 0 & 0 \end{matrix} \right]\left[ \begin{matrix} 9/24 \\ 5/24 \\ 5/24 \\ 5/24 \end{matrix} \right]=\left[ \begin{matrix} 15/48 \\ 11/48 \\ 11/48 \\ 11/48 \end{matrix} \right]

  • \cdots

  • vn=[01/2101/3001/21/3001/21/31/200][3/92/92/92/9]=[3/92/92/92/9]v_{n}=\left[ \begin{matrix} 0 & 1/2 & 1 & 0 \\ 1/3 & 0 & 0 & 1/2\\ 1/3 & 0 & 0 & 1/2 \\ 1/3 & 1/2 & 0 & 0 \end{matrix} \right]\left[ \begin{matrix} 3/9 \\ 2/9 \\ 2/9 \\ 2/9 \end{matrix} \right]=\left[ \begin{matrix} 3/9 \\ 2/9 \\ 2/9 \\ 2/9 \end{matrix} \right]

得出,AA 概率(權重)最高,BCDB、C、D 權重相同。

求這個最終的網頁概率分佈,有倆種方法:

  • 特徵向量
  • 暴力迭代,反覆做矩陣乘法,最後就會得到收斂的 vnv_{n}
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) 

 


終止點和陷阱問題

上述網頁瀏覽者的行爲是一個馬爾科夫過程,但滿足收斂性於 vnv_{n},需要具備一個條件:

  • 圖是強連通的,即從任意網頁可以到達其他任意網頁。

這就有倆個問題,會讓馬爾科夫過程的收斂於 vnv_{n},而是 00

  • 【終止點問題】:有一些網頁不指向任何網頁,上網者就會永恆的停止在 CC 裏。

    設置終止點:把 CCAA 的鏈接丟掉,CC 沒有指向任何網頁,變成了一個終止點。

    按照對應的轉移矩陣:

    • A=[00.50000.33000.50.33000.50.330.500]A=\left[ \begin{matrix} 0 & 0.50 & 0 & 0 \\ 0.33 & 0 & 0 & 0.5\\ 0.33 & 0 & 0 & 0.5 \\ 0.33 & 0.5 & 0 & 0 \end{matrix} \right]

     

    P.S. 第三列全爲 00

    不斷迭代下去,最終所有元素都爲 00

  • 【陷阱問題】:有些網頁不指向別的網頁,但指向自己,上網者就會陷入困境,再也不能從 CC 裏出來。

    設置陷阱:把 CCAA 的鏈接丟掉,CC 指向自己,變成了一個陷阱。

    按照對應的轉移矩陣:

    • A=[00.50000.33000.50.33000.50.330.500]A=\left[ \begin{matrix} 0 & 0.50 & 0 & 0 \\ 0.33 & 0 & 0 & 0.5\\ 0.33 & 0 & 0 & 0.5 \\ 0.33 & 0.5 & 0 & 0 \end{matrix} \right]

    不斷迭代下去,最終只有 C=1C=1

那怎麼解決終止點問題和陷阱問題呢?

其實這個過程忽略了一個問題 — 網頁瀏覽者瀏覽的隨意性

瀏覽者會隨機地選擇網頁,而當遇到一個結束網頁或者一個陷阱網頁(比如兩個示例中的 CC)時,TA可能會在瀏覽器的地址中隨機輸入一個地址,當然這個地址可能又是原來的網頁,但這有可能逃離這個陷阱。

根據現實網頁瀏覽者的瀏覽行爲,對算法進行改進。

假設每一步,網頁瀏覽者離開當前網頁跳轉到各個網頁的概率是 1n\frac{1}{n},查看當前網頁的概率爲aa,那麼TA從瀏覽器地址欄跳轉的概率爲(1a)(1-a),於是原來的迭代公式轉化爲:

  • V=aAV+(1a)eV`=aAV+(1−a)e

現在我們來計算帶陷阱的網頁圖的概率分佈:

  • v1=aAv0+(1a)e=0.8[01/2101/3001/21/3001/21/31/200][1/41/41/41/4]+0.2[1/41/41/41/4]=[9/6013/6025/6013/60]v_{1}=aAv_{0}+(1−a)e=0.8\left[ \begin{matrix} 0 & 1/2 & 1 & 0 \\ 1/3 & 0 & 0 & 1/2\\ 1/3 & 0 & 0 & 1/2 \\ 1/3 & 1/2 & 0 & 0 \end{matrix} \right]\left[ \begin{matrix} 1/4 \\ 1/4 \\ 1/4 \\ 1/4 \end{matrix} \right]+0.2\left[ \begin{matrix} 1/4 \\ 1/4 \\ 1/4 \\ 1/4 \end{matrix} \right]=\left[ \begin{matrix} 9/60 \\ 13/60 \\ 25/60 \\ 13/60 \end{matrix} \right]

不斷迭代下去,得到:


這就是 PageRank網頁排序算法,MapReduce上有 java 版的源碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章