一、用戶行為數據
一個用戶行為表示為6部分,即產生行為的用戶和行為的對象、行為的種類、產生行為的上下文、行為的內容和權重。用戶行為的統一表示如下:
user id 產生行為的用戶的唯一標識
item id 產生行為的對象的唯一標識
behavior type 行為的種類(比如是購買還是瀏覽)
context 產生行為的上下文,包括時間和地點等
behavior weight 行為的權重(如果是觀看視頻的行為,那么這個權重可以是觀看時長;如果是打分行為,這個權重可以是分數)
behavior content 行為的內容 (如果是評論行為,那么就是評論的文本;如果是打標簽的行為,就是標簽)
二、用戶行為分析
在利用用戶行為數據設計推薦算法之前,研究人員首先需要對用戶行為數據進行分析,了解數據中蘊含的一般規律,這樣才能對算法的設計起到指導作用。
1、用戶活躍度和物品流行度的分布:
很多關於互聯網數據的研究發現,互聯網上的很多數據分布都滿足一種稱為Power Law的分布,這個分布在互聯網領域也稱長尾分布。
2、用戶活躍度和物品流行度的關系:
一般來說,不活躍的用戶要么是新用戶,要么是只來過網站一兩次的老用戶。那么,不同活躍度的用戶喜歡的物品的流行度是否有差別?一般認為,新用戶傾向於瀏覽熱門的物品,因為他
們對網站還不熟悉,只能點擊首頁的熱門物品,而老用戶會逐漸開始瀏覽冷門的物品。這表明用戶越活躍,越傾向於瀏覽冷門的物品。
僅僅基於用戶行為數據設計的推薦算法一般稱為協同過濾算法。學術界對協同過濾算法進行了深入研究,提出了很多方法,比如基於鄰域的方法(neighborhood-based)、隱語義模型
(latent factor model)、基於圖的隨機游走算法(random walk on graph)等。在這些方法中,最著名的、在業界得到最廣泛應用的算法是基於鄰域的方法,
而基於鄰域的方法主要包含下面兩種算法:
1)基於用戶的協同過濾算法 這種算法給用戶推薦和他興趣相似的其他用戶喜歡的物品。
2)基於物品的協同過濾算法 這種算法給用戶推薦和他之前喜歡的物品相似的物品。
3、實驗設計和算法測評
訓練集和測試集划分,交叉驗證
評測指標:recall、precision、coverage、popularity
新穎度,這里用推薦列表中物品的平均流行度度量推薦結果的新穎度。如果推薦出的物品都很熱門,說明推薦的新穎度較低,否則說明推薦結果比較新穎。
注意:這里,在計算平均流行度時對每個物品的流行度取對數,這是因為物品的流行度分布滿足長尾分布,在取對數后,流行度的平均值更加穩定。
4、基於鄰域的算法
基於鄰域的算法是推薦系統中最基本的算法,該算法不僅在學術界得到了深入研究,而且在業界得到了廣泛應用。
基於鄰域的算法分為兩大類,一類是基於用戶的協同過濾算法,另一類是
基於物品的協同過濾算法。下面幾節將對這兩種算法進行深入介紹,對比它們的優缺點並提出改進方案。
A 基於用戶的協同過濾算法
I . 基於用戶的協同過濾算法主要包括兩個步驟:
(1) 找到和目標用戶興趣相似的用戶集合。
(2) 找到這個集合中的用戶喜歡的,且目標用戶沒有聽說過的物品推薦給目標用戶。
興趣相似度:
步驟(1)的關鍵就是計算兩個用戶的興趣相似度。這里,協同過濾算法主要利用行為的相似度計算興趣的相似度。給定用戶u和用戶v,令N(u)表示用戶u曾經有過正反饋的物品集合,令N(v)
為用戶v曾經有過正反饋的物品集合。那么,我們可以通過如下的
Jaccard公式簡單地計算u和v的興趣相似度:
或者通過余弦相似度計算:

