1. 程式人生 > >《推薦系統》基於使用者和Item的協同過濾演算法的分析與實現(Python)

《推薦系統》基於使用者和Item的協同過濾演算法的分析與實現(Python)


開啟微信掃一掃,關注《資料與演算法聯盟》

1:協同過濾演算法簡介

2:協同過濾演算法的核心

3:協同過濾演算法的應用方式

4:基於使用者的協同過濾演算法實現

5:基於物品的協同過濾演算法實現

一:協同過濾演算法簡介

    關於協同過濾的一個最經典的例子就是看電影,有時候不知道哪一部電影是我們喜歡的或者評分比較高的,那麼通常的做法就是問問周圍的朋友,看看最近有什麼好的電影推薦。在問的時候,都習慣於問跟自己口味差不 多的朋友,這就是協同過濾的核心思想。

   協同過濾是在海量資料中挖掘出小部分與你品味類似的使用者,在協同過濾中,這些使用者成為鄰居,然後根據他們喜歡的東西組織成一個排序的目錄推薦給你。所以就有如下兩個核心問題

   (1)如何確定一個使用者是否與你有相似的品味?

   (2)如何將鄰居們的喜好組織成一個排序目錄?

   協同過濾演算法的出現標誌著推薦系統的產生,協同過濾演算法包括基於使用者和基於物品的協同過濾演算法。

二:協同過濾演算法的核心

      要實現協同過濾,需要進行如下幾個步驟 

      1)收集使用者偏好

      2)找到相似的使用者或者物品

      3)計算並推薦

三:協同過濾演算法的應用方式

1:基於使用者的協同過濾演算法

基於使用者的協同過濾通過不同使用者對物品的評分來評測使用者之間的相似性,基於使用者的相似性做推薦,簡單的講:給使用者推薦和他興趣相投的其他使用者喜歡的物品

演算法實現流程分析:

(1):計算使用者的相似度

計算使用者相似度的方法請參考這篇部落格:點選閱讀  這裡我採用的是餘弦相似度

下面我拿這個圖舉例

    

計算使用者的相似度,例如A,B為

同理

         但是這樣計算的效率是低的,因為我們需要計算每一對使用者之間的相似度,事實上,很多使用者相互之間並沒有對同樣的物品產生過行為,所以很多時候當分子為0的時候沒有必要再去計算分母,所以這裡可以優化:即首先計算出|N(u) 並 N(v)| != 0 的使用者對(u,v),然後對這種情況計算分母以得到兩個使用者的相似度。

針對此優化,需要2步:

     (1)建立物品到使用者的倒查表T,表示該物品被哪些使用者產生過行為;

     (2)根據倒查表T,建立使用者相似度矩陣W:在T中,對於每一個物品i,設其對應的使用者為j,k,在W中,更新相應的元素值,w[j][k]=w[j][k]+1,w[k][j]=w[k][j]+1,以此類推,掃描完倒查表T中的所有物品後,就可以得到最終的使用者相似度矩陣W,這裡的W是餘弦相似度中的分子部分,然後將W除以分母可以得到最終的使用者興趣相似度。

得到使用者的相似度後,便可以進行下一步了

(2):給使用者推薦興趣最相近的k個使用者所喜歡的物品

公式如下:

其中,p(u,i)表示使用者u對物品i的感興趣程度,S(u,k)表示和使用者u興趣最接近的K個使用者,N(i)表示對物品i有過行為的使用者集合,Wuv表示使用者u和使用者v的興趣相似度,Rvi表示使用者v對物品i的興趣(這裡簡化,所有的Rvi都等於1)。

根據UserCF演算法,可以算出,使用者A對物品c、e的興趣是:

2:基於物品的協同過濾演算法

基於item的協同過濾通過不同使用者對不同item的評分來評測item之間的相似性,基於item的相似性做推薦,簡單的講:給使用者推薦和他之前喜歡物品相似的物品

演算法流程分析:

同樣拿上邊的圖舉例,在這裡預設使用者對物品的打分均為1

(1):構建物品的同現矩陣


在這裡對矩陣做歸一化處理就可以得到物品之間的餘弦相似度矩陣了其中歸一化處理

按照標準定義

這裡,分母|N(i)|是喜歡物品i的使用者數,而分子 N(i) N( j) 是同時喜歡物品i和物品j的使用者數。因此,上述公式可以理解為喜歡物品i的使用者中有多少比例的使用者也喜歡物品j。

