協同過濾算法

協做型過濾

協同過濾是利用集體智慧的一個典型方法。要理解什麼是協同過濾 (Collaborative Filtering, 簡稱CF),首先想一個簡單的問題,若是你如今想看個電影,但你不知道具體看哪部,你會怎麼作?大部分的人會問問周圍的朋友,看看最近有什麼好看的電影推薦,而咱們通常更傾向於從口味比較相似的朋友那裏獲得推薦。這就是協同過濾的核心思想。python

協同過濾通常是在海量的用戶中發掘出一小部分和你品位比較相似的,在協同過濾中,這些用戶成爲鄰居,而後根據他們喜歡的其餘東西組織成一個排序的目錄推薦給你。git

要實現協同過濾,須要如下幾個步驟:github

  • 蒐集偏好ide

  • 尋找相近用戶函數

  • 推薦物品oop

蒐集偏好

首先,咱們要尋找一種表達不一樣人及其偏好的方法。這裏咱們用python的嵌套字典來實現。測試

在本章中所用的數據,是從國外的網站grouplens下載的u.data。該數據總共四列,共分爲用戶ID、電影ID、用戶評分、時間。咱們只需根據前三列,生成相應的用戶偏好字典。網站

#生成用戶偏好字典
def make_data():
    result={}
    f = open('data/u.data', 'r')
    lines = f.readlines()
    for line in lines:
        #按行分割數據
        (userId , itemId , score,time ) = line.strip().split("\t")
        #字典要提早定義
        if not result.has_key( userId ):
            result[userId]={}
        #注意float,否則後續的運算存在類型問題
        result[userId][itemId] = float(score)
    return result

另外若是想在字典中顯示展示電影名,方便分析,也能夠根據u.item中電影數據,預先生成電影的數據集。ui

#將id替換爲電影名 構造數據集
def loadMovieLens(path='data'):
    # 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

根據上面兩個函數中的一種,到此咱們的用戶數據集已經構造好了,因爲數據量不是很是大,暫時放在內存中便可。
因爲以上數據集比較抽象,不方便講解,至此咱們定義一個簡單的數據集來說解一些例子,一個簡單的嵌套字典:this

#用戶:{電影名稱:評分}
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}
}

尋找相近用戶

收集完用戶信息後,咱們經過一些方法來肯定兩個用戶之間品味的類似程度,計算他們的類似度評價值。有不少方法能夠計算,咱們在此介紹兩套常見的方法:歐幾里得距離和皮爾遜相關度。

歐幾里得距離

歐幾里得距離(euclidea nmetric)(也稱歐式距離)是一個一般採用的距離定義,指在m維空間中兩個點之間的真實距離,或者向量的天然長度(即該點到原點的距離)。在二維和三維空間中的歐氏距離就是兩點之間的實際距離。

數學定義:
已知兩點 A = (x_1,x_2,...,x_n)和B = (y_1,y_2,...,y_n),則兩點間距離:
$$|AB|=\sqrt{(x_1 - x_2)^2+(y_1 - y_2)^2+...+(x_n-y_n)^2}$$
接下來咱們只要把數據集映射爲座標系就能夠明顯的比較出類似度,以"Snakes on a Plane"和"You, Me and Dupree"兩部電影距離,有座標系以下圖:
歐幾里得距離圖例

計算上圖中Toby和Mick LaSalle的類似度:

from math import sqrt
sqrt(pow( 4.5-4 , 2 ) + pow( 1 - 2 , 2))
1.118033988749895

上面的式子計算出了實際距離值,但在實際應用中,咱們但願類似度越大返回的值越大,而且控制在0~1之間的值。爲此,咱們能夠取函數值加1的倒數(加1是爲了防止除0的狀況):

1/(1+sqrt(pow( 4.5-4 , 2 ) + pow( 1 - 2 , 2)))
0.4721359549995794

接下來咱們就能夠封裝一個基於歐幾里得距離的類似度評價,具體python實現以下:

#歐幾里得距離
def sim_distance( prefs,person1,person2 ):
    si={}
    for itemId in prefs[person1]:
        if itemId in prefs[person2]:
            si[itemId] = 1
    #no same item
    if len(si)==0: return 0
    sum_of_squares = 0.0    
    
    #計算距離 
    sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2) for item in si])
    return 1/(1 + sqrt(sum_of_squares) )

基於測試數據集critics,則能夠計算兩我的的類似度值爲:

sim_distance( critics , 'Toby', 'Mick LaSalle' )
0.307692307692

皮爾遜相關度

皮爾遜相關係數是一種度量兩個變量間相關程度的方法。它是一個介於 1 和 -1 之間的值,其中,1 表示變量徹底正相關, 0 表示無關,-1 表示徹底負相關。

數學公式
$$\frac{\sum x_iy_i-\frac{\sum x_i\sum y_i}{n}}{\sqrt{\sum x_i^2-\frac{(\sum x_i)^2}{n}}\sqrt{\sum y_i^2-\frac{(\sum y_i)^2}{n}}}$$

