基於勝率矩陣的PageRank排序

initial_h發表於2020-05-10

  在做博弈模型評估的時候,遇到一個問題是如何評價多個模型的優劣。例如我有訓練好的三個圍棋模型A,B,C,兩兩之間對打之後有一個勝負關係,如何對這三個模型進行排序呢?通常對於人類選手這種水平有波動的情形,棋類比賽通常計算選手Elo得分按分值排序,足球籃球等通過聯賽積分或勝場進行排序,但對於固定不變的AI模型,我認為用類似PageRank的方式計算更方便也更加準確。
  這篇文章先從問題來源講起,再講解PageRank演算法的思想,最後程式設計實現排序方法並指出一些需要注意的地方。

目錄

一、問題來源

  現在,深度強化學習更多的用在博弈模型的訓練當中,比如圍棋的AlphaZero,星際爭霸的AlphaStar,DO他的OpenAI FIVE。比如我們已經訓練好了三個模型A,B,C,並且可以相互對打很多局,我們需要一個方法排出誰第一,誰第二。之前NeurIPS2019多智慧體競賽設計的排序方法就存在明顯的bug,出現了A能勝過B,且A對C的勝率高於B對C的勝率,最後算出的排名卻是B更靠前。主辦方也承認了計算方式有缺陷並表示會在之後的比賽中修正,但是當前排名維持不變。
  那為什麼成熟的Elo值計算方式沒有用在這類模型評估上面呢?Elo值通常用在圍棋、象棋等棋類排名上,電子競技例如英雄聯盟等也可以認為是類似Elo的積分方式。這類問題的特點是

  • 可通過一對一比賽得到一局的勝負關係,但和相同對手的對局次數有限,很難得到穩定的勝率關係。
  • 玩家水平並非固定不變,可隨環境、狀態等因素波動(臨場發揮),也可因長期訓練/荒廢而提升/下降(絕對實力)。

  我們需要根據這種1v1(or 5v5)的每一局的勝負關係,給出所有玩家的即時能力大小排序。由於每個人的水平都會因為身體因素、年齡因素等產生波動,這和一個固定的模型是不一樣的。而Elo可以根據每一局的實時對局結果立即更新當前排名,對棋類、競技體育等的時效性需求非常適合,也可以較為準確的反應玩家的當前水平排名。雖然它也不是絕對的準確,不過已經是針對這類需求很好的排序方法了。
  回過頭來,對於已經訓練好的AI模型,它的能力不會發生變化,並且我們可以通過足夠多的測試得到兩兩之間的準確勝率關係,這種情況下我們如果強行套Elo的演算法一局一局挑選對手對打,更新Elo值,再挑對手對打,再更新Elo值,就會顯得沒有必要(因為我們並不關心每一局後的實時排名)而且很麻煩,再者如果中途有一個新加入的模型需要從0開始評估,要想得到較為穩定的排名關係就會顯得更加麻煩。
  而PageRank的方法可以充分利用模型之間容易得到的穩定勝負關係,用矩陣迭代的方式計算出最終排名,簡單且準確。

二、PageRank演算法

演算法思想

  PageRank演算法是Google發明用來做網頁排序的,依據網頁之間的連結關係對網頁重要度進行排序。其主要設計思想如下

  • (1) 每個網頁的初始重要程度相同,比如\(a=1,b=1,c=1,...\)
  • (2) 如果許多網頁\(b,c,d...\)指向某個網頁\(a\),則網頁\(a\)很重要
  • (3) 如果某個重要的網頁\(a\)指向某個網頁\(b\),則網頁\(b\)因為\(a\)很重要也會獲得更高的重要度。

  這個想法其實和paper的引用有相似之處,每一篇新paper剛發表,很難評價其質量,可以粗略認為paper質量都一樣;如果有一篇paper被引用很多,那麼這篇paper肯定質量比較好;如果某偏很好的paper引用了另一篇paper,那這篇被引用的paper也理應質量不錯。
  基於這三點主要思想,我們假定有a,b,c,d四個網址,其連結關係如圖所示

  首先根據思想(1),假定每個網頁的初始重要度相同,比如都是1,則有重要度向量\(x=(1,1,1,1)\)
  接下來我們根據思想(2)、(3)計算每個網頁被指向後的重要度變化。令\(T_{i,j}\)表示網頁\(j\)是否指向網頁\(i\),則有