進行處理後的結果為:


當然為了出現推薦熱門的商品,對上述公式的優化為:

這個公式懲罰了物品j的權重,因此減輕了熱門物品會和很多物品相似的可能性(此種情況下感興趣的自己推導)。

(2):建立使用者對物品的評分矩陣(以A舉例,沒有評分的物品為0)

(3):矩陣計算推薦結果


這裡N(u)是使用者喜歡的物品的集合,S(j,K)是和物品j最相似的K個物品的集合,wji是物品j和i的相似度,rui是使用者u對物品i的興趣。(對於隱反饋資料集,如果使用者u對物品i有過行為,即可令rui=1。)該公式的含義是,和使用者歷史上感興趣的物品越相似的物品,越有可能在使用者的推薦列表中獲得比較高的排名。

推薦結果=同現矩陣 * 評分矩陣


從中去掉A已經打過分的物品,a,b,d,則可以看出,A對e的喜歡程度和c一樣,和上邊計算結果一致,所以就會將兩者推薦給A

3:混合推薦

所謂的混合演算法,主體思路還是基於使用者的協同過濾,只是在計算兩個使用者的相似度時又嵌套了item-based CF思想。

度量使用者i和使用者j相似度更好的方法是:

1.使用者i參與評分的專案集合為IiIi,使用者j參與評分的專案集合為IjIj,找到它們的並集Uij=IiIjUij=Ii∪Ij

2.在集合UijUij中使用者i未評分的專案是Ni=UijIiNi=Uij−Ii,採用item-based CF方法重新估計使用者i對NiNi中每個專案的評分。

3.這樣使用者i和j對UijUij的評分就都是非0值了,在此情況下計算他們的相似度。

四:基於使用者的協同過濾演算法實現

#-*-coding:utf-8-*-
'''
Created on 2016年5月2日

@author: Gamer Think
'''
from math import sqrt

fp = open("uid_score_bid","r")

users = {}

for line in open("uid_score_bid"):
    lines = line.strip().split(",")
    if lines[0] not in users:
        users[lines[0]] = {}
    users[lines[0]][lines[2]]=float(lines[1])


#----------------新增程式碼段END----------------------



class recommender:
    #data:資料集,這裡指users
    #k:表示得出最相近的k的近鄰
    #metric:表示使用計算相似度的方法
    #n:表示推薦book的個數
    def __init__(self, data, k=3, metric='pearson', n=12):

        self.k = k
        self.n = n
        self.username2id = {}
        self.userid2name = {}
        self.productid2name = {}

        self.metric = metric
        if self.metric == 'pearson':
            self.fn = self.pearson
        if type(data).__name__ == 'dict':
            self.data = data
      
    def convertProductID2name(self, id):

        if id in self.productid2name:
            return self.productid2name[id]
        else:
            return id

    #定義的計算相似度的公式,用的是皮爾遜相關係數計算方法
    def pearson(self, rating1, rating2):
        sum_xy = 0
        sum_x = 0
        sum_y = 0
        sum_x2 = 0
        sum_y2 = 0
        n = 0
        for key in rating1:
            if key in rating2:
                n += 1
                x = rating1[key]
                y = rating2[key]
                sum_xy += x * y
                sum_x += x
                sum_y += y
                sum_x2 += pow(x, 2)
                sum_y2 += pow(y, 2)
        if n == 0:
            return 0
        
        #皮爾遜相關係數計算公式 
        denominator = sqrt(sum_x2 - pow(sum_x, 2) / n)  * sqrt(sum_y2 - pow(sum_y, 2) / n)
        if denominator == 0:
            return 0
        else:
            return (sum_xy - (sum_x * sum_y) / n) / denominator
    
    def computeNearestNeighbor(self, username):
        distances = []
        for instance in self.data:
            if instance != username:
                distance = self.fn(self.data[username],self.data[instance])
                distances.append((instance, distance))

        distances.sort(key=lambda artistTuple: artistTuple[1],reverse=True)
        return distances
    
    #推薦演算法的主體函式
    def recommend(self, user):
        #定義一個字典,用來儲存推薦的書單和分數
        recommendations = {}
        #計算出user與所有其他使用者的相似度,返回一個list
        nearest = self.computeNearestNeighbor(user)
        # print nearest
        
        userRatings = self.data[user]