def UserSimilarity(train):
W = dict()
for u in train.keys():
for v in train.keys():
if u == v:
continue
W[u][v] = len(train[u] & train[v])
W[u][v] /= math.sqrt(len(train[u]) * len(train[v]) * 1.0)
return W
該代碼對兩兩用戶都利用余弦相似度計算相似度。這種方法的時間復雜度是O(|U|*|U|),這在用戶數很大時非常耗時。事實上,很多用戶相互之間並沒有對同樣的物品產生過行為,即很多時
用戶間交集為0。上面的算法將很多時間浪費在了計算這種用戶之間的相似度上。如果換一個思路,我們可以首先計算出交集不為0的用戶對(u,v),然后再對這種情況除以分母。
為此,可以首先建立物品到用戶的倒排表,對於每個物品都保存對該物品產生過行為的用戶列表。


參數K是UserCF的一個重要參數,它的調整對推薦算法的各種指標都會產生一定的影響。
准確率和召回率 可以看到,推薦系統的精度指標(准確率和召回率)並不和參數K成線性關系。在MovieLens數據集中,選擇K=80左右會獲得比較高的准確率和召回率。因此選
擇合適的K對於獲得高的推薦系統精度比較重要。當然,推薦結果的精度對K也不是特別敏感,只要選在一定的區域內,就可以獲得不錯的精度。
流行度 可以看到,在3個數據集上K越大則UserCF推薦結果就越熱門。這是因為K決定了UserCF在給你做推薦時參考多少和你興趣相似的其他用戶的興趣,那么如果K越大,參
考的人越多,結果就越來越趨近於全局熱門的物品。
覆蓋率 可以看到,在3個數據集上,K越大則UserCF推薦結果的覆蓋率越低。覆蓋率的降低是因為流行度的增加,隨着流行度增加,UserCF越來越傾向於推薦熱門的物品,從
而對長尾物品的推薦越來越少,因此造成了覆蓋率的降低。
II. 用戶相似度計算的改進
上一節介紹了計算用戶興趣相似度的最簡單的公式(余弦相似度公式),但這個公式過於粗糙,本節將討論如何改進該公式來提高UserCF的推薦性能。
以圖書為例,如果兩個用戶都曾經買過《新華字典》,這絲毫不能說明他們興趣相似,因為絕大多數中國人小時候都買過《新華字典》。但如果兩個用戶都買過《數據挖掘導論》,那可以認為他們的興趣比較相似,因為只有研究數據挖掘的人才會買這本書。換句話說,兩個用戶對冷門物品采取過同樣的行為更能說明他們興趣的相似度。因此,John S. Breese在論文①中提出了如下公式,根據用戶行為計算用戶的興趣相似度:

將上述相似度記為UserCF-IIF,通過實驗評測UserCF-IIF的推薦性能,並將其和UserCF進行對比。在上一節的實驗中,K=80時UserCF的性能最好,因此這里的實驗同樣選取K=80。
UserCF-IIF在各項性能上略優於UserCF。這說明在計算用戶興趣相似度時考慮物品的流行度對提升推薦結果的質量確實有幫助。
III. 實際在線系統使用UserCF的例子
相比我們后面要討論的基於物品的協同過濾算法(ItemCF), UserCF在目前的實際應用中使用並不多。其中最著名的使用者是Digg,它在2008年對推薦系統進行了新的嘗試①。Digg使用推薦系統的原因也是信息過載,它的研究人員經過統計發現,每天大概會有15 000篇新的文章,而每個用戶的精力是有限的,而且興趣差別很大。因此Digg覺得應該通過推薦系統幫用戶從這么多篇文章中找到真正令他們感興趣的內容,同時使每篇文章都有機會被展示給用戶。利用對文章的 頂 和 踩 來找出相同的興趣的用戶,使用用戶協同過濾來實現推薦。
B 基於物品的協同過濾算法
基於物品的協同過濾(item-based collaborative filtering)算法是目前業界應用最多的算法。由亞馬遜提出。
基於物品的協同過濾算法(簡稱ItemCF)給用戶推薦那些和他們之前喜歡的物品相似的物品。比如,該算法會因為你購買過《數據挖掘導論》而給你推薦《機器學習》。
不過,ItemCF算法並不利用物品的內容屬性計算物品之間的相似度,它主要通過分析用戶的行為記錄計算物品之間的相似度。
基於物品的協同過濾算法主要分為兩步:
(1) 計算物品之間的相似度。
(2) 根據物品的相似度和用戶的歷史行為給用戶生成推薦列表。
亞馬遜顯示相關物品推薦時的標題是“Customers Who Bought This Item Also Bought”從這句話的定義出發,我們可以用下面的公式定
義物品的相似度:

