機器學習經典算法之PageRank

Google 的兩位創始人都是斯坦福大學的博士生,他們提出的 PageRank 算法受到了論文影響力因子的評價啓發。當一篇論文被引用的次數越多,證實這篇論文的影響力越大。正是這個想法解決了當時網頁檢索質量不高的問題。html

/*請尊重做者勞動成果,轉載請標明原文連接:*/node

/* https://www.cnblogs.com/jpcflyer/p/11180263.html * /git

1、 PageRank 的簡化模型程序員

咱們先來看下 PageRank 是如何計算的。github

我假設一共有 4 個網頁 A、B、C、D。它們之間的連接信息如圖所示:算法

這裏有兩個概念你須要瞭解一下。spring

出鏈指的是連接出去的連接。入鏈指的是連接進來的連接。好比圖中 A 有 2 個入鏈,3 個出鏈。shell

簡單來講,一個網頁的影響力 = 全部入鏈集合的頁面的加權影響力之和,用公式表示爲:數組

u 爲待評估的頁面, B_{u} 爲頁面 u 的入鏈集合。針對入鏈集合中的任意頁面 v,它能給 u 帶來的影響力是其自身的影響力 PR(v) 除以 v 頁面的出鏈數量,即頁面 v 把影響力 PR(v) 平均分配給了它的出鏈,這樣統計全部能給 u 帶來連接的頁面 v,獲得的總和就是網頁 u 的影響力,即爲 PR(u)。微信

因此你能看到,出鏈會給被連接的頁面賦予影響力,當咱們統計了一個網頁鏈出去的數量,也就是統計了這個網頁的跳轉機率。

在這個例子中,你能看到 A 有三個出鏈分別連接到了 B、C、D 上。那麼當用戶訪問 A 的時候,就有跳轉到 B、C 或者 D 的可能性,跳轉機率均爲 1/3。

B 有兩個出鏈,連接到了 A 和 D 上,跳轉機率爲 1/2。

這樣,咱們能夠獲得 A、B、C、D 這四個網頁的轉移矩陣 M:

咱們假設 A、B、C、D 四個頁面的初始影響力都是相同的,即:

當進行第一次轉移以後,各頁面的影響力 w_{1} 變爲:

而後咱們再用轉移矩陣乘以 w_{1} 獲得 w_{2} 結果,直到第 n 次迭代後 w_{n} 影響力再也不發生變化,能夠收斂到 (0.3333,0.2222,0.2222,0.2222),也就是對應着 A、B、C、D 四個頁面最終平衡狀態下的影響力。

你能看出 A 頁面相比於其餘頁面來講權重更大,也就是 PR 值更高。而 B、C、D 頁面的 PR 值相等。

 

至此,咱們模擬了一個簡化的 PageRank 的計算過程,實際狀況會比這個複雜,可能會面臨兩個問題:

1. 等級泄露(Rank Leak):若是一個網頁沒有出鏈,就像是一個黑洞同樣,吸取了其餘網頁的影響力而不釋放,最終會致使其餘網頁的 PR 值爲 0。

 

 

2. 等級沉沒(Rank Sink):若是一個網頁只有出鏈,沒有入鏈(以下圖所示),計算的過程迭代下來,會致使這個網頁的 PR 值爲 0(也就是不存在公式中的 V)。

針對等級泄露和等級沉沒的狀況,咱們須要靈活處理。

好比針對等級泄露的狀況,咱們能夠把沒有出鏈的節點,先從圖中去掉,等計算完全部節點的 PR 值以後,再加上該節點進行計算。不過這種方法會致使新的等級泄露的節點的產生,因此工做量仍是很大的。

有沒有一種方法,能夠同時解決等級泄露和等級沉沒這兩個問題呢?

 

2、 PageRank 的隨機瀏覽模型

爲了解決簡化模型中存在的等級泄露和等級沉沒的問題,拉里·佩奇提出了 PageRank 的隨機瀏覽模型。他假設了這樣一個場景:用戶並不都是按照跳轉連接的方式來上網,還有一種多是不論當前處於哪一個頁面,都有機率訪問到其餘任意的頁面,好比說用戶就是要直接輸入網址訪問其餘頁面,雖然這個機率比較小。

因此他定義了阻尼因子 d,這個因子表明了用戶按照跳轉連接來上網的機率,一般能夠取一個固定值 0.85,而 1-d=0.15 則表明了用戶不是經過跳轉連接的方式來訪問網頁的,好比直接輸入網址。

其中 N 爲網頁總數,這樣咱們又能夠從新迭代網頁的權重計算了,由於加入了阻尼因子 d,必定程度上解決了等級泄露和等級沉沒的問題。

經過數學定理(這裏不進行講解)也能夠證實,最終 PageRank 隨機瀏覽模型是能夠收斂的,也就是能夠獲得一個穩定正常的 PR 值。

 

3、 PageRank 在社交影響力評估中的應用

網頁之間會造成一個網絡,是咱們的互聯網,論文之間也存在着相互引用的關係,能夠說咱們所處的環境就是各類網絡的集合。

只要是有網絡的地方,就存在出鏈和入鏈,就會有 PR 權重的計算,也就能夠運用咱們今天講的 PageRank 算法。

咱們能夠把 PageRank 算法延展到社交網絡領域中。好比在微博上,若是咱們想要計算某我的的影響力,該怎麼作呢?

一我的的微博粉絲數並不必定等於他的實際影響力。若是按照 PageRank 算法,還須要看這些粉絲的質量如何。若是有不少明星或者大 V 關注,那麼這我的的影響力必定很高。若是粉絲是經過購買殭屍粉得來的,那麼即便粉絲數再多,影響力也不高。

一樣,在工做場景中,好比說脈脈這個社交軟件,它計算的就是我的在職場的影響力。若是你的工做關係是李開復、江南春這樣的名人,那麼你的職場影響力必定會很高。反之,若是你是個學生,在職場上被鏈入的關係比較少的話,職場影響力就會比較低。

一樣,若是你想要看一個公司的經營能力,也能夠看這家公司都和哪些公司有合做。若是它合做的都是世界 500 強企業,那麼這個公司在行業內必定是領導者,若是這個公司的客戶都是小客戶,即便數量比較多,業內影響力也不必定大。

除非像淘寶同樣,有海量的中小客戶,最後大客戶也會找上門來尋求合做。因此權重高的節點,每每會有一些權重一樣很高的節點在進行合做。

 

4、 如何使用工具實現 PageRank 算法

PageRank 算法工具在 sklearn 中並不存在,咱們須要找到新的工具包。實際上有一個關於圖論和網絡建模的工具叫 NetworkX,它是用 Python 語言開發的工具,內置了經常使用的圖與網絡分析算法,能夠方便咱們進行網絡數據分析。

上節課,我舉了一個網頁權重的例子,假設一共有 4 個網頁 A、B、C、D,它們之間的連接信息如圖所示:

針對這個例子,咱們看下用 NetworkX 如何計算 A、B、C、D 四個網頁的 PR 值,具體代碼以下:

 1 import networkx as nx
 2 
 3 # 建立有向圖
 4 
 5 G = nx.DiGraph()
 6 
 7 # 有向圖之間邊的關係
 8 
 9 edges = [("A", "B"), ("A", "C"), ("A", "D"), ("B", "A"), ("B", "D"), ("C", "A"), ("D", "B"), ("D", "C")]
10 
11 for edge in edges:
12 
13     G.add_edge(edge[0], edge[1])
14 
15 pagerank_list = nx.pagerank(G, alpha=1)
16 
17 print("pagerank 值是:", pagerank_list)

NetworkX 工具把中間的計算細節都已經封裝起來了,咱們直接調用 PageRank 函數就能夠獲得結果:

1 pagerank 值是: {'A': 0.33333396911621094, 'B': 0.22222201029459634, 'C': 0.22222201029459634, 'D': 0.22222201029459634}

 

好了,運行完這個例子以後,咱們來看下 NetworkX 工具都有哪些經常使用的操做。

1. 關於圖的建立

圖能夠分爲無向圖和有向圖,在 NetworkX 中分別採用不一樣的函數進行建立。無向圖指的是不用節點之間的邊的方向,使用 nx.Graph() 進行建立;有向圖指的是節點之間的邊是有方向的,使用 nx.DiGraph() 來建立。在上面這個例子中,存在 A→D 的邊,但不存在 D→A 的邊。

 

2.關於節點的增長、刪除和查詢

若是想在網絡中增長節點,可使用 G.add_node(‘A’) 添加一個節點,也可使用 G.add_nodes_from([‘B’,‘C’,‘D’,‘E’]) 添加節點集合。若是想要刪除節點,可使用 G.remove_node(node) 刪除一個指定的節點,也可使用 G.remove_nodes_from([‘B’,‘C’,‘D’,‘E’]) 刪除集合中的節點。

那麼該如何查詢節點呢?

若是你想要獲得圖中全部的節點,就可使用 G.nodes(),也能夠用 G.number_of_nodes() 獲得圖中節點的個數。

 

3. 關於邊的增長、刪除、查詢

增長邊與添加節點的方式相同,使用 G.add_edge(「A」, 「B」) 添加指定的「從 A 到 B」的邊,也可使用 add_edges_from 函數從邊集合中添加。咱們也能夠作一個加權圖,也就是說邊是帶有權重的,使用 add_weighted_edges_from 函數從帶有權重的邊的集合中添加。在這個函數的參數中接收的是 1 個或多個三元組 [u,v,w] 做爲參數,u、v、w 分別表明起點、終點和權重。

另外,咱們可使用 remove_edge 函數和 remove_edges_from 函數刪除指定邊和從邊集合中刪除。

另外可使用 edges() 函數訪問圖中全部的邊,使用 number_of_edges() 函數獲得圖中邊的個數。

以上是關於圖的基本操做,若是咱們建立了一個圖,而且對節點和邊進行了設置,就能夠找到其中有影響力的節點,原理就是經過 PageRank 算法,使用 nx.pagerank(G) 這個函數,函數中的參數 G 表明建立好的圖。

 

5、 如何用 PageRank 揭祕希拉里郵件中的人物關係

瞭解了 NetworkX 工具的基礎使用以後,咱們來看一個實際的案例:希拉里郵件人物關係分析。

希拉里郵件事件相信你也有耳聞,對這個數據的背景咱們就不作介紹了。你能夠從 GitHub 上下載這個數據集: https://github.com/cystanford/PageRank

整個數據集由三個文件組成:Aliases.csv,Emails.csv 和 Persons.csv,其中 Emails 文件記錄了全部公開郵件的內容,發送者和接收者的信息。Persons 這個文件統計了郵件中全部人物的姓名及對應的 ID。由於姓名存在別名的狀況,爲了將郵件中的人物進行統一,咱們還須要用 Aliases 文件來查詢別名和人物的對應關係。

整個數據集包括了 9306 封郵件和 513 我的名,數據集仍是比較大的。不過這一次咱們不須要對郵件的內容進行分析,只須要經過郵件中的發送者和接收者(對應 Emails.csv 文件中的 MetadataFrom 和 MetadataTo 字段)來繪製整個關係網絡。由於涉及到的人物不少,所以咱們須要經過 PageRank 算法計算每一個人物在郵件關係網絡中的權重,最後篩選出來最有價值的人物來進行關係網絡圖的繪製。

 

瞭解了數據集和項目背景以後,咱們來設計到執行的流程步驟:

首先咱們須要加載數據源;

在準備階段:咱們須要對數據進行探索,在數據清洗過程當中,由於郵件中存在別名的狀況,所以咱們須要統一人物名稱。另外郵件的正文並不在咱們考慮的範圍內,只統計郵件中的發送者和接收者,所以咱們篩選 MetadataFrom 和 MetadataTo 這兩個字段做爲特徵。同時,發送者和接收者可能存在屢次郵件往來,須要設置權重來統計兩人郵件往來的次數。次數越多表明這個邊(從發送者到接收者的邊)的權重越高;

在挖掘階段:咱們主要是對已經設置好的網絡圖進行 PR 值的計算,但郵件中的人物有 500 多人,有些人的權重可能不高,咱們須要篩選 PR 值高的人物,繪製出他們之間的往來關係。在可視化的過程當中,咱們能夠經過節點的 PR 值來繪製節點的大小,PR 值越大,節點的繪製尺寸越大。

 

設置好流程以後,實現的代碼以下:

  1 # -*- coding: utf-8 -*-
  2 
  3 # 用 PageRank 挖掘希拉里郵件中的重要任務關係
  4 
  5 import pandas as pd
  6 
  7 import networkx as nx
  8 
  9 import numpy as np
 10 
 11 from collections import defaultdict
 12 
 13 import matplotlib.pyplot as plt
 14 
 15 # 數據加載
 16 
 17 emails = pd.read_csv("./input/Emails.csv")
 18 
 19 # 讀取別名文件
 20 
 21 file = pd.read_csv("./input/Aliases.csv")
 22 
 23 aliases = {}
 24 
 25 for index, row in file.iterrows():
 26 
 27     aliases[row['Alias']] = row['PersonId']
 28 
 29 # 讀取人名文件
 30 
 31 file = pd.read_csv("./input/Persons.csv")
 32 
 33 persons = {}
 34 
 35 for index, row in file.iterrows():
 36 
 37     persons[row['Id']] = row['Name']
 38 
 39 # 針對別名進行轉換        
 40 
 41 def unify_name(name):
 42 
 43     # 姓名統一小寫
 44 
 45     name = str(name).lower()
 46 
 47     # 去掉, 和 @後面的內容
 48 
 49     name = name.replace(",","").split("@")[0]
 50 
 51     # 別名轉換
 52 
 53     if name in aliases.keys():
 54 
 55         return persons[aliases[name]]
 56 
 57     return name
 58 
 59 # 畫網絡圖
 60 
 61 def show_graph(graph, layout='spring_layout'):
 62 
 63     # 使用 Spring Layout 佈局,相似中心放射狀
 64 
 65     if layout == 'circular_layout':
 66 
 67         positions=nx.circular_layout(graph)
 68 
 69     else:
 70 
 71         positions=nx.spring_layout(graph)
 72 
 73     # 設置網絡圖中的節點大小,大小與 pagerank 值相關,由於 pagerank 值很小因此須要 *20000
 74 
 75     nodesize = [x['pagerank']*20000 for v,x in graph.nodes(data=True)]
 76 
 77     # 設置網絡圖中的邊長度
 78 
 79     edgesize = [np.sqrt(e[2]['weight']) for e in graph.edges(data=True)]
 80 
 81     # 繪製節點
 82 
 83     nx.draw_networkx_nodes(graph, positions, node_size=nodesize, alpha=0.4)
 84 
 85     # 繪製邊
 86 
 87     nx.draw_networkx_edges(graph, positions, edge_size=edgesize, alpha=0.2)
 88 
 89     # 繪製節點的 label
 90 
 91     nx.draw_networkx_labels(graph, positions, font_size=10)
 92 
 93     # 輸出希拉里郵件中的全部人物關係圖
 94 
 95     plt.show()
 96 
 97 # 將寄件人和收件人的姓名進行規範化
 98 
 99 emails.MetadataFrom = emails.MetadataFrom.apply(unify_name)
100 
101 emails.MetadataTo = emails.MetadataTo.apply(unify_name)
102 
103 # 設置遍的權重等於發郵件的次數
104 
105 edges_weights_temp = defaultdict(list)
106 
107 for row in zip(emails.MetadataFrom, emails.MetadataTo, emails.RawText):
108 
109     temp = (row[0], row[1])
110 
111     if temp not in edges_weights_temp:
112 
113         edges_weights_temp[temp] = 1
114 
115     else:
116 
117         edges_weights_temp[temp] = edges_weights_temp[temp] + 1
118 
119 # 轉化格式 (from, to), weight => from, to, weight
120 
121 edges_weights = [(key[0], key[1], val) for key, val in edges_weights_temp.items()]
122 
123 # 建立一個有向圖
124 
125 graph = nx.DiGraph()
126 
127 # 設置有向圖中的路徑及權重 (from, to, weight)
128 
129 graph.add_weighted_edges_from(edges_weights)
130 
131 # 計算每一個節點(人)的 PR 值,並做爲節點的 pagerank 屬性
132 
133 pagerank = nx.pagerank(graph)
134 
135 # 將 pagerank 數值做爲節點的屬性
136 
137 nx.set_node_attributes(graph, name = 'pagerank', values=pagerank)
138 
139 # 畫網絡圖
140 
141 show_graph(graph)
142 
143  
144 
145 # 將完整的圖譜進行精簡
146 
147 # 設置 PR 值的閾值,篩選大於閾值的重要核心節點
148 
149 pagerank_threshold = 0.005
150 
151 # 複製一份計算好的網絡圖
152 
153 small_graph = graph.copy()
154 
155 # 剪掉 PR 值小於 pagerank_threshold 的節點
156 
157 for n, p_rank in graph.nodes(data=True):
158 
159     if p_rank['pagerank'] < pagerank_threshold:
160 
161         small_graph.remove_node(n)
162 
163 # 畫網絡圖, 採用 circular_layout 佈局讓篩選出來的點組成一個圓
164 
165 show_graph(small_graph, 'circular_layout')

 

運行結果以下:

針對代碼中的幾個模塊我作個簡單的說明:

1. 函數定義

人物的名稱須要統一,所以我設置了 unify_name 函數,同時設置了 show_graph 函數將網絡圖可視化。NetworkX 提供了多種可視化佈局,這裏我使用 spring_layout 佈局,也就是呈中心放射狀。

除了 spring_layout 外,NetworkX 還有另外三種可視化佈局,circular_layout(在一個圓環上均勻分佈節點),random_layout(隨機分佈節點 ),shell_layout(節點都在同心圓上)。

2. 計算邊權重

郵件的發送者和接收者的郵件往來可能不止一次,咱們須要用二者之間郵件往來的次數計算這二者之間邊的權重,因此我用 edges_weights_temp 數組存儲權重。而上面介紹過在 NetworkX 中添加權重邊(即便用 add_weighted_edges_from 函數)的時候,接受的是 u、v、w 的三元數組,所以咱們還須要對格式進行轉換,具體轉換方式見代碼。

3.PR 值計算及篩選

我使用 nx.pagerank(graph) 計算了節點的 PR 值。因爲節點數量不少,咱們設置了 PR 值閾值,即 pagerank_threshold=0.005,而後遍歷節點,刪除小於 PR 值閾值的節點,造成新的圖 small_graph,最後對 small_graph 進行可視化(對應運行結果的第二張圖)。

 

搜索關注微信公衆號「程序員姜小白」,獲取更新精彩內容哦。

相關文章
相關標籤/搜索