協(xié)同過濾(collaborative filtering)是一種在推薦系統(tǒng)中廣泛使用的技術(shù)。該技術(shù)通過分析用戶或者事物之間的相似性,來預(yù)測用戶可能感興趣的內(nèi)容并將此內(nèi)容推薦給用戶。 這里的相似性可以是人口特征的相似性,也可以是歷史瀏覽內(nèi)容的相似性,還可以是個(gè)人通過一定機(jī)制給與某個(gè)事物的回應(yīng)。比如,A和B是無話不談的好朋友,并且都喜歡看電影,那么協(xié)同過濾會(huì)認(rèn)為A和B的相似度很高,會(huì)將A喜歡但是B沒有關(guān)注的電影推薦給B,反之亦然。 協(xié)同過濾推薦分為3種類型: 基于用戶(user-based)的協(xié)同過濾(UserCF) 文章中的完整源碼、資料、數(shù)據(jù)、技術(shù)交流提升, 均可加知識星球交流群獲取,群友已超過2000人,添加時(shí)切記的備注方式為:來源+興趣方向,方便找到志同道合的朋友vb.net教程C#教程python教程。 算法原理 不知道大家平時(shí)在網(wǎng)上購物的時(shí)候有沒有這樣的體驗(yàn),比如你在網(wǎng)上商城下單了一個(gè)手機(jī),在訂單完成的界面,網(wǎng)頁會(huì)給你推薦同款手機(jī)的手機(jī)殼,你此時(shí)很可能就會(huì)點(diǎn)進(jìn)去瀏覽一下,順便買一個(gè)手機(jī)殼。 其實(shí)這就是ItemCF算法在背后默默工作。ItemCF算法給用戶推薦那些和他們之前喜歡的物品相似的物品。因?yàn)槟阒百I了手機(jī),ItemCF算法計(jì)算出來手機(jī)殼與手機(jī)之間的相似度較大,所以給你推薦了一個(gè)手機(jī)殼,這就是它的工作原理。 看起來是不是跟UserCF算法很相似是不是?只不過這次不再是計(jì)算用戶之間的相似度,而是換成了計(jì)算物品之間的相似度。 由上述描述可以知道ItemCF算法的主要步驟如下: 計(jì)算物品之間的相似度 ItemCF算法并不是直接根據(jù)物品本身的屬性來計(jì)算相似度,而是通過分析用戶的行為來計(jì)算物品之間的相似度。 什么意思呢?比如手機(jī)和手機(jī)殼,除了形狀相似之外沒有什么其它的相似點(diǎn),直接計(jì)算相似度似乎也無從下手。但是換個(gè)角度來考慮這個(gè)問題,如果有很多個(gè)用戶在買了手機(jī)的同時(shí),又買了手機(jī)殼,那是不是可以認(rèn)為手機(jī)和手機(jī)殼比較相似呢vb.net教程C#教程python教程? 最后將篩選出來的物品按照用戶對其感興趣程度逆序排序,取全體列表或者列表前K個(gè)物品推薦給用戶,至此ItemCF算法完成。 算法工作流程 下表是一個(gè)簡易的原始數(shù)據(jù)集,也稱之為User-Item表,即用戶-物品列表,記錄了每個(gè)用戶喜愛的物品,數(shù)據(jù)表格如下: 用戶A的共現(xiàn)矩陣 上圖是用戶A的共現(xiàn)矩陣C,C[i][j]代表的含義是同時(shí)喜歡物品i和物品j的用戶數(shù)量。舉個(gè)例子,C[a][b]=1代表的含義就是同時(shí)喜歡物品a和物品b的用戶有一個(gè),就是用戶A。 下面用一張圖來描述如何構(gòu)建完整的共現(xiàn)矩陣: 通過對不同用戶的喜愛物品集合構(gòu)成的共現(xiàn)矩陣進(jìn)行累加,最終得到了上圖中的矩陣C,這其實(shí)就是相似度公式中的分子部分。 這里只是為了演示如何計(jì)算物品整體共現(xiàn)矩陣,實(shí)際在代碼中我們并不會(huì)采用分別為每個(gè)用戶建立一個(gè)共現(xiàn)矩陣再累加的方式。為了加快效率和節(jié)省空間,可以使用python的dict來實(shí)現(xiàn),具體可以參考代碼實(shí)現(xiàn)章節(jié)。 接下來我們計(jì)算最終的物品相似度矩陣,以物品a和物品b的相似度計(jì)算為例,通過上面計(jì)算的計(jì)算可知C[a][b]=1,即同時(shí)喜歡物品a和物品b的用戶有一位。根據(jù)User-Item表可以統(tǒng)計(jì)出N(a)=2,N(b)=3,那么物品a和物品b的相似度計(jì)算如下: 有了物品相似度矩陣W之后,我們對用戶進(jìn)行物品推薦了,下面以給用戶C推薦物品為例,展示一下計(jì)算過程: P(C,a) = W[d][a] = 0.71 故給用戶C的推薦列表是{b,a,e}。 相似度算法改進(jìn) 假設(shè)有這么一個(gè)用戶,他是開書店的,并且買了當(dāng)當(dāng)網(wǎng)上80%的書準(zhǔn)備用來自己賣。那么他的購物車?yán)锇?dāng)當(dāng)網(wǎng)80%的書。假設(shè)當(dāng)當(dāng)網(wǎng)有100萬本書,也就是說他買了80萬本。從前面對ItemCF的討論可以看到,這意味著因?yàn)榇嬖谶@么一個(gè)用戶,有80萬本書兩兩之間就產(chǎn)生了相似 度,也就是說,內(nèi)存里即將誕生一個(gè)80萬乘80萬的稠密矩陣。 John S. Breese在論文1中提出了一個(gè)稱為IUF(Inverse User Frequence),即用戶活躍度對數(shù)的 倒數(shù)的參數(shù),他也認(rèn)為活躍用戶對物品相似度的貢獻(xiàn)應(yīng)該小于不活躍的用戶,他提出應(yīng)該增加IUF 參數(shù)來修正物品相似度的計(jì)算公式: 上述公式對活躍用戶做了一種軟性的懲罰,但是對于很多過于活躍的用戶,比如上面那位買了當(dāng)當(dāng)網(wǎng)80%圖書的用戶,為了避免相似度矩陣過于稠密,我們在實(shí)際計(jì)算中一般直接忽略他的興趣列表,而不將其納入到相似度計(jì)算的數(shù)據(jù)集中。 相似度矩陣歸一化處理 實(shí)驗(yàn)表明,歸一化的好處不僅僅在于增強(qiáng)推薦的準(zhǔn)確度,還可以提高推薦的覆蓋率和多樣性。 代碼實(shí)踐 訓(xùn)練階段 數(shù)據(jù)預(yù)處理,建立User-Item表 尋找與被推薦用戶喜愛物品集最相似的N個(gè)物品 注意,ratings.dat里面包含了100多萬條評價(jià)數(shù)據(jù),為了減少訓(xùn)練時(shí)間,可以只讀取部分?jǐn)?shù)據(jù),本文讀取了前29415條數(shù)據(jù),即前200個(gè)用戶的評價(jià)數(shù)據(jù)。ratings.dat原始數(shù)據(jù)每行包含了4列,本文中只取了’UserID'、’MovieID'這兩列。 接下來使用以下代碼來讀取數(shù)據(jù)并建立User-Item表: import random import pandas as pd def LoadMovieLensData(filepath, train_rate): ratings = pd.read_table(filepath, sep="::", header=None, names=["UserID", "MovieID", "Rating", "TimeStamp"], engine='python') ratings = ratings[['UserID','MovieID']] train = [] test = [] random.seed(3) for idx, row in ratings.iterrows(): user = int(row['UserID']) item = int(row['MovieID']) if random.random() < train_rate: train.append([user, item]) else: test.append([user, item]) return PreProcessData(train), PreProcessData(test) def PreProcessData(originData): """ 建立User-Item表,結(jié)構(gòu)如下: {"User1": {MovieID1, MoveID2, MoveID3,...} "User2": {MovieID12, MoveID5, MoveID8,...} ... } """ trainData = dict() for user, item in originData: trainData.setdefault(user, set()) trainData[user].add(item) return trainData 建立物品整體共現(xiàn)矩陣 def ItemMatrix(trainData, similarity): """ 建立物品共現(xiàn)矩陣 :param trainData: User-Item表 :param similarity: 相似度計(jì)算函數(shù)選擇 :return: """ N = defaultdict(int) # 記錄每個(gè)物品的喜愛人數(shù) itemSimMatrix = defaultdict(int) # 共現(xiàn)矩陣 for user, items in trainData.items(): for i in items: itemSimMatrix.setdefault(i, dict()) N[i] += 1 for j in items: if i == j: continue itemSimMatrix[i].setdefault(j, 0) if similarity == "cosine": itemSimMatrix[i][j] += 1 elif similarity == "iuf": itemSimMatrix[i][j] += 1. / math.log1p(len(items) * 1.) return itemSimMatrix 建立物品相似度矩陣 def ItemSimilarityMatrix(ItemMatrix, N, isNorm): """ 計(jì)算物品相似度矩陣 :param ItemMatrix: :param N: :param isNorm: :return: """ itemSimMatrix = dict() for i, related_items in ItemMatrix.items(): for j, cij in related_items.items(): # 計(jì)算相似度 itemSimMatrix[i][j] = cij / math.sqrt(N[i] * N[j]) # 是否要標(biāo)準(zhǔn)化物品相似度矩陣 if isNorm: for i, relations in itemSimMatrix.items(): max_num = relations[max(relations, key=relations.get)] # 對字典進(jìn)行歸一化操作之后返回新的字典 itemSimMatrix[i] = {k: v / max_num for k, v in relations.items()} return itemSimMatrix 推薦階段 def recommend(trainData, itemSimMatrix, user, N, K): """ :param trainData: User-Item表 :param itemSimMatrix: 物品相似度矩陣 :param user: 被推薦的用戶user :param N: 推薦的商品個(gè)數(shù) :param K: 查找的最相似的用戶個(gè)數(shù) :return: 按照user對推薦物品的感興趣程度排序的N個(gè)商品 """ recommends = dict() # 先獲取user的喜愛物品列表 items = trainData[user] for item in items: # 對每個(gè)用戶喜愛物品在物品相似矩陣中找到與其最相似的K個(gè) for i, sim in sorted(itemSimMatrix[item].items(), key=itemgetter(1), reverse=True)[:K]: if i in items: continue # 如果與user喜愛的物品重復(fù)了,則直接跳過 recommends.setdefault(i, 0.) recommends[i] += sim # 根據(jù)被推薦物品的相似度逆序排列,然后推薦前N個(gè)物品給到用戶 return dict(sorted(recommends.items(), key=itemgetter(1), reverse=True)[:N]) 核心代碼 import math import random import pandas as pd from collections import defaultdict from operator import itemgetter def LoadMovieLensData(filepath, train_rate): ratings = pd.read_table(filepath, sep="::", header=None, names=["UserID", "MovieID", "Rating", "TimeStamp"], engine='python') ratings = ratings[['UserID','MovieID']] train = [] test = [] random.seed(3) for idx, row in ratings.iterrows(): user = int(row['UserID']) item = int(row['MovieID']) if random.random() < train_rate: train.append([user, item]) else: test.append([user, item]) return PreProcessData(train), PreProcessData(test) def PreProcessData(originData): """ 建立User-Item表,結(jié)構(gòu)如下: {"User1": {MovieID1, MoveID2, MoveID3,...} "User2": {MovieID12, MoveID5, MoveID8,...} ... } """ trainData = dict() for user, item in originData: trainData.setdefault(user, set()) trainData[user].add(item) return trainData class ItemCF(object): """ Item based Collaborative Filtering Algorithm Implementation""" def __init__(self, trainData, similarity="cosine", norm=True): self._trainData = trainData def similarity(self): N = defaultdict(int) #記錄每個(gè)物品的喜愛人數(shù) for user, items in self._trainData.items(): for i in items: self._itemSimMatrix.setdefault(i, dict()) N[i] += 1 for j in items: if i == j: continue self._itemSimMatrix[i].setdefault(j, 0) if self._similarity == "cosine": self._itemSimMatrix[i][j] += 1 elif self._similarity == "iuf": self._itemSimMatrix[i][j] += 1. / math.log1p(len(items) * 1.) for i, related_items in self._itemSimMatrix.items(): for j, cij in related_items.items(): self._itemSimMatrix[i][j] = cij / math.sqrt(N[i]*N[j]) # 是否要標(biāo)準(zhǔn)化物品相似度矩陣 if self._isNorm: for i, relations in self._itemSimMatrix.items(): max_num = relations[max(relations, key=relations.get)] # 對字典進(jìn)行歸一化操作之后返回新的字典 self._itemSimMatrix[i] = {k : v/max_num for k, v in relations.items()} def recommend(self, user, N, K): """ :param user: 被推薦的用戶user :param N: 推薦的商品個(gè)數(shù) :param K: 查找的最相似的用戶個(gè)數(shù) :return: 按照user對推薦物品的感興趣程度排序的N個(gè)商品 """ recommends = dict() # 先獲取user的喜愛物品列表 items = self._trainData[user] for item in items: # 對每個(gè)用戶喜愛物品在物品相似矩陣中找到與其最相似的K個(gè) for i, sim in sorted(self._itemSimMatrix[item].items(), key=itemgetter(1), reverse=True)[:K]: if i in items: continue # 如果與user喜愛的物品重復(fù)了,則直接跳過 recommends.setdefault(i, 0.) recommends[i] += sim # 根據(jù)被推薦物品的相似度逆序排列,然后推薦前N個(gè)物品給到用戶 return dict(sorted(recommends.items(), key=itemgetter(1), reverse=True)[:N]) if __name__ == "__main__": train, test = LoadMovieLensData("../Data/ml-1m/ratings.dat", 0.8) print("train data size: %d, test data size: %d" % (len(train), len(test))) ItemCF = ItemCF(train, similarity='iuf', norm=True) ItemCF.train() # 分別對以下4個(gè)用戶進(jìn)行物品推薦 print(ItemCF.recommend(1, 5, 80)) 上述代碼對測試集中前4個(gè)用戶進(jìn)行了電影推薦,對每個(gè)用戶而言,從與他們喜愛電影相似的80部電影中挑選出5部推薦。 輸出結(jié)果是一個(gè)dict,里面包含了給用戶推薦的電影以及用戶對每部電影的感興趣程度,按照逆序排列。 結(jié)果如下: 版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。 原文鏈接:https://blog.csdn.net/m0_59596990/article/details/130459464 |
|