和UserCF算法類似,用ItemCF算法計算物品相似度時也可以首先建立用戶—物品倒排表(即對每個用戶建立一個包含他喜歡的物品的列表),然后對於每個用戶,將他物品列表中的物品兩
兩在共現矩陣C中加1。

def ItemSimilarity(train):
#calculate co-rated users between items
C = dict()
N = dict()
for u, items in train.items():
for i in users:
N[i] += 1
for j in users:
if i == j:
continue
C[i][j] += 1
#calculate finial similarity matrix W
W = dict()
for i,related_items in C.items():
for j, cij in related_items.items():
W[u][v] = cij / math.sqrt(N[i] * N[j])
return W
在MovieLens數據集上利用上面的程序計算電影之間相似度的結果。盡管在計算過程中沒有利用任何內容屬性,但利用ItemCF計算的結果卻是可以從內容上看
出某種相似度的。一般來說,同系列的電影、同主角的電影、同風格的電影、同國家和地區的電影會有比較大的相似度。

該公式的含義是,和用戶歷史上感興趣的物品越相似的物品,越有可能在用戶的推薦列表中獲得比較高的排名。
從這個例子可以看到,ItemCF的一個優勢就是可以提供推薦解釋,即利用用戶歷史上喜歡的物品為現在的推薦結果進行解釋。
1)用戶活躍度對物品相似度的影響
從前面的討論可以看到,在協同過濾中兩個物品產生相似度是因為它們共同出現在很多用戶的興趣列表中。
換句話說,每個用戶的興趣列表都對物品的相似度產生貢獻。那么,是不是每個用戶的貢獻都相同呢?
假設有這么一個用戶,他是開書店的,並且買了當當網上80%的書准備用來自己賣。另外可以看到,這個用戶雖然活躍,但是買這些書並非都是出於自身的興趣,而且這些書覆\
蓋了當當網圖書的很多領域,所以這個用戶對於他所購買書的兩兩相似度的貢獻應該遠遠小於一個只買了十幾本自己喜歡的書的文學青年。
John S. Breese在論文①中提出了一個稱為IUF(Inverse User Frequence),即用戶活躍度對數的倒數的參數,他也認為活躍用戶對物品相似度的貢獻應該小於不活躍的用戶,他提出應該增加IUF
參數來修正物品相似度的計算公式:

當然,上面的公式只是對活躍用戶做了一種軟性的懲罰,但對於很多過於活躍的用戶,比如上面那位買了當當網80%圖書的用戶,為了避免相似度矩陣過於稠密,我們在實際計算中一般直
接忽略他的興趣列表,而不將其納入到相似度計算的數據集中。直接去除。
ItemCF-IUF在准確率和召回率兩個指標上和ItemCF相近,但ItemCF-IUF明顯提高了推薦結果的覆蓋率,降低了推薦結果的流行度。從這個意義上說,ItemCF-IUF確實改進了ItemCF的綜合性能。
2) 物品相似度的歸一化
Karypis在研究中發現如果將ItemCF的相似度矩陣按最大值歸一化,可以提高推薦的准確率。相似度的歸一化也可以提高推薦的多樣性。
那么,對於兩個不同的類,什么樣的類其類內物品之間的相似度高,什么樣的類其類內物品相似度低呢?一般來說,熱門的類其類內物品相似度一般比較大。如果不進行歸一化,就會推薦
比較熱門的類里面的物品,而這些物品也是比較熱門的。因此,推薦的覆蓋率就比較低。相反,如果進行相似度的歸一化,則可以提高推薦系統的覆蓋率。
從實驗結果可以看到,歸一化確實能提高ItemCF的性能,其中各項指標都有了比較明顯的提高。准確率、召回率、覆蓋度都有提升,流行度降低(越低越好,說明新穎度高)。
3)UserCF和ItemCF的綜合比較
首先回顧一下UserCF算法和ItemCF算法的推薦原理。UserCF給用戶推薦那些和他有共同興趣愛好的用戶喜歡的物品,而ItemCF給用戶推薦那些和他之前喜歡的物品類似的物品。
從這個算法的原理可以看到,UserCF的推薦結果着重於反映和用戶興趣相似的小群體的熱點,而ItemCF的推薦結果着重於維系用戶的歷史興趣。換句話說,UserCF的推薦更社會化,反映了用戶所在的小型興趣群體中物品的熱門程度,而ItemCF的推薦更加個性化,反映了用戶自己的興趣傳承。
新聞網站,新聞推薦中,使用UserCF更合適。但是,在圖書、電子商務和電影網站,比如亞馬遜、豆瓣、Netflix中,ItemCF則能極大地發揮優勢。
在早期的研究中,大部分研究人員都是讓少量的用戶對大量的物品進行評價,然后研究用戶興趣的模式。那么,對於他們來說,因為用戶很少,計算用戶興趣相似度是最快也是最簡單
的方法。但在實際的互聯網中,用戶數目往往非常龐大,而在圖書、電子商務網站中,物品的數目則是比較少的。此外,物品的相似度相對於用戶的興趣一般比較穩定,因此使用ItemCF是
比較好的選擇。當然,新聞網站是個例外,在那兒,物品的相似度變化很快,物品數目龐大,相反用戶興趣則相對固定(都是喜歡看熱門的),所以新聞網站的個性化推薦使用UserCF算法的更多。
首先要指出的是,離線實驗的性能在選擇推薦算法時並不起決定作用。首先應該滿足產品的需求,比如如果需要提供推薦解釋,那么可能得選擇ItemCF算法。其次,需要看實現代價,比如
若用戶太多,很難計算用戶相似度矩陣,這個時候可能不得不拋棄UserCF算法。最后,離線指標和點擊率等在線指標不一定成正比。而且,這里對比的是最原始的UserCF和ItemCF算法,
這兩種算法都可以進行各種各樣的改進。一般來說,這兩種算法經過優化后,最終得到的離線性能是近似的。
哈利波特問題:

