協同過濾
- 1.CF概述
- 2.數據表示
- 3.衡量相似度
- 4.共現矩陣
- 5.UserCF
- 6.ItemCF
- 7.UserCF 與ItemCF 應用場景、主要缺陷
- 8.基于UserCF 電影推薦demo
《深度學習/推薦系統》讀書筆記
推薦系統的發展一日千里
傳統的推薦模型(2010年前后):協同過濾、羅輯回歸、因子分解、梯度提升樹
深度學習推薦模型(2015年后):…
在深度學習推薦模型成為推薦、廣告、搜索領域的主流,但是傳統推薦模型學習十分必要。因其為深度學習推薦模型的基礎,并且具備可解釋性強、硬件環境要求低,易于快速訓練和部署等不可替代的優勢。
1.CF概述
協同過濾–Collaborative Filtering(CF)
- 1992年–Xerox研發基于協同過濾的郵件篩選系統
- 2003年–Amazon發表論文《Amazon.com Recommenders Item-to-Item Collaborative Filtering》,促使CF成為使用熱點
基本思想:相似的人有相似的行為(購買),相似的物品有相似的屬性(被喜歡)。
基本流程:數據表示、定義相似、找相似TopN、按照相似候選集進行推薦決策。
三類協同過濾算法:
- 用戶協同過濾(UserCF):目標用戶與相似用戶的喜好相似
- 物品協同過濾(ItemCF):給用戶推薦有正反饋物品的相似物品
- 矩陣分解協同過濾(Matrix Factorization,MF):解決共現矩陣稀疏的問題。
2.數據表示
特征向量表示用戶/物品。
常用可解釋特征:
- 用戶向量–購買行為,評價行為,性別,年齡
- 物品向量–商品的類別,商品描述,用戶反饋信息
數據的高級語義特征,拓撲結構特征需要專門建模分析。
挖掘數據特征表示本就是一個重要的研究方向吧。
3.衡量相似度
向量本身具有長度和方向兩個屬性,當存在兩個向量相互作用時,還存在向量間夾角的問題。因此可以長度、夾角等不同的側重點,定義不同的相似性度量。
常用的相似性度量指標
- 距離度量–考察差異向量的長度∣∣x?y∣∣p||x-y||_p∣∣x?y∣∣p?–1范數、2范數、無窮范數
- 余弦相似度:考察特征向量之間的夾角(x,y)–cos(x,y)=<x,y>∣∣x∣∣?∣∣y∣∣cos(x,y) = \frac{<x,y>}{||x||*||y||}cos(x,y)=∣∣x∣∣?∣∣y∣∣<x,y>?
- 皮爾遜相關系數:與推薦內容十分相關的定義式
4.共現矩陣
是用戶和商品對某一屬性行為的矩陣表示,例如購買,點擊,好評等行為。(缺陷就是只能表示一種屬性。)
簡單舉例:以用戶作為矩陣行坐標,商品作為矩陣的列坐標。矩陣中的元素就是某個用戶是否購買了某件商品。
5.UserCF
問題:是否推薦物品A給用戶X
- 收集各用戶對商品庫物品購買行為的共現矩陣(矩陣中缺失值的處理:取平均,直接去除)
- 計算TopN 相似用戶
- 依據TopN 相似用戶對物品A的購買行為,確定是否推薦物品A給用戶X(意見不統一的采用投票法,評分加權平均法)
主要缺陷:
- 用戶數遠大于物品數,維護用戶相似度矩陣的存儲開銷大。(兩兩相似N^2的空間)
- 用戶歷史數據非常稀疏,只有幾次購買或者點擊行為的用戶很難準確找到相似用戶。
6.ItemCF
實際在應用過程中,Amazon 和 Netflix 采用的是ItemCF 構建推薦系統。
通過計算物品列向量的相似度–》物品間相似度矩陣
- 構建共現矩陣[m*n]–m個用戶, n件商品;
- 計算共現矩陣列向量之間的兩兩相似度,構建[n*n]物品相似度矩陣;
- 獲取目標用戶歷史行為中有正反饋的物品列表;
- 利用物品相似度矩陣找出相似的TopK物品,構成相似物品集合;
- 依據相似度得分,生成最終推薦列表
強調:相似物品集合是 目標用戶有正反饋的物品的相似物品
注意:多個正反饋物品相似于同一個物品,這個物品的相似度得分取多個相似度的加權平均值。
7.UserCF 與ItemCF 應用場景、主要缺陷
- UserCF:用戶相似度,使其具備更強的社交特性,得知自己的同類最近喜歡什么,適用于新聞推薦場景,發現熱點跟蹤人點趨勢。
- ItemCF:適用于興趣變化較為穩定的應用,用戶在一個時間段傾向于尋找類似上商品。電影,書籍,電視劇這種喜好風格,一段時間內變化比較小。
主要缺陷:推薦結果的頭部效應比較明顯,處理稀疏向量的能力比較弱
- 泛化能力弱,無法將兩個物品相似推廣到其他物品相似的計算上
- 熱門商品具有很強的頭部效應,更大家都相似。
- 而尾部商品由于特征向量稀疏,跟大家都不相似,導致很少被推薦。
為了解決上述問題,同時增加模型的泛化能力,矩陣分解技術被提出來(下一篇講)
用更稠密的隱向量表示用戶和物品,挖掘用戶的隱藏興趣和隱藏特征。
8.基于UserCF 電影推薦demo
(想找一個有關于推薦系統所有算法的repository,沒有找到合適的,尤其是關于經典推薦算法。動手敲一敲唄。)
數據:用戶對電影的評分
數據格式:
# [用戶ID,電影ID,電影評分,時間標簽] 8W條數據
1 1 2 3 876893171
2 1 3 4 878542960
3 1 4 3 876893119
4 1 5 3 889751712
基本思路:預測用戶未評分的電影評分,給該每個用戶推薦預測評分最高的TopN 個電影。預測的依據–相似用戶對該電影評分的加權平均效果。
代碼參考《python與數據挖掘》第10章
demo代碼與數據
# 20210424 UserCF demo
# movie recommend
import pandas as pddef prediction(df, userdf, Nn=15):# 預測用戶未評分電影的評分corr = df.T.corr() # 計算用戶的相關person相關系數矩陣rats = userdf.copy()for usrid in userdf.index:print(usrid)# step1:獲取用戶未評分電影dfnull = df.loc[usrid][df.loc[usrid].isnull()] # 用戶user1沒有評分的電影:('mov6',nan)usrv = df.loc[usrid].mean() # 用戶user1電影評分的均值# step2: 預測未評分電影的分值for i in range(len(dfnull)):nft = (df[dfnull.index[i]]).notnull() # 用戶user1沒有評分的電影,其他人評分與否if(Nn <= len(nft)):nlist = df[dfnull.index[i]][nft][:Nn] # 用戶user1沒有評分的電影,前Nn有評分的人else:nlist = df[dfnull.index[i]][nft][:len(nft)] # len(df[dfnull.index[i]][nft]) < len(nft), 有啥用呢# 1)獲取非null相關系數,有評分人列表nlist = nlist[corr.loc[usrid, nlist.index].notnull()] # 用戶user1 和 有評分人的非null 相關系數的評分人列表nratsum, corsum = 0, 0if(0!=nlist.size):nv = df.loc[nlist.index,:].T.mean() # 相關有評分人對所有電影的評分的平均值for index in nlist.index: # 相關評論人userxncor = corr.loc[usrid, index] # 用戶user1 和 userx相關系數nratsum += ncor*(df[dfnull.index[i]][index]-nv[index]) # ncor*(df['mov6'][userx]-nv[userx])corsum += abs(ncor)if(corsum != 0):rats.at[usrid, dfnull.index[i]] = usrv + nratsum/corsum # 預測用戶user1對沒有評分電影的評分else:rats.at[usrid, dfnull.index[i]] = usrv # 無其他用戶評分修正的情況下,用自己的評分均值填補else:rats.at[usrid,dfnull.index[i]] = Nonereturn rats
def recomm(df, userdf, Nn=15, TopN=3):# 依據為未評分電影預測評分,給出每個用戶的推薦列表。ratings = prediction(df, userdf, Nn)recomm = []for usrid in userdf.index:# 按Nan值獲取未評分項ratft = userdf.loc[usrid].isnull()ratnull = ratings.loc[usrid][ratft]# 對預測評分項進行排序if(len(ratnull) >= TopN):sortlist = (ratnull.sort_values(ascending=False)).index[:TopN]else:sortlist = ratnull.sort_values(ascending=False).index[:len(ratnull)]recomm.append(sortlist)return ratings,recommif __name__ == "__main__":print("------使用基于UserCF算法對電影進行推薦中...-----")traindata = pd.read_csv("./data/Chapter10/u1.base", sep='\t', index_col=None, header=None) # [用戶ID,電影ID,電影評分,時間標簽] 8W條數據print(traindata.head())testdata = pd.read_csv("./data/Chapter10/u1.test", sep='\t', index_col=None, header=None)traindata = traindata[:1000]testdata = testdata[:1000]# 刪除時間列--本例中沒有用traindata.drop(3, axis=1, inplace=True)testdata.drop(3, axis=1, inplace=True)# 行與列重新命名traindata.rename(columns={0: 'userid', 1: 'movid', 2: 'rat'}, inplace=True)testdata.rename(columns={0: 'userid', 1: 'movid', 2: 'rat'}, inplace=True)# 整理成共現矩陣traindf = traindata.pivot(index='userid', columns='movid', values='rat')testdf = testdata.pivot(index='userid', columns='movid', values='rat')# 重命名表格的行和列traindf.rename(index={i: 'user%d'%i for i in traindf.index}, inplace=True)testdf.rename(index={i: 'user%d'%i for i in testdf.index}, inplace=True)traindf.rename(columns={i: 'mov%d'%i for i in traindf.columns}, inplace=True)testdf.rename(columns={i: 'mov%d'%i for i in testdf.columns}, inplace=True)print('d', traindata.head())userdf = traindf.loc[testdf.index]trainrats, trainrecom = recomm(traindf, userdf)print(trainrecom[:1])print(len(trainrecom))print('end')
輸出:
# 每個用戶的推薦電影列表,每人推薦3部
[Index(['mov189', 'mov396', 'mov390'], dtype='object', name='movid')]