這個轉自csdn,很貼近工程。
協同過濾(Collective Filtering)可以說是推薦系統的標配算法。
在談推薦必談協同的今天,我們也來談一談基於KNN的協同過濾在實際的推薦應用中的一些心得體會。
我們首先從協同過濾的兩個假設聊起。
兩個假設:
- 用戶一般會喜歡與自己喜歡物品相似的物品
- 用戶一般會喜歡與自己相似的其他用戶喜歡的物品
上述假設分別對應了協同過濾的兩種實現方式:基於物品相似(item_cf)及基於用戶相似(user_cf)。
因此,協同過濾在實現過程中,最本質的任務就是計算相似度的問題,包括計算item之間的相似度,或user之間的相似度。要計算相似度,就需要一個用來計算相似度的表達。
輸入數據:
而協同過濾的輸入是一個用戶對item的評分矩陣,如下圖所示:
這種輸入數據中,沒有關於item或user的任何擴展描述。
因此item和user之間只能互為表達,用所有用戶對某item的評分來描述一個item,以及用某個用戶所有評分的item來描述該用戶本身。分別對應途中的行向量和列向量。
這樣,我們只需要搞定向量之間相似度的計算問題就OK了。
相似度計算:
以Item相似度為例,我們一般使用如下的公式完成相似度的計算:
其中:
Wij表示標號為i,j的兩個item的相似度
U(i,j)表示同時對i,j有評分的用戶的集合
Rui表示用戶u對item i的評分
參數lambda為平滑(懲罰)參數
當然,相似度的計算有很多可選的方案, 例如直接計算兩個向量夾角的cos值。只是通過實驗發現,文中提到的方法在Lz的實際推薦應用中,效果要略好而已。
對於user相似度則只需要把輸入矩陣轉置看待即可。
評分預測過程:
仍以item_cf為例,在得到item相似度之后,可以通過一個矩陣乘法運算來為輸入數據中的空白處計算一個預測值。
將輸入矩陣看成一個n*m的矩陣,記為A,n為用戶數,m為item數。而計算得到的相似度矩陣為一個m*m的矩陣,記為B。
則預測評分的過程就是A*B的過程,結果也是一個n*m的矩陣。
具體公式如下:
其中bi為歌曲本身的流行程度,可通過其他方式計算得到。
相似度計算的復雜度:
item相似度計算的復雜度:輸入矩陣A(n*m) 中的用戶數為n,item數為m,則要計算出一個m*m的相似度矩陣的話共需要O=m*m*n*d,d為數據的稀疏度。
假設n=200w,m=5w,數據稀疏度為1%,則O=50萬億。好在該過程可以很方便地使用map-reducer的方式在hdfs上分布式實現。
再假設你通過1000個計算節點來完成,則每個計算節點上的復雜度O1=5000億。
user相似度計算的復雜度:O=n*n*m*d,假設計算資源仍為1000,則每個計算節點的復雜度為O1=2000萬億。
相似度計算的兩種實現方式:
一般來講,item的量都是比較小的,而且固定,而用戶的量會比較大,而且每天都可能會不一樣。
因此,在計算過程中的實用策略也不同,大體上有如下兩種:
1. 倒排式計算:
這種方式尤其適用於用戶量較大,而item量較小,且每個用戶已評分的item數較少的情況。
具體做法就是:
在MR的map階段將每個用戶的評分item組合成pair <left,right,leftscore,rightscore> 輸出,left作為分發鍵,left+right作為排序鍵。
在reduce階段,將map中過來的數據掃一遍即可求得所有item的相似度。
詳細Hadoop過程如下所示:
該方式的好處在於沒有關聯的item之間不會產生相似度的計算開銷,所謂沒有關聯指沒有任何一個用戶對這兩個item都有評分。
不好的地方在於,如果某些用戶評分數據較多,則產生的pair對會十分巨大,在map和reducer之間會有極大的IO開銷。
2. 矩陣分塊計算:
這種計算方式適用於用戶量遠大於item量的情況下,用戶相似度的計算過程。
具體做法:將評分矩陣M切分成多個小塊,每個小塊與原矩陣的轉置矩陣做類似矩陣乘法的運算(按照文中提到的相似度計算公式,而非向量內積),
最后將計算得到的結果合並即可。
詳細Hadoop過程如下所示:
該方式的好處是避免map與reducer之間的大量緩存,而且這種方式壓根不需要reducer。
不好的地方在於transpose(M)需要在任務開始計算前緩存在每個hadoop計算節點,在矩陣M較大的時候會影響任務的啟動效率。
兩種方式各有優劣,精確計算user之間或item之間的相似度時需要根據實際的數據特點選擇合適的計算方式。
然而,隨着業務的增長,評分矩陣會越來越大,同時也會越來越稠密。
這時無論采用哪一種方式,精確地計算user之間或item之間的相似度已可望而不可及。
而且,考慮實際的推薦場景,在數據足夠多的情況下,相似度的精確計算也沒有必要。
基於SimHash的相似度計算:
當數據量太大時,往往只需要求得一個與最優解相近的近似解即可,相似度的計算也是如此。
基於SimHash計算用戶之間或item之間的相似度是推薦中較為常用的技巧。
該方法之所以能夠work,主要基於如下兩點:1.hash的隨機性,2.數據足夠多。
這兩點決定了通過simhash計算得到的相似度結果是否靠譜。
而關於SimHash的具體原理,本文不做詳述,不熟悉的同學可參閱鏈接simhash算法原理或其他相關資料。
這里主要介紹下自己在實際工作中的做法:
首先Hash函數的選擇:
本人在實際工作中嘗試了兩種Hash函數:
1. DJBX33A,將字符串映射成一個64位的無符號整數。具體實現參考:DJBX33A哈希函數實現
2. MD5, 將字符串映射成一個128為的二進制流。本人在實際工作中使用char[16]來表示。
漢明距離與相似度:
在計算得到每個對象的hashsign之后,可進一步得到兩個hashsign的漢明距離。假設最終的sign為n維,則漢明距離最大為n,最小為0.分別對應相似度為-1,1。
當漢明距離為n/2時,兩個hashsign對應的對象之間的相似度為0,也可以理解為他們的夾角為 PI/2。
可以通過如下公式對漢明距離與相似度之間做轉換:
其中N為sign維數,Hij為i,j之間的漢明距離。
漢明距離的計算:
1. 如果hashcode在64位以內,最終計算的hashsign可以使用計算機內置的類型來表示,如unsigned long。
然后對兩個對象的hashsig按位異或,計算結果中有多少位為1,即為漢明距離值。
2. 如果hashcode大於64位,例如MD5,則需要一個復合結構來表示。我們可以使用char[16]來表示128bit。
然后在計算漢明距離時分別使用兩個unsigned long 來表示高位的64bit和低位的64bit。
實踐證明,通過simhash計算的到的相似度,與真實的相似度之間幾乎等同。
而且,在實際應用中,通過simhash的推薦結果,魯棒性也更強。