不過,上述方法還不能徹底地解決哈利波特問題。每個用戶一般都會在不同的領域喜歡一種物品。以電視為例,看新聞聯播是父輩每天的必修課,他們每天基本就看新聞聯播,而且每
天不看別的新聞,就看這一種新聞。此外,他們很多都是電視劇迷,都會看央視一套8點的電視劇。那么,最終結果就是黃金時間的電視劇都和新聞聯播相似,而新聞聯播和其他新聞的相似
度很低。
上面的問題換句話說就是,兩個不同領域的最熱門物品之間往往具有比較高的相似度。這個時候,僅僅靠用戶行為數據是不能解決這個問題的,因為用戶的行為表示這種物品之間應該相似
度很高。此時,我們只能依靠引入物品的內容數據解決這個問題,比如對不同領域的物品降低權重等。這些就不是協同過濾討論的范疇了。
5、隱語義模型
1) LFM(latent factor model)隱語義模型逐漸成為推薦系統領域耳熟能詳的名詞。其實該算法最早在文本挖掘領域被提出,用於找到文本的隱含語義。相關的名詞有LSI、pLSA、LDA和Topic Model.
隱語義模型是最近幾年推薦系統領域最為熱門的研究話題,它的核心思想是通過隱含特征(latent factor)聯系用戶興趣和物品。
那么如何給A和B推薦圖書呢?
對於UserCF,首先需要找到和他們看了同樣書的其他用戶(興趣相似的用戶),然后給他們推薦那些用戶喜歡的其他書。
對於ItemCF,需要給他們推薦和他們已經看的書相似的書,比如作者B看了很多關於數據挖掘的書,可以給他推薦機器學習或者模式識別方面的書。
還有一種方法,可以對書和物品的興趣進行分類。對於某個用戶,首先得到他的興趣分類,然后從分類中挑選他可能喜歡的物品。
總結一下,這個基於興趣分類的方法大概需要解決3個問題。
如何給物品進行分類?
如何確定用戶對哪些類的物品感興趣,以及感興趣的程度?
對於一個給定的類,選擇哪些屬於這個類的物品推薦給用戶,以及如何確定這些物品在一個類中的權重?
對於第一個問題的簡單解決方案是找編輯給物品分類,但編輯分類存在着許多問題。
研究人員提出:為什么我們不從數據出發,自動地找到那些類,然后進行個性化推薦?於是,隱含語義分析技術(latent variable analysis)出現了。隱含語義分析技術
因為采取基於用戶行為統計的自動聚類。
隱含語義分析技術的分類來自對用戶行為的統計,代表了用戶對物品分類的看法。
隱含語義分析技術允許我們指定最終有多少個分類,這個數字越大,分類的粒度就會越細,反正分類粒度就越粗。
隱含語義分析技術會計算出物品屬於每個類的權重,因此每個物品都不是硬性地被分到某一個類中。
隱含語義分析技術給出的每個分類都不是同一個維度的,它是基於用戶的共同興趣計算出來的,如果用戶的共同興趣是某一個維度,那么LFM給出的類也是相同的維度。
隱含語義分析技術可以通過統計用戶行為決定物品在每個類中的權重,如果喜歡某個類的用戶都會喜歡某個物品,那么這個物品在這個類中的權重就可能比較高。
隱含語義分析技術從誕生到今天產生了很多著名的模型和方法,其中和該技術相關且耳熟能詳的名詞有pLSA、LDA、隱含類別模型(latent class model)、隱含主題模型(latent topic model)、
矩陣分解(matrix factorization)。
推薦系統的用戶行為分為顯性反饋和隱性反饋。LFM在顯性反饋數據(也就是評分數據)上解決評分預測問題並達到了很好的精度。
隱性反饋數據集,這種數據集的特點是只有正樣本(用戶喜歡什么物品),而沒有負樣本(用戶對什么物品不感興趣)。
首先如何給每個用戶生成負樣本:
通過2011年的KDD Cup的Yahoo! Music推薦系統比賽,我們發現對負樣本采樣時應該遵循以下原則:
對每個用戶,要保證正負樣本的平衡(數目相似)。
對每個用戶采樣負樣本時,要選取那些很熱門,而用戶卻沒有行為的物品。

