讀古今文學網 > 機器學習實戰 > 14.4 基於協同過濾的推薦引擎 >

14.4 基於協同過濾的推薦引擎

近十年來,推薦引擎對因特網用戶而言已經不是什麼新鮮事物了。Amazon會根據顧客的購買歷史向他們推薦物品,Netflix會向其用戶推薦電影,新聞網站會對用戶推薦新聞報道,這樣的例子還有很多很多。當然,有很多方法可以實現推薦功能,這裡我們只使用一種稱為協同過濾(collaborative filtering)的方法。協同過濾是通過將用戶和其他用戶的數據進行對比來實現推薦的。

這裡的數據是從概念上組織成了類似圖14-2所給出的矩陣形式。當數據採用這種方式進行組織時,我們就可以比較用戶或物品之間的相似度了。這兩種做法都會使用我們很快就介紹到的相似度的概念。當知道了兩個用戶或兩個物品之間的相似度,我們就可以利用已有的數據來預測未知的用戶喜好。例如,我們試圖對某個用戶喜歡的電影進行預測,推薦引擎會發現有一部電影該用戶還沒看過。然後,它就會計算該電影和用戶看過的電影之間的相似度,如果其相似度很高,推薦算法就會認為用戶喜歡這部電影。

在上述場景下,唯一所需要的數學方法就是相似度的計算,這並不是很難。接下來,我們首先討論物品之間的相似度計算,然後討論在基於物品和基於用戶的相似度計算之間的折中。最後,我們介紹推薦引擎成功的度量方法。

14.4.1 相似度計算

我們希望擁有一些物品之間相似度的定量方法。那麼如何找出這些方法呢?倘若我們面對的是食品銷售網站,該如何處理?或許可以根據食品的配料、熱量、某個烹調類型的定義或者其他類似的信息進行相似度的計算。現在,假設該網站想把業務拓展到餐具行業,那麼會用熱量來描述一個叉子嗎?問題的關鍵就在於用於描述食品的屬性和描述餐具的屬性有所不同。倘若我們使用另外一種比較物品的方法會怎樣呢?我們不利用專家所給出的重要屬性來描述物品從而計算它們之間的相似度,而是利用用戶對它們的意見來計算相似度。這就是協同過濾中所使用的方法。它並不關心物品的描述屬性,而是嚴格地按照許多用戶的觀點來計算相似度。圖14-3給出了由一些用戶及其對前面給出的部分菜餚的評級信息所組成的矩陣。

圖14-3 用於展示相似度計算的簡單矩陣

我們計算一下手撕豬肉和烤牛肉之間的相似度。一開始我們使用歐氏距離來計算。手撕豬肉和烤牛肉的歐氏距離為:

而手撕豬肉和鰻魚飯的歐氏距離為:

在該數據中,由於手撕豬肉和烤牛肉的距離小於手撕豬肉和鰻魚飯的距離,因此手撕豬肉與烤牛肉比與鰻魚飯更為相似。我們希望,相似度值在0到1之間變化,並且物品對越相似,它們的相似度值也就越大。我們可以用相似度=1/(1+距離)這樣的算式來計算相似度。當距離為0時,相似度為1.0。如果距離真的非常大時,相似度也就趨近於0。

第二種計算距離的方法是皮爾遜相關係數(Pearson correlation)。我們在第8章度量回歸方程的精度時曾經用到過這個量,它度量的是兩個向量之間的相似度。該方法相對於歐氏距離的一個優勢在於,它對用戶評級的量級並不敏感。比如某個狂躁者對所有物品的評分都是5分,而另一個憂鬱者對所有物品的評分都是1分,皮爾遜相關係數會認為這兩個向量是相等的。在NumPy中,皮爾遜相關係數的計算是由函數corrcoef進行的,後面我們很快就會用到它了。皮爾遜相關係數的取值範圍從-1到+1,我們通過0.5 + 0.5*corrcoef這個函數計算,並且把其取值範圍歸一化到0到1之間。

另一個常用的距離計算方法就是餘弦相似度(cosine similarity),其計算的是兩個向量夾角的餘弦值。如果夾角為90度,則相似度為0;如果兩個向量的方向相同,則相似度為1.0。同皮爾遜相關係數一樣,餘弦相似度的取值範圍也在-1到+1之間,因此我們也將它歸一化到0到1之間。計算餘弦相似度值,我們採用的兩個向量AB夾角的餘弦相似度的定義如下:

其中,||A||、||B||表示向量A、B的2范數,你可以定義向量的任一范數,但是如果不指定范數階數,則都假設為2范數。向量[4,2,2]的2范數為:

同樣,NumPy的線性代數工具箱中提供了范數的計算方法linalg.norm

接下來我們將上述各種相似度的計算方法寫成Python中的函數。打開svdRec.py文件並加入下列代碼。

程序清單14-1 相似度計算

from numpy import *
from numpy import linalg as la

def ecludSim(inA,inB):
    return 1.0/(1.0 + la.norm(inA - inB))

def pearsSim(inA,inB):
    if len(inA) < 3 : return 1.0
    return 0.5+0.5*corrcoef(inA, inB, rowvar = 0)[0][1]

def cosSim(inA,inB):
    num = float(inA.T*inB)
    denom = la.norm(inA)*la.norm(inB)
    return 0.5+0.5*(num/denom)
  

程序中的3個函數就是上面提到的幾種相似度的計算方法。為了便於理解,NumPy的線性代數工具箱linalg被作為la導入,函數中假定inAinB都是列向量。perasSim函數會檢查是否存在3個或更多的點。如果不存在,該函數返回1.0,這是因為此時兩個向量完全相關。

下面我們對上述函數進行嘗試。在保存好文件svdRec.py之後,在Python提示符下輸入如下命令:

>>> reload(svdRec)
<module \'svdRec\' from \'svdRec.pyc\'>
>>> myMat=mat(svdRec.loadExData)
>>> svdRec.ecludSim(myMat[:,0],myMat[:,4])
0.12973190755680383
>>> svdRec.ecludSim(myMat[:,0],myMat[:,0])
1.0
  

歐氏距離看上去還行,那麼接下來試試餘弦相似度:

>>> svdRec.cosSim(myMat[:,0],myMat[:,4])
0.5
>>> svdRec.cosSim(myMat[:,0],myMat[:,0])
1.0000000000000002    
  

餘弦相似度似乎也行,就再試試皮爾遜相關係數:

>>> svdRec.pearsSim(myMat[:,0],myMat[:,4])
0.20596538173840329>>> svdRec.pearsSim(myMat[:,0],myMat[:,0])
1.0 
  

上面的相似度計算都是假設數據採用了列向量方式進行表示。如果利用上述函數來計算兩個行向量的相似度就會遇到問題(我們很容易對上述函數進行修改以計算行向量之間的相似度)。這裡採用列向量的表示方法,暗示著我們將利用基於物品的相似度計算方法。後面我們會闡述其中的原因。

14.4.2 基於物品的相似度還是基於用戶的相似度?

我們計算了兩個餐館菜餚之間的距離,這稱為基於物品(item-based)的相似度。另一種計算用戶距離的方法則稱為基於用戶(user-based)的相似度。回到圖14-3,行與行之間比較的是基於用戶的相似度,列與列之間比較的則是基於物品的相似度。到底使用哪一種相似度呢?這取決於用戶或物品的數目。基於物品相似度計算的時間會隨物品數量的增加而增加,基於用戶的相似度計算的時間則會隨用戶數量的增加而增加。如果我們有一個商店,那麼最多會有幾千件商品。在撰寫本書之際,最大的商店大概有100 000件商品。而在Netflix大賽中,則會有480 000個用戶和17 700部電影。如果用戶的數目很多,那麼我們可能傾向於使用基於物品相似度的計算方法。

對於大部分產品導向的推薦引擎而言,用戶的數量往往大於物品的數量,即購買商品的用戶數會多於出售的商品種類。

14.4.3 推薦引擎的評價

如何對推薦引擎進行評價呢?此時,我們既沒有預測的目標值,也沒有用戶來調查他們對預測的滿意程度。這裡我們就可以採用前面多次使用的交叉測試的方法。具體的做法就是,我們將某些已知的評分值去掉,然後對它們進行預測,最後計算預測值和真實值之間的差異。

通常用於推薦引擎評價的指標是稱為最小均方根誤差(Root Mean Squared Error,RMSE)的指標,它首先計算均方誤差的平均值然後取其平方根。如果評級在1星到5星這個範圍內,而我們得到的RMSE為1.0,那麼就意味著我們的預測值和用戶給出的真實評價相差了一個星級。