#         print userRatings
        totalDistance = 0.0
        #得住最近的k個近鄰的總距離
        for i in range(self.k):
            totalDistance += nearest[i][1]
        if totalDistance==0.0:
            totalDistance=1.0
            
        #將與user最相近的k個人中user沒有看過的書推薦給user,並且這裡又做了一個分數的計算排名
        for i in range(self.k):
            
            #第i個人的與user的相似度,轉換到[0,1]之間
            weight = nearest[i][1] / totalDistance
            
            #第i個人的name
            name = nearest[i][0]

            #第i個使用者看過的書和相應的打分
            neighborRatings = self.data[name]

            for artist in neighborRatings:
                if not artist in userRatings:
                    if artist not in recommendations:
                        recommendations[artist] = (neighborRatings[artist] * weight)
                    else:
                        recommendations[artist] = (recommendations[artist]+ neighborRatings[artist] * weight)

        recommendations = list(recommendations.items())
        recommendations = [(self.convertProductID2name(k), v)for (k, v) in recommendations]
        
        #做了一個排序
        recommendations.sort(key=lambda artistTuple: artistTuple[1], reverse = True)

        return recommendations[:self.n],nearest
 
def adjustrecommend(id):
    bookid_list = []
    r = recommender(users)
    k,nearuser = r.recommend("%s" % id)
    for i in range(len(k)):
        bookid_list.append(k[i][0])
    return bookid_list,nearuser[:15]        #bookid_list推薦書籍的id,nearuser[:15]最近鄰的15個使用者
資料集的格式如下(點選下載 時間太久了,資料集丟失,抱歉):



程式呼叫:

bookid_list,near_list = adjustrecommend("changanamei")
print ("bookid_list:",bookid_list)
print ("near_list:",near_list)
執行結果:

五:基於物品的協同過濾演算法的實現


參考項亮的《推薦系統實戰》結合上例中的資料進行演算法實現

#-*-coding:utf-8-*-

'''
Created on 2016-5-30

@author: thinkgamer
'''
import math

class ItemBasedCF:
    def __init__(self,train_file):
        self.train_file = train_file
        self.readData()
    def readData(self):
        #讀取檔案,並生成使用者-物品的評分表和測試集
        self.train = dict()     #使用者-物品的評分表
        for line in open(self.train_file):
            # user,item,score = line.strip().split(",")
            user,score,item = line.strip().split(",")
            self.train.setdefault(user,{})
            self.train[user][item] = int(float(score))

    def ItemSimilarity(self):
        #建立物品-物品的共現矩陣
        C = dict()  #物品-物品的共現矩陣
        N = dict()  #物品被多少個不同使用者購買
        for user,items in self.train.items():
            for i in items.keys():
                N.setdefault(i,0)
                N[i] += 1
                C.setdefault(i,{})
                for j in items.keys():
                    if i == j : continue
                    C[i].setdefault(j,0)
                    C[i][j] += 1
        #計算相似度矩陣
        self.W = dict()
        for i,related_items in C.items():
            self.W.setdefault(i,{})
            for j,cij in related_items.items():
                self.W[i][j] = cij / (math.sqrt(N[i] * N[j]))
        return self.W

    #給使用者user推薦,前K個相關使用者
    def Recommend(self,user,K=3,N=10):
        rank = dict()
        action_item = self.train[user]     #使用者user產生過行為的item和評分
        for item,score in action_item.items():
            for j,wj in sorted(self.W[item].items(),key=lambda x:x[1],reverse=True)[0:K]:
                if j in action_item.keys():
                    continue
                rank.setdefault(j,0)
                rank[j] += score * wj
        return dict(sorted(rank.items(),key=lambda x:x[1],reverse=True)[0:N])
    
#宣告一個ItemBased推薦的物件    
Item = ItemBasedCF("uid_score_bid")
Item.ItemSimilarity()
recommedDic = Item.Recommend("xiyuweilan")
for k,v in recommedDic.iteritems():
    print k,"\t",v

執行結果:

1080309     8.18161438413
1119522     11.8165100292
1040104     7.92927319995
1254588     12.8331124639
1082138     7.72409411532
3131626     11.3426906217
26669243     6.96972128519
26305561     6.24816554216
26384985     6.36064881658
1085799     8.20314297616