我們同樣通過離線實驗評測LFM的性能。首先,我們在MovieLens數據集上用LFM計算出用戶興趣向量p和物品向量q,然后對於每個隱類找出權重最大的物品。
結果表明,每一類的電影都是合理的,都代表了一類用戶喜歡看的電影。從而說明LFM確實可以實現通過用戶行為將物品聚類的功能。
其次,我們通過實驗對比了LFM在TopN推薦中的性能。在LFM中,重要的參數有4個:
隱特征的個數F;
學習速率alpha;
正則化參數lambda;
負樣本/正樣本比例 ratio。
通過實驗發現,ratio參數對LFM的性能影響最大。因此,固定F=100、alpha=0.02、lambda=0.01,然后研究負樣本/正樣本比例ratio對推薦結果性能的影響。
隨着負樣本數目的增加,LFM的准確率和召回率有明顯提高。不過當ratio>10以后,准確率和召回率基本就比較穩定了。同時,隨着負樣本數目的增加,覆蓋率不
斷降低,而推薦結果的流行度不斷增加,說明ratio參數控制了推薦算法發掘長尾的能力。當數據集非常稀疏時,LFM的性能會明顯下降,甚至不如UserCF和ItemCF的性能。
2)基於LFM的實際系統的例子: 雅虎新聞推薦,實時性問題和冷啟動問題。
3)LFM和基於鄰域的方法的比較:
LFM是一種基於機器學習的方法,具有比較好的理論基礎。這個方法和基於鄰域的方法(比如UserCF、ItemCF)相比,各有優缺點。
理論基礎 LFM具有比較好的理論基礎,它是一種學習方法,通過優化一個設定的指標建立最優的模型。基於鄰域的方法更多的是一種基於統計的方法,並沒有學習過程。
離線計算的空間復雜度 基於鄰域的方法需要維護一張離線的相關表。在離線計算相關表的過程中,如果用戶/物品數很多,將會占據很大的內存。假設有M個用戶和N個物品,
在計算相關表的過程中,我們可能會獲得一張比較稠密的臨時相關表(盡管最終我們對每個物品只保留K個最相關的物品,但在中間計算過程中稠密的相關表是不可避免的),
那么假設是用戶相關表,則需要O(M*M)的空間,而對於物品相關表,則需要O(N*N)的空間。而LFM在建模過程中,如果是F個隱類,那么它需要的存儲空間是O(F*(M+N)),這在
M和N很大時可以很好地節省離線計算的內存。在Netflix Prize中,因為用戶數很龐大(40多萬),很少有人使用UserCF算法(據說需要30 GB左右的內存),而LFM由於大量節
省了訓練過程中的內存(只需要4 GB),從而成為Netflix Prize中最流行的算法。
離線計算的時間復雜度 假設有M個用戶、N個物品、K條用戶對物品的行為記錄。那么,UserCF計算用戶相關表的時間復雜度是O(N * (K/N)^2),而ItemCF計算物品相關表的時間
復雜度是O(M*(K/M)^2)。而對於LFM,如果用F個隱類,迭代S次,那么它的計算復雜度是O(K * F * S)。那么,如果K/N > F*S,則代表UserCF的時間復雜度低於LFM,如果
K/M>F*S,則說明ItemCF的時間復雜度低於LFM。在一般情況下,LFM的時間復雜度要稍微高於UserCF和ItemCF,這主要是因為該算法需要多次迭代。但總體上,這兩種算法
在時間復雜度上沒有質的差別。
在線實時推薦 UserCF和ItemCF在線服務算法需要將相關表緩存在內存中,然后可以在線進行實時的預測。以ItemCF算法為例,一旦用戶喜歡了新的物品,就可以通過查詢內
存中的相關表將和該物品相似的其他物品推薦給用戶。因此,一旦用戶有了新的行為,而且該行為被實時地記錄到后台的數據庫系統中,他的推薦列表就會發生變化。而從LFM
的預測公式可以看到,LFM在給用戶生成推薦列表時,需要計算用戶對所有物品的興趣權重,然后排名,返回權重最大的N個物品。那么,在物品數很多時,這一過程的時間
復雜度非常高,可達O(M*N*F)。因此,LFM不太適合用於物品數非常龐大的系統,如果要用,我們也需要一個比較快的算法給用戶先計算一個比較小的候選列表,然后再用
LFM重新排名。另一方面,LFM在生成一個用戶推薦列表時速度太慢,因此不能在線實時計算,而需要離線將所有用戶的推薦結果事先計算好存儲在數據庫中。因此,LFM不
能進行在線實時推薦,也就是說,當用戶有了新的行為后,他的推薦列表不會發生變化。
推薦解釋 ItemCF算法支持很好的推薦解釋,它可以利用用戶的歷史行為解釋推薦結果。但LFM無法提供這樣的解釋,它計算出的隱類雖然在語義上確實代表了一類興趣和物品,
卻很難用自然語言描述並生成解釋展現給用戶。
6、基於圖的模型
1) 基於圖的模型(graph-based model)是推薦系統中的重要內容。其實,很多研究人員把基於鄰域的模型也稱為基於圖的模型,因為可以把基於鄰域的模型看做基於圖的模型的簡單形式。

