本章主要內(nèi)容是教會大家【如何根據(jù)群體偏好來給人們提供推薦】,這些應(yīng)用多如牛毛,我就不跟大家像書里那樣廢話一堆了(不好意思叛复,作者大人),直接進入正題吧扔仓,嘻嘻!
協(xié)作型過濾
一個協(xié)作型過濾算法通常的做法就是對一大群人進行搜索褐奥,并從中找出與我們品味相近的一小群人。算法會對這些人所偏愛的其他內(nèi)容進行考察翘簇,并將它們組合起來構(gòu)造出一個經(jīng)過排名的推薦列表撬码。
搜集偏好
我們要做的第一件事就是尋找一種表達不同人及其偏好的方法。在python中缘揪,達到這一目的的一種非常簡單的方法是使用一個嵌套的字典耍群。
# 這是一個字典結(jié)構(gòu)表示每個影評人對所看電影的評分情況,后續(xù)所有
#計算均基于該數(shù)據(jù)
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}}
尋找相近的用戶
搜集完人們的偏好數(shù)據(jù)之后找筝,我們需要有一種方法來確定人們在品味方面的相似程度蹈垢。為此,我們可以將每個人與所有其他人進行對比袖裕,并計算他們的相似度評價值曹抬。有若干種方法可以達到此目的,比如以下兩套計算相似度評價值的體系:歐幾里德距離和皮爾遜相關(guān)度急鳄。
關(guān)于歐幾里德距離評價方法谤民,它以經(jīng)過人們一致評價的物品為坐標軸堰酿,然后將參與評價的人繪制到圖上,并考察他們彼此間的距離遠近张足,如下圖所示:
![](http://drp.io/files/534aada2ea491.png)
兩人在“偏好空間”中的距離越近触创,他們的興趣偏好就越相似,因為這張圖是二維为牍,所以你只能看到兩項評分哼绑,但這一規(guī)則對于更多數(shù)量的評分項而言是同樣適用的。不過我們還需要一個函數(shù)來對偏好越相近的情況給出越大的值碉咆,為此我們可以將函數(shù)加1(避免遇到被0整除的錯誤)抖韩,并取其倒數(shù)。如此我們就可以出一個計算相似度的函數(shù):
# 返回一個有關(guān)person1和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)
在來看看皮爾遜相關(guān)度評價疫铜,該相關(guān)系數(shù)是判斷兩組數(shù)據(jù)與某一直線的擬合程度的一種度量茂浮。它更適用于數(shù)據(jù)不是很規(guī)范(比如評價總是相對于平均水平偏離很大時),會傾向于給出更好的結(jié)果壳咕,下圖展示的是兩位影評人(分別為x與y)對不同電影的打分:
![](http://drp.io/files/534aadd8bdd23.png)
圖上我們看到了一條直線席揽,因其繪制原則是盡可能地靠近圖上的所有坐標點,故而稱作“最佳擬合線(best-fit line)”囱井。如果兩位評論者對所有影片的評分情況相同驹尼,那么這直線將會成為對角線,并且會與圖上所有的坐標點相交庞呕,從而得到一個結(jié)果為1的理想相關(guān)度評價新翎。
另外值得注意的是采用皮爾遜方法進行評價時,它修正了“夸大分值(grade inflation)”的情況住练,比如一個人總是比另一個人給出一個更好的評分地啰,而正好這個分值之差又始終保持一致,則他們依然存在很好的關(guān)聯(lián)性讲逛。然后用“歐幾里德距離”評價的話亏吝,會因為一個人總比另一個人的評分要高,導(dǎo)致兩者不相近的結(jié)論盏混,即使他們品味相似蔚鸥。而這一行為是否是我們需要的結(jié)果,則取決于具體的應(yīng)用場景许赃。
皮爾遜相關(guān)度評價算法首先會找出兩位評論者都曾評價過的物品止喷,然后計算兩者的評分總和與平方和,并求得評分的乘積之和混聊。最后弹谁,算法利用這些結(jié)果計算出皮爾遜相關(guān)系數(shù),這一公式相對于歐幾里德不是非常直觀,但是通過除以將所有變量的變化值相乘后得到得結(jié)果预愤,它的確能告訴我們變量的總體變化情況沟于。
實現(xiàn)算法如下:
# 返回p1和p2的皮爾遜相關(guān)系數(shù)
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
為評論者打分,找到自己人植康!
接下來我們在實際場景下使用上面的算法旷太,我們根據(jù)指定人員對每個人進行打分,并找出最接近的匹配結(jié)果销睁,本例我們就是找尋與自己有相似品味的影評人泳秀,因為這樣我們就知道在選擇影片時我們應(yīng)該采納誰的建議了,太棒了榄攀!下面就是實現(xiàn)代碼:
# 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]
該函數(shù)利用了Python的列表推導(dǎo)式,采用先前定義過的皮爾遜相關(guān)度評價算法金句,將自身與字典中的其他每一個用戶進行比較檩赢,然后函數(shù)返回排序結(jié)果的前n項
推薦物品
通過上文能找到趣味相投的影評者固然不錯,但跟時常的情況是我們需要的是一份影片的推薦违寞,通過在與自己品味相近的人那里挑選他所喜歡的影片一個是隨意贞瞒,一個是累,而且有時候影評人自己都還沒看過新的電影趁曼,當(dāng)然也不存在相應(yīng)的評論军浆。 更可怕的情況時,我們可能找到一個熱衷某一部影片的古怪評論者挡闰,而根據(jù)topMatches所返回的結(jié)果乒融,其他評論者都不看好該影片。
為了解決該問題摄悯,我們需要通過一個經(jīng)過加權(quán)的評價值來為影片打分赞季,評論者的評分結(jié)果因此而形成了先后的排名。為此我們需要取得所有評論者的評價結(jié)果奢驯,借此得到相似度后申钩,再乘以他們?yōu)槊坎坑捌o的評價值。如下表:
![](http://drp.io/files/534aae286f55b.png)
如此瘪阁,相比于與我們不相似的人撒遣,那些與我們相似的人將會對整體評價值擁有更多的貢獻。我們也可以選擇利用總值來計算排名管跺,但是我們必須要要考慮到更多的影評人可能會對結(jié)果造成更大的影響义黎。為了修正這一問題,我們通過除以相似度之和(Sim.Sum)伙菜。
下列代碼反映了上述過程轩缤,非常簡單易懂,并且它對歐幾里德距離評價或者皮爾遜相關(guān)度評價都是適用的:
# Gets recommendations for a person by using a weighted average
# of every other user's rankings
# 利用所有他人評價值的加權(quán)平均,為默認提供建議
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
# 忽略評價為0或者小于0的情況
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
匹配商品
我們已經(jīng)知道如何為指定人員尋找品味相近者火的,以及如何推薦商品壶愤,但是假如我們想了解哪些商品是彼此相近的纺非,那該如何做呢盟步?在這種情況下,我們可以通過產(chǎn)看哪些人喜歡某一個特定物品末荐,以及這些人喜歡其他物品來決定相似度湃累,事實上和我們上面在獲取人與人的相似度的方法一樣——只需要在人員和物品對換即可勃救。
將人和物對調(diào)并不會總是得到有價值的結(jié)果,但大多數(shù)情況有助于做出有意義的對比治力,比如為了向不同個體推薦商品蒙秒,可能會先收集人們的購買歷史,將商品與人對調(diào)——可以令零售商找到購買某些商品的潛在客戶宵统。另一個潛在用途則是在專門推薦鏈接的網(wǎng)站上晕讲,這樣可以確保新出現(xiàn)的連接能夠被那些最有可能對它產(chǎn)生興趣的用戶找到。
基于物品的過濾
從上面來看我們用的技術(shù)被稱為“基于用戶的協(xié)作過濾”马澈,除此之外瓢省,另一種可供選擇的方法被稱為“基于物品的協(xié)作過濾”,在擁有大量數(shù)據(jù)集的情況下痊班,基于物品的協(xié)作性過濾能夠得出更好的結(jié)論勤婚,而且它允許我們將將大量計算任務(wù)預(yù)先進行,從而使給予推薦的用戶能夠更快地得到所要的結(jié)果涤伐。
其總體思路就是為每件物品預(yù)先計算好最為相近的其他物品馒胆。然后 當(dāng)我們想為某位用戶提供推薦時,就可以查看他曾經(jīng)評過分的物品废亭,從而選出排位靠前者国章,在構(gòu)造出一個加權(quán)列表,其中包含了與這些選中物品最為相似的其他物品豆村,這里與之前基于用戶的最顯著的區(qū)別在于液兽,物品間的比較不會像用戶間的比較那么頻繁變化。這就意味著物品的相似度可以單獨預(yù)先進行掌动。
構(gòu)造物品比較數(shù)據(jù)
為了對物品進行比較四啰,我們要做的第一件事就是編寫一個函數(shù),構(gòu)造一個包含相近物品的完整數(shù)據(jù)集粗恢,如下代碼所示:
def calculateSimilarItems(prefs,n=10):
# Create a dictionary of items showing which other items they
# are most similar to.
result={}
# Invert the preference matrix to be item-centric
# 以物品為中心對偏好矩陣實施倒置處理
itemPrefs=transformPrefs(prefs)
for item in itemPrefs:
# Find the most similar items to this one
# 尋找最為相近的物品
scores=topMatches(itemPrefs,item,n=n,similarity=sim_distance)
result[item]=scores
return result
該函數(shù)首先利用了transformPrefs函數(shù)柑晒,對反映評價值的字典進行倒置處理,從而得到一個有關(guān)物品及其用戶評價情況的列表眷射,然后程序循環(huán)遍歷每項物品匙赞,并將轉(zhuǎn)換了的字典傳入topMatches函數(shù)中佛掖,求得最為近似的物品及其相似度評價值,最后返回一個包含物品及其相近物品列表的字典
獲得推薦
現(xiàn)在我們可以在不遍歷整個數(shù)據(jù)集的情況下涌庭,利用反映物品相似度的字典來給出推薦了芥被。我們可以取到用戶評價過的所有物品,找出相近的物品坐榆,并根據(jù)相似度對其進行加權(quán)拴魄,我們可以很容易地根據(jù)物品字典來得到相似度。下標給出了利用基于物品的方法尋找推薦的過程席镀。這里不同于之前沒有涉及所有評論者匹中,而是給出了一個表格,對我們打過分和未打過分的影片進行了對比豪诲。如下表所示:
![](http://drp.io/files/534aae180b283.png)
每一行列出了一部曾經(jīng)看過的電影顶捷,以及對該電影的個人評價。對于每一部未看過的電影屎篱,相應(yīng)有一列與已觀看影片的相似度焊切,通過評分與相似度相乘來獲取未看影片的推薦值》际遥總計一行給出了每部影片相似度的總計與推薦值得總計。為了預(yù)測我們對影片的評分情況刹勃,只要將R.x列的總計值除以相似度總計值即可堪侯。我們可以看出來我們再也不必為所有其他評論者計算相似度評價值,因為物品相似度數(shù)據(jù)集是已經(jīng)事先構(gòu)造好的荔仁,如上面的calculateSimilarItems方法計算所得伍宦,代碼如下所示:
基于物品的相似度獲取推薦物品
def getRecommendedItems(prefs, itemMatch, user):
userRatings=prefs[user]
scores={}
totalSim={}
# Loop over items rated by this user
# 循環(huán)遍歷由當(dāng)前用戶評分的物品
for(item, rating) in userRatings.items():
# Loop over items similar to this one
# 循環(huán)遍歷與當(dāng)前物品近似的物品
for (similarity,item2) in itemMatch[item]:
# Ignore if this user has already rated this item
# 忽略已經(jīng)評價過的商品
if item2 in userRatings:
continue
scores.setdefault(item2,0)
# Weighted sum of rating times similarity
# 評價度與相似度加權(quán)之和
scores[item2]+=similarity*rating
totalSim.setdefault(item2,0)
# Sum of all the similarities
# 全部相似度之和
totalSim[item2]+=similarity
# Divide each total score by total weighting to get an average
# 加權(quán)合計值除以加權(quán)和,求出平均值乏梁,即為預(yù)計評分
rankings=[(score/totalSim[item],item) for item,score in scores.items( )]
# Return the rankings from highest to lowest
rankings.sort( )
rankings.reverse( )
return rankings
跟我來實戰(zhàn)1:
學(xué)了這么多次洼,該是那現(xiàn)實的數(shù)據(jù)練練手的時候了,我們使用的數(shù)據(jù)是來自于這個網(wǎng)站MovLen遇骑,
請大家下載10萬的zip包即可卖毁。解壓后里面有不少文件,但是我們只需要關(guān)心u.item和u.data落萎,前者包含了一組影片id和影片信息的列表亥啦,后者則是包含如下形式(用戶id、影片id练链、評分翔脱、評價時間)的實際評價情況:
![](http://drp.io/files/534aad3e79972.png)
該數(shù)據(jù)集包含了943位用戶對1682部影片所做的評價,每位用戶至少曾經(jīng)為20部影片做過評價媒鼓。那我們開始:
第一步 加載數(shù)據(jù)
def loadMovieLens(path='/data/movielens'):
# Get movie titles
movies={}
for line in open(path+'/u.item'):
(id,title)=line.split('|')[0:2]
movies[id]=title
# Load data
prefs={}
for line in open(path+'/u.data'):
(user,movieid,rating,ts)=line.split('\t')
prefs.setdefault(user,{})
prefs[user][movies[movieid]]=float(rating)
return prefs
第二步查看用戶評分
>>> reload(recommendations)
<module 'recommendations' from 'recommendations.py'>
>>> prefs=recommendations.loadMovieLens()
>>> prefs['87']
{'Birdcage, The (1996)': 4.0, 'E.T. the Extra-Terrestrial (1982)': 3.0, 'Bananas
(1971)': 5.0, 'Sting, The (1973)': 5.0, 'Bad Boys (1995)': 4.0, 'In the Line of
Fire (1993)': 5.0, 'Star Trek: The Wrath of Khan (1982)': 5.0, 'Speechless (199...
... ...
第三步獲取基于用戶的推薦
>>> recommendations.getRecommendations(prefs, '87')[0:10]
[(5.0, 'They Made Me a Criminal (1939)'), (5.0, 'Star Kid (1997)'), (5.0, 'Santa
with Muscles (1996)'), (5.0, 'Saint of Fort Washington, The (1993)'), (5.0, 'Ma
rlene Dietrich: Shadow and Light (1996) '), (5.0, 'Great Day in Harlem, A (1994)
'), (5.0, 'Entertaining Angels: The Dorothy Day Story (1996)'), (5.0, 'Boys, Les
(1997)'), (4.89884443128923, 'Legal Deceit (1997)'), (4.815019082242709, 'Lette
r From Death Row, A (1998)')]
第四步獲取基于物品的推薦
>>> itemsim=recommendations.calculateSimilarItems(prefs, n=50)
100 / 1664
200 / 1664
300 / 1664
届吁。错妖。。
>>> recommendations.getRecommendedItems(prefs, itemsim, '87')[0:30]
[(5.0, "What's Eating Gilbert Grape (1993)"), (5.0, 'Vertigo (1958)'), (5.0, 'Us
ual Suspects, The (1995)'), (5.0, 'Toy Story (1995)'), (5.0, 'Titanic (1997)'),
(5.0, 'Sword in the Stone, The (1963)'), (5.0, 'Stand by Me (1986)'), (5.0, 'Sli
ng Blade (1996)'), (5.0, 'Silence of the Lambs, The (1991)'), (5.0, 'Shining, Th
e (1980)'), (5.0, 'Shine (1996)'), (5.0, 'Sense and Sensibility (1995)'), (5.0,
'Scream (1996)'), (5.0, 'Rumble in the Bronx (1995)'), (5.0, 'Rock, The (1996)')
盡管物品相似度字典花費時間較長疚沐,但推薦過程中由于數(shù)據(jù)完全預(yù)先構(gòu)造是瞬間完成暂氯,而且獲取推薦所花費的時間不會隨著用戶數(shù)量增加而增加
基于用戶過濾還是基于物品過濾
在針對大數(shù)據(jù)集生成推薦列表時,基于物品進行過濾的方式明顯要比基于用戶的過濾更快濒旦,不過它有維護物品相似度表的額外開銷株旷。影評數(shù)據(jù)相對密集(影評人幾乎對每部電影都做過評價),而delicious書簽用戶的數(shù)據(jù)可能就是稀疏的(大多數(shù)書簽都分散在小眾收藏夾)尔邓,對于稀疏數(shù)據(jù)晾剖,基于物品的過濾方法通常優(yōu)于基于用戶過濾,而對于密集數(shù)據(jù)梯嗽,則兩者效果幾乎一樣齿尽。盡管如此,基于用戶的過濾方法更加易于實現(xiàn)灯节,而且沒有額外步驟循头,因此他更適用于規(guī)模小的變化非常頻繁的內(nèi)存數(shù)據(jù)集⊙捉總之卡骂,告訴一些人存在一些人和自己偏好相近是有一定價值的。
現(xiàn)在大家應(yīng)該對相似度評價值的計算有所掌握形入,并且也應(yīng)該清楚利用它們對用戶和物品進行比較全跨。