利用Python實現基於協同過濾演算法的影片推薦
協同過濾演算法即對一大群人進行搜尋,找出其中品味與我們相近的一小群人,並將這一小群人的偏好進行組合來構造一個推薦列表。
本文利用Python3.5分別實現了基於使用者和基於物品的協同過濾演算法的影片推薦。具體過程如下:先建立了一個涉及人員、物品和評價值的字典,然後利用兩種相似度測量演算法(歐幾里得距離和皮爾遜相關度)分別基於使用者和基於物品進行影片推薦及評論者推薦,最後對兩種協同過濾方式的選擇提出了建議。
使用字典收集偏好
新建 recommendations.py 檔案,並加入以下程式碼構建一個數據集:
# A dictionary of movie critics and their ratings of a small
# set of movies
critics={'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5,
'The Night Listener': 3.0},
'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5,
'Just My Luck': 1.5, 'Superman Returns' : 5.0, 'The Night Listener': 3.0,
'You, Me and Dupree': 3.5},
'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
'Superman Returns': 3.5, 'The Night Listener': 4.0},
'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
'The Night Listener': 4.5, 'Superman Returns': 4.0 ,
'You, Me and Dupree': 2.5},
'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
'You, Me and Dupree': 2.0},
'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}}
上面的字典清晰的展示了一位影評者對若干部電影的打分,分值為1-5。
這樣就很容易對其進行查詢和修改,如查詢某人對某部影片的評分。程式碼如下:
>>> from recommendations import critics
>>> critics['Lisa Rose']['Snakes on a Plane']
3.5
尋找相似使用者
尋找相似使用者,即確定人們在品味方面的相似度。這需要將每個人與其他所有人進行對比,並計算相似度評價值。這裡採用了歐幾里得距離和皮爾遜相關度兩套演算法來計算相似度評價值。
歐幾里得距離評價
歐幾里得距離是多維空間中兩點之間的距離,用來衡量二者的相似度。距離越小,相似度越高。
歐氏距離公式:
程式碼實現:
from math import sqrt
# Returns a distance-based similarity score for person1 and person2
def sim_distance(prefs,person1,person2):
# Get the list of shared_items
si={}
for item in prefs[person1]:
if item in prefs[person2]: si[item]=1
# if they have no ratings in common, return 0
if len(si)==0: return 0
# Add up the squares of all the differences
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2)
for item in prefs[person1] if item in prefs[person2]])
return 1/(1+sum_of_squares)
這一函式返回介於0到1之間的值。呼叫該函式,傳入兩個人的名字,可計算相似度評價值。程式碼如下:
>>> import recommendations
>>> recommendations.sim_distance(recommendations.critics,'Lisa Rose','Gene Seymour')
0.14814814814814814
皮爾遜相關度評價
皮爾遜相關係數是判斷兩組資料與某一直線擬合程度的一種度量,修正了“誇大分值”,在資料不是很規範的時候(如影評者對影片的評價總是相對於平均水平偏離很大時),會給出更好的結果。相關係數越大,相似度越高。
皮爾遜相關係數公式:
程式碼實現:
# Returns the Pearson correlation coefficient for p1 and p2
def sim_pearson(prefs,p1,p2):
# Get the list of mutually rated items
si={}
for item in prefs[p1]:
if item in prefs[p2]: si[item]=1
# if they are no ratings in common, return 0
if len(si)==0: return 0
# Sum calculations
n=len(si)
# Sums of all the preferences
sum1=sum([prefs[p1][it] for it in si])
sum2=sum([prefs[p2][it] for it in si])
# Sums of the squares
sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
sum2Sq=sum([pow(prefs[p2][it],2) for it in si])
# Sum of the products
pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
# Calculate r (Pearson score)
num=pSum-(sum1*sum2/n)
den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
if den==0: return 0
r=num/den
return r
這一函式返回介於-1到1之間的值。呼叫該函式,傳入兩個人的名字,可計算相似度評價值。程式碼如下:
>>> import recommendations
>>> recommendations.sim_pearson(recommendations.critics,'Lisa Rose','Gene Seymour')
0.39605901719066977
基於使用者提供推薦
推薦評論者
從反映偏好的字典中返回最為匹配者,返回結果的個數和相似度函式均為可選引數,對列表進行排序,評價值最高者排在最前面,從而為評論者打分。
程式碼實現:
# Returns the best matches for person from the prefs dictionary.
# Number of results and similarity function are optional params.
def topMatches(prefs,person,n=5,similarity=sim_pearson):
scores=[(similarity(prefs,person,other),other)
for other in prefs if other!=person]
scores.sort()
scores.reverse()
return scores[0:n]
呼叫該方法並傳入某一姓名,將得到一個有關影評著及其相似度評價值的列表。程式碼如下:
>>> import recommendations
>>> recommendations.topMatches(recommendations.critics,'Toby',n=4)
[(0.9912407071619299, 'Lisa Rose'), (0.9244734516419049, 'Mick LaSalle'), (0.8934051474415647, 'Claudia Puig'), (0.66284898035987, 'Jack Matthews')]
推薦影片
通過一個經過加權的評價值來為影片打分,返回所有他人評價值的加權平均、歸一及排序後的列表,並推薦給對應的影評者。
最終評價值的計算方法:
該方法的執行過程如下表1-1所示:
評論者 | 相似度 | Night | S.xNight | Lady | S.xLady | Luck | S.xLuck |
---|---|---|---|---|---|---|---|
Rose | 0.99 | 3.0 | 2.97 | 2.5 | 2.48 | 3.0 | 2.97 |
Seymour | 0.38 | 3.0 | 1.14 | 3.0 | 1.14 | 1.5 | 0.57 |
Puig | 0.89 | 4.5 | 4.42 | 3.0 | 2.68 | ||
LaSalle | 0.92 | 3.0 | 2.77 | 3.0 | 2.77 | 2.0 | 1.85 |
Matthews | 0.66 | 3.0 | 1.99 | 3.0 | 1.99 | ||
總計 | 12.89 | 8.38 | 8.07 | ||||
Sim. Sum | 3.84 | 2.95 | 3.18 | ||||
總計/Sim. Sum | 3.35 | 2.83 | 2.53 |
程式碼實現:
# Gets recommendations for a person by using a weighted average
# of every other user's rankings
def getRecommendations(prefs,person,similarity=sim_pearson):
totals={}
simSums={}
for other in prefs:
# don't compare me to myself
if other==person: continue
sim=similarity(prefs,person,other)
# ignore scores of zero or lower
if sim<=0: continue
for item in prefs[other]:
# only score movies I haven't seen yet
if item not in prefs[person] or prefs[person][item]==0:
# Similarity * Score
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim
# Sum of similarities
simSums.setdefault(item,0)
simSums[item]+=sim
# Create the normalized list
rankings=[(total/simSums[item],item) for item,total in totals.items()]
# Return the sorted list
rankings.sort()
rankings.reverse()
return rankings
對結果進行排序後,可得到一個經過排名的影片列表,並推測出自己對每部影片的評價情況。程式碼如下:
>>> import recommendations
>>> recommendations.getRecommendations(recommendations.critics,'Toby')
[(3.3477895267131017, 'The Night Listener'), (2.8325499182641614, 'Lady in the Water'), (2.530980703765565, 'Just My Luck')]
>>> recommendations.getRecommendations(recommendations.critics,'Toby',
... similarity=recommendations.sim_distance)
[(3.5002478401415877, 'The Night Listener'), (2.7561242939959363, 'Lady in the Water'), (2.461988486074374, 'Just My Luck')]
可發現,選擇不同的相似性度量方法,對結果的影響微乎其微。
基於物品提供推薦
推薦影片
當沒有收集到關於使用者的足夠資訊時,可通過檢視那些人喜歡某一特定物品,以及這些人喜歡哪些其他物品來決定相似度。因此只需將之前字典裡的人員與物品進行對換,就可以複用之前的方法。
程式碼實現:
def transformPrefs(prefs):
result={}
for person in prefs:
for item in prefs[person]:
result.setdefault(item,{})
# Flip item and person
result[item][person]=prefs[person][item]
return result
呼叫topMatches()函式得到一組與《The Night Listener》最為相近的影片列表,程式碼如下:
>>> import recommendations
>>> movies=recommendations.transformPrefs(recommendations.critics)
>>> recommendations.topMatches(movies,'The Night Listener')
[(0.5555555555555556, 'Just My Luck'), (-0.1798471947990544, 'Superman Returns'), (-0.250000000000002, 'You, Me and Dupree'), (-0.5663521139548527, 'Snakes on a Plane'), (-0.6123724356957927, 'Lady in the Water')]
推薦評論者
還可以為物品推薦使用者,呼叫getRecommendations()函式得到為《You, Me and Dupree》推薦合乎該品味的評論者,程式碼如下:
>>> import recommendations
>>> recommendations.getRecommendations(movies,'You, Me and Dupree')
[(3.1637361366111816, 'Michael Phillips')]
兩種協同過濾方式的選擇
基於物品的過濾方式推薦結果更加個性化,反映使用者自己的興趣傳承,對於稀疏資料集在精準度上更優,而且針對大資料集生成推薦列表時明顯更快,不過有維護物品相似度的額外開銷。
但是,基於使用者的過濾方法更易於實現,推薦結果著重於反應和使用者興趣相似的小群體的熱點,著重於維繫使用者的歷史興趣,更適合於規模較小的變化非常頻繁的記憶體資料集,或者有推薦相近偏好使用者給指定使用者的需求。