2) 基於圖的推薦算法
將用戶行為表示為二分圖模型后,下面的任務就是在二分圖上給用戶進行個性化推薦。如果將個性化推薦算法放到二分圖模型上,那么給用戶u推薦物品的任務就可以轉化為度量用戶頂點
vu和與vu沒有邊直接相連的物品節點在圖上的相關性,相關性越高的物品在推薦列表中的權重就越高。
一般來說圖中頂點的相關性主要取決於下面3個因素:
兩個頂點之間的路徑數;
兩個頂點之間路徑的長度;
兩個頂點之間的路徑經過的頂點。
而相關性高的一對頂點一般具有如下特征:
兩個頂點之間有很多路徑相連;
連接兩個頂點之間的路徑長度都比較短;
連接兩個頂點之間的路徑不會經過出度比較大的頂點
計算圖中頂點之間相關性的方法: 一種基於隨機游走的PersonalRank算法:

雖然PersonalRank算法可以通過隨機游走進行比較好的理論解釋,但該算法在時間復雜度上有明顯的缺點。因為在為每個用戶進行推薦時,都需要在整個用戶物品二分圖上進行迭代,直到
整個圖上的每個頂點的PR值收斂。這一過程的時間復雜度非常高.
為了解決PersonalRank每次都需要在全圖迭代並因此造成時間復雜度很高的問題,這里給出兩種解決方案。第一種很容易想到,就是減少迭代次數,在收斂之前就停止。這樣會影響最終的
精度,但一般來說影響不會特別大。另一種方法就是從矩陣論出發,重新設計算法。

對矩陣運算比較熟悉的讀者可以輕松將PersonalRank轉化為矩陣的形式。令M為用戶物品二分圖的轉移概率矩陣:

實現代碼如下:
#矩陣的方法
#coding:utf-8
import numpy as np
from numpy.linalg import solve
import time
from scipy.sparse.linalg import gmres, lgmres
from scipy.sparse import csr_matrix
if __name__ == '__main__':
alpha = 0.8
vertex = ['A', 'B', 'C', 'a', 'b', 'c', 'd']
M = np.array([[0, 0, 0, 0.5, 0, 0.5, 0],
[0, 0, 0, 0.25, 0.25, 0.25, 0.25],
[0, 0, 0, 0, 0, 0.5, 0.5],
[0.5, 0.5, 0, 0, 0, 0, 0],
[0, 1.0, 0, 0, 0, 0, 0],
[0.333, 0.333, 0.333, 0, 0, 0, 0],
[0, 0.5, 0.5, 0, 0, 0, 0]])
r0 = np.array([[1], [0], [0], [0], [0], [0], [0]]) # 從'A'開始游走
# 直接解線性方程法
n = M.shape[0]
A = np.eye(n) - alpha * M.T
b = (1 - alpha) * r0
begin = time.time()
r = solve(A, b)
end = time.time()
print('user time', end - begin)
rank = {}
for j in np.arange(n):
rank[vertex[j]] = r[j]
for ele in vertex:
print(ele, rank[ele])
# 采用CSR法對稀疏矩陣進行壓縮存儲,然后解線性方程
data = list()
row_ind = list()
col_ind = list()
for row in np.arange(n):
for col in np.arange(n):
if (A[row, col] != 0):
data.append(A[row, col])
row_ind.append(row)
col_ind.append(col)
AA = csr_matrix((data, (row_ind, col_ind)), shape=(n, n))
begin = time.time()
r = gmres(AA, b, tol=1e-08, maxiter=1)[0]
end = time.time()
print("user time", end - begin)
rank = {}
for j in np.arange(n):
rank[vertex[j]] = r[j]
for ele in vertex:
print(ele, rank[ele])
('user time', 0.009312868118286133)
('A', array([0.3137876]))
('B', array([0.16565576]))
('C', array([0.07569236]))
('a', array([0.15864619]))
('b', array([0.03313115]))
('c', array([0.18892314]))
('d', array([0.0634081]))
('user time', 0.006492137908935547)
('A', 0.31378760114963755)
('B', 0.1656557621557125)
('C', 0.07569236305439948)
('a', 0.15864619289099752)
('b', 0.0331311524311425)
('c', 0.1889231381127573)
('d', 0.06340809765290228)
#循環的方法
def PersonalRank(G, alpha, root, max_step):
rank = {index: 0 for index in G.keys()}
for i in range(max_step):
for j in rank.keys():
temp = 0
for k in G[j]:
temp += alpha * rank[k] / len(G[k])
rank[j] = temp
if j == root:
rank[j] += 1 - alpha
return rank
if __name__ == "__main__":
G = {'A': ['a', 'c'],
'B': ['a', 'b', 'c', 'd'],
'C': ['c', 'd'],
'a': ['A', 'B'],
'b': ['B'],
'c': ['A', 'B', 'C'],
'd': ['B', 'C']}
rank = PersonalRank(G, 0.8, 'A', 1000)
vertex = ['A', 'B', 'C', 'a', 'b', 'c', 'd']
result = []
for index in vertex:
result.append([index, rank[index]])
result.sort(key=lambda x: x[1], reverse=True)
for ele in result:
print (ele[0],ele[1])
A 0.3137876011496375 c 0.18892313811275727 B 0.16565576215571243 a 0.1586461928909975 C 0.07569236305439944 d 0.06340809765290226 b 0.03313115243114249
從上面結果可以看出,從從A點開始游走,到達概率c>B>a>C>d>b
因為A已經訪問過a和c了,所以給A推薦d的概率要大於b的概率
參考資料:
1. https://cloud.tencent.com/developer/article/1098856 推薦算法圖推薦-基於隨機游走的personalrank算法實現
2.https://blog.csdn.net/bbbeoy/article/details/78646635 基於圖的推薦算法(PersonalRank)
3.https://donche.github.io/2017/10/31/PageRankNPersonalRank.html PageRank 與基於圖的推薦算法
4. https://blog.csdn.net/love_data_scientist/article/details/95047023
5. https://blog.csdn.net/HHTNAN/article/details/79899069