\[T_{i,j}= \left\{\begin{array}{l} 1, if \ \ j \rightarrow i\\ 0,otherwise \end{array}\right. \]

  其中\(i,j \in \{a,b,c,d\}\),且自己指向自己記為0,即\(T(i,i)=0\)。重要度的變化如下計算\(x_i=\sum x_j \times T_{i,j}\),這種方式也很直覺,就是把所有指向\(i\)的網頁的當前重要度加起來,就是網頁\(i\)的重要度。由於剛開始大家的初始重要度都是1,則從圖中的指向關係可以算出

\[\begin{array}{l} x^\prime(a)=1 \times T_{a,a}+1 \times T_{a,b}+1 \times T_{a,c}+1 \times T_{a,d}\\ \qquad \ \ = 1 \times 0 + 1 \times 0 + 1 \times 1 + 1 \times 1 \\ \qquad \ \ = 2 \end{array} \]

  同理有\(x^\prime(b)=1;x^\prime(c)=3;x^\prime(d)=2\)。這裡有一個問題需要注意,大家的初始權值都為1,但是發出去的權重卻大於1,例如網頁\(a\)指向了\(b,c,d\)三個網頁,它發出去的權值為3,這是不太合理的。一個簡單的修正方式是,令\(T_{i,j}\)中同一個網頁發出去的連結的和為1,從而每個\(T_{i,j}\)還表示發出去的權值,而不僅僅表示有無。即有

\[T_{i,j}\leftarrow \left\{\begin{array}{l} \frac{1}{\sum_{k \in \{a,b,c,d\}} T_{k,j}}, if \ \exists \ \ j \rightarrow k\\ 0, otherwise \end{array}\right. \]

  此時,我們有\(T_{i,a}=\frac{1}{3};T_{i,b}=\frac{1}{2};T_{i,c}=1;T_{i,d}=\frac{1}{2}\)。重新計算每個網頁的重要度有

\[\begin{array}{l} x^\prime(a)=1 \times T_{a,a}+1 \times T_{a,b}+1 \times T_{a,c}+1 \times T_{a,d}\\ \qquad \ \ = 1 \times 0 + 1 \times 0 + 1 \times 1 + 1 \times \frac{1}{2} \\ \qquad \ \ = \frac{3}{2} \end{array} \]

  同理有\(x^\prime(b)=\frac{1}{3};x^\prime(c)=\frac{4}{3};x^\prime(d)=\frac{5}{6}\)。我們繼續將重要度向量\(x\)進行第二次迭代計算,有

\[\begin{array}{l} x^{\prime\prime}(a)=x^\prime(a) \times T_{a,a} + x^\prime(b) \times T_{a,b}+ x^\prime(c) \times T_{a,c}+ x^\prime(d) \times T_{a,d}\\ \qquad \ \ = \frac{3}{2} \times 0 + \frac{1}{3} \times 0 + \frac{4}{3} \times 1 + \frac{5}{6} \times \frac{1}{2} \\ \qquad \ \ = \frac{7}{4} \end{array} \]

  同理有\(x^{\prime\prime}(b)=\frac{1}{2};x^{\prime\prime}(c)=\frac{13}{12};x^{\prime\prime}(d)=\frac{2}{3}\)。將計算表示為矩陣形式,我們有

\[x=\begin{pmatrix} 1 \\ 1 \\ 1 \\ 1 \end{pmatrix}, \qquad T=\begin{pmatrix} 0 & 0 & 1 & \frac{1}{2} \\ \frac{1}{3} & 0 & 0 & 0 \\ \frac{1}{3} & \frac{1}{2} & 0 & \frac{1}{2} \\ \frac{1}{3} & \frac{1}{2} & 0 & 0 \end{pmatrix} \]

  那麼前兩次迭代可以表示為

\[x^\prime=Tx;\quad x^{\prime\prime}=Tx^\prime=T^2x \]

  經過無窮次迭代\(x^\infty=T^\infty x\)收斂,\(x^\infty\)每個分量的大小即為對應網頁的重要度大小。實際情況中,不必作無限次運算即可收斂。
  接下來的問題是:對於任意這樣的矩陣,是否都會收斂呢?如何判斷當前矩陣是否具有這種收斂性?下一步給出比較直觀的理解和判斷方法,忽略證明過程。

數學原理

  如果我們把這個問題看作一個馬氏(隨機)過程,那麼四個網頁組成的向量\(x\)其實就是四個狀態。我們不取權值1,而是歸一化為\(x=(\frac{1}{4},\frac{1}{4},\frac{1}{4},\frac{1}{4})\),那麼\(x\)可以看做是該馬氏過程的初始狀態概率分佈。矩陣\(T\)就是一步的狀態轉移概率矩陣。我們的目標則是求該轉移矩陣的平穩分佈,這個平穩分佈是與初始狀態分佈\(x\)無關的,也就是說無論\(x\)的取值是多少,最後算出來的\(x^\infty\)都一樣。那麼現在的問題是什麼樣的\(T\)可以保證平穩分佈存在且唯一。這裡我們給出結論並簡單解釋,不作數學證明,可參考馬氏鏈平穩分佈存在與唯一性的簡潔證明與計算

  定理: 若馬氏鏈不可約且正常返,則平穩分佈存在且唯一。

  • 不可約:通俗來說,就是每個狀態都可以通過一步或者多步轉移到達任意另一個狀態。
  • 正常返:可以理解為每個狀態在有限步轉移後再回到自己的概率為1。
    如下圖所示例子

  從圖中可以看出,\(a\)可以通過\(a \rightarrow b \rightarrow c \rightarrow d\)到達\(d\),而\(d\)無法轉移到\(a\)等狀態,所以這個轉移矩陣不可約。同理,當\(a\)轉移到\(d\)等狀態時,就再也無法回到\(a\),則該轉移矩陣非正常返。這種情況下我們無法得到唯一的分佈。
  例如我們分別取初始分佈為

\[x_1=\begin{pmatrix} 0.1 \\ 0.2 \\ 0.3 \\ 0.1 \\ 0.1 \\ 0.2 \end{pmatrix}, \qquad x_2=\begin{pmatrix} 0 \\ 0 \\ 0 \\ 0.3 \\ 0.2 \\ 0.5 \end{pmatrix} \]

  狀態轉移矩陣為

\[T=\begin{pmatrix} 0 & 0 & 0.5 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0.5 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0.5 & 0 & 1 \\ 0 & 0 & 0 & 0.5 & 1 & 0 \end{pmatrix} \]

  則有

\[x^\infty_1=T^\infty x_1=\begin{pmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0.45 \\ 0.55 \end{pmatrix}, \qquad x^\infty_2=T^\infty x_2 = \begin{pmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0.35 \\ 0.65 \end{pmatrix} \]

  顯然\(x^\infty_1 \not = x^\infty_2\)
  到這裡我們說明了收斂性的問題,但其實真正在運用的時候還會遇到一些實際問題。下面,我們回到需要真正解決的AI模型排序的問題,用程式碼實現演算法,並解決一些運用中遇到的實際問題。

三、例項分析

  通過前述方式構建勝率矩陣,我們可以算得平穩分佈,但還有一些實際問題需要微調演算法。

對角線取值

  在之前的網頁排序裡,對角線的元素被取為0,如果在勝率矩陣中也取為0,會出現錯誤的排序。假如勝率矩陣為

    a    b    c
a   0   0.2  0.9
b  0.8   0    1  
c  0.1   0    0

  其中\(a\)\(b\)的勝率為0.2,\(a\)\(c\)的勝率為0.9;而\(b\)\(a\)的勝率為0.8,\(b\)\(c\)的勝率為1。可以很容易的看出\(b\)是最厲害的,\(a\)次之,\(c\)最弱。但如果我們直接套前面的計算方式,有

import numpy as np

T = np.matrix([[0  ,0.2,0.9],
               [0.8, 0 , 1 ],
               [0.1, 0 , 0 ]])

for i in range(T.shape[0]): # 歸一化為狀態轉移概率矩陣
    T[:,i] = T[:,i]/np.sum(T[:,i])

X = np.matrix([1/3,1/3,1/3]) # 初始分佈
X = X.T

print(T)
print(T**2000*X) 

  得到

T: 
[[0.         1.         0.47368421]
 [0.88888889 0.         0.52631579]
 [0.11111111 0.         0.        ]]

X:
[[0.48579545]
 [0.46022727]
 [0.05397727]]

  可以發現\(a\)居然比\(b\)的分值高,這顯然是不合理的。出現這個問題的原因在於,在將勝率矩陣轉化為概率矩陣時,歸一化的操作改變了\(b\)指向\(a\)的權值,直接從0.2拉到了1,使得\(b\)把所有自身的重要度都貢獻給了\(a\)。一個合理的解決辦法是將對角線取為0.5,表示自己對自己的勝率是五五開。這種方式可以防止某個概率在歸一化的過程中被不合理的放縮。此時勝率矩陣為:

    a    b    c
a  0.5  0.2  0.9
b  0.8  0.5   1  
c  0.1   0   0.5

  計算得到

T:
[[0.35714286 0.28571429 0.375     ]
 [0.57142857 0.71428571 0.41666667]
 [0.07142857 0.         0.20833333]]

X:
[[0.31038506]
 [0.66161027]
 [0.02800467]]

  可以看到,這個結果是合理的。同時這種方式還可以防止某一列出現全為0的情形。

構造不可約且正常返

  通常我們需要考慮到各種勝負關係的情況,來保證平穩分佈存在且唯一。假如勝率矩陣為

    a    b    c
a  0.5   1    1
b   0   0.5  0.3  
c   0   0.7  0.5

  可以看出\(a\)\(b\)\(c\)的勝率都為1;而\(c\)\(b\)的勝率為0.7。可以很容易的看出排序應該為\(a,c,b\)。但計算得到的結果為:

T:
[[1.         0.45454545 0.55555556]
 [0.         0.22727273 0.16666667]
 [0.         0.31818182 0.27777778]]

X:
[[1.]
 [0.]
 [0.]]

  可以發現\(b\)\(c\)的排序無法區分。出現這個問題的原因在於\(a\)是一個吸收態,只有指入沒有指出。可以通過一個權值很小的均勻的轉移矩陣進行微調。取

\[E=\begin{pmatrix} \frac{1}{3} & \frac{1}{3} & \frac{1}{3} \\ \frac{1}{3} & \frac{1}{3} & \frac{1}{3} \\ \frac{1}{3} & \frac{1}{3} & \frac{1}{3} \end{pmatrix} \]

  其中權重引數\(\alpha=0.001\),則修正後的矩陣表示為\(S = (1-\alpha)\times T + \alpha \times E\)。這裡的\(T\)是歸一化為概率矩陣的\(T\)。此時有矩陣\(S\)對每個狀態都至少有一個小的轉移概率,即不存在吸收態。同時可以注意到\(T\)並不滿足不可約且正常返的條件,但\(T\)存在平穩分佈,這說明了之前的定理條件是充分條件,而非必要條件。可以留意一下這點。最終有

T = np.matrix([[0.5, 1 , 1 ],
               [ 0 ,0.5,0.3],
               [ 0 ,0.7,0.5]])

for i in range(T.shape[0]): # 歸一化為狀態轉移概率矩陣
    T[:,i] = T[:,i]/np.sum(T[:,i])

E = np.matrix(np.ones_like(T))/T.shape[0]
alpha = 1e-3
S = (1-alpha)*T+alpha*E

X = np.matrix([1/3,1/3,1/3]) # 初始分佈
X = X.T

print(S)
print(S**2000*X) 

  得到

S:
[[9.99333333e-01 4.54424242e-01 5.55333333e-01]
 [3.33333333e-04 2.27378788e-01 1.66833333e-01]
 [3.33333333e-04 3.18196970e-01 2.77833333e-01]]

X:
[[9.98694573e-01]
 [5.86177258e-04]
 [7.19249506e-04]]

  此結果合理,且可以看出\(a\)遠遠強於\(c,b\)

完整程式碼及示例

  最終程式碼封裝為函式:

def pagerank(T):
    assert type(T) == np.matrix, 'please use np.matrix'
    for i in range(T.shape[0]):
        T[:,i] = T[:,i]/np.sum(T[:,i])
    E = np.matrix(np.ones_like(T))/T.shape[0]
    alpha = 1e-3
    S = (1-alpha)*T+alpha*E
    
    X = np.matrix([1]*T.shape[0])/T.shape[0]
    X = X.T
    
    score = S**200*X
    
    return score

  我們給一個不太好肉眼判斷的勝率關係如下:

    a    b    c
a  0.5  0.6  0.3
b  0.4  0.5  0.6  
c  0.7  0.4  0.5

  這裡三個模型出現了相互剋制的情形,即
\(a \stackrel{beats}{\longrightarrow} b \stackrel{beats}{\longrightarrow} c \stackrel{beats}{\longrightarrow} a\),帶入函式:

score:
matrix([[0.30789762],
        [0.34109655],
        [0.35100582]])

  可得排序關係\(c,b,a\)

相關文章