與歐幾里得距離不一樣,咱們根據兩個用戶來創建笛卡爾座標系,根據用戶對相同電影的評分來創建點,以下圖:
基於皮爾遜相關度的評分結果
在圖上,咱們還能夠看到一條線,因其繪製的原則是儘量的接近圖上全部點,故該線也稱爲最佳擬合線。用皮爾遜方法進行評價時,能夠修正「誇大值」部分,例如某人對電影的要求更爲嚴格,給出分數老是偏低。

python代碼實現:

#皮爾遜相關度 
def sim_pearson(prefs,p1,p2):
    si={}
    for item in prefs[p1]: 
      if item in prefs[p2]: si[item]=1
    
    if len(si)==0: return 0
    
    n=len(si)
    
    #計算開始
    sum1=sum([prefs[p1][it] for it in si])
    sum2=sum([prefs[p2][it] for it in si])
    
    sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
    sum2Sq=sum([pow(prefs[p2][it],2) for it in si])   
    
    pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
    
    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

最後根據critics的數據計算Gene Seymour和Lisa Rose的相關度:

recommendations.sim_pearson(recommendations.critics,'Gene Seymour','Lisa Rose')

爲評論者打分

到此,咱們就能夠根據計算出用戶之間的相關度,並根據相關度來生成相關度列表,找出與用戶口味相同的其餘用戶。

#推薦用戶
def topMatches(prefs,person,n=5,similarity=sim_distance):
    #python列表推導式
    scores=[(similarity(prefs,person,other),other) for other in prefs if other!=person]
    scores.sort()
    scores.reverse()
    return scores[0:n]

推薦物品

對於用戶來講,找出與他具備類似愛好的人並不重要,主要是爲他推薦他可能喜歡的物品,因此咱們還須要根據用戶間類似度進一步計算。例如爲Toby推薦,因爲數據很少,咱們取得全部推薦者的類似度,再乘以他們對電影的評價,得出該電影對於Toby的推薦值,也能夠認爲是Toby可能爲電影打的分數。以下圖:
爲Toby提供推薦
咱們經過計算其餘用戶對某個Toby沒看過電影的加權和來獲得總權重,最後除以類似度和,是爲了防止某一電影被看過的多,總和會更多的影響,也稱「歸一化」處理。

#基於用戶推薦物品
def getRecommendations(prefs,person,similarity=sim_pearson):
    
    totals={}
    simSums={}

    for other in prefs:
        #不和本身作比較
        if other == person: 
            continue
        sim = similarity( prefs,person,other )
        #去除負相關的用戶
        if sim<=0: continue
        for item in prefs[other]:
            #只對本身沒看過的電影作評價
            if item in prefs[person]:continue
            totals.setdefault( item ,0 )
            totals[item] += sim*prefs[other][item]
            simSums.setdefault(item,0)
            simSums[item] += sim
    #歸一化處理生成推薦列表
    rankings=[(totals[item]/simSums[item],item) for item in totals]
    #rankings=[(total/simSums[item],item) for item,total in totals.items()]
    rankings.sort()
    rankings.reverse()
    return rankings

基於物品的協同過濾

以上所講的都是基於用戶間類似的推薦,下面咱們看看基於物品的推薦。

一樣,先構造數據集,即以物品爲key的字典,格式爲{電影:{用戶:評分,用戶:評分}}

#基於物品的列表
def transformPrefs(prefs):
    itemList ={}
    for person in prefs:
        for item in prefs[person]:
            if not itemList.has_key( item ):
                itemList[item]={}
                #result.setdefault(item,{})
            itemList[item][person]=prefs[person][item]
    return itemList

計算物品間的類似度,物品間類似的變化不會像人那麼頻繁,因此咱們能夠構造物品間類似的集合,存成文件重複利用:

#構建基於物品類似度數據集
def calculateSimilarItems(prefs,n=10):
    result={}
    itemPrefs=transformPrefs(prefs)
    c = 0
    for item in itemPrefs:
        c += 1
        if c%10==0: print "%d / %d" % (c,len(itemPrefs))
        scores=topMatches(itemPrefs,item,n=n,similarity=sim_distance)
        result[item]=scores
    return result

基於物品的推薦值計算,經過Toby已看影片的評分,乘以未看影片之間的類似度,來獲取權重。最後歸一化處理以下圖:
爲Toby提供基於物品的推薦

#基於物品的推薦
def getRecommendedItems(prefs,itemMatch,user):
    userRatings=prefs[user]
    scores={}
    totalSim={}
# Loop over items rated by this user
    for (item,rating) in userRatings.items( ):
      # Loop over items similar to this one
      for (similarity,item2) in itemMatch[item]:

        # Ignore if this user has already rated this item
        if item2 in userRatings: continue
        # Weighted sum of rating times similarity
        scores.setdefault(item2,0)
        scores[item2]+=similarity*rating
        # Sum of all the similarities
        totalSim.setdefault(item2,0)
        totalSim[item2]+=similarity

# Divide each total score by total weighting to get an average
    rankings=[(score/totalSim[item],item) for item,score in scores.items( )]

# Return the rankings from highest to lowest
    rankings.sort( )
    rankings.reverse( )
    return rankings

源碼

思考

  1. UserCF和ItemCF的比較

  2. 歸一化處理的更合適方法

  3. 與頻繁模式挖掘的區別

相關文章
相關標籤/搜索