推荐系统的发展一日千里
传统的推荐模型(2010年前后):协同过滤、罗辑回归、因子分解、梯度提升树
深度学习推荐模型(2015年后):…
在深度学习推荐模型成为推荐、广告、搜索领域的主流,但是传统推荐模型学习十分必要。因其为深度学习推荐模型的基础,并且具备可解释性强、硬件环境要求低,易于快速训练和部署等不可替代的优势。
协同过滤–Collaborative Filtering(CF)
基本思想:相似的人有相似的行为(购买),相似的物品有相似的属性(被喜欢)。
基本流程:数据表示、定义相似、找相似TopN、按照相似候选集进行推荐决策。
三类协同过滤算法:
特征向量表示用户/物品。
常用可解释特征:
数据的高级语义特征,拓扑结构特征需要专门建模分析。
挖掘数据特征表示本就是一个重要的研究方向吧。
向量本身具有长度和方向两个属性,当存在两个向量相互作用时,还存在向量间夹角的问题。因此可以长度、夹角等不同的侧重点,定义不同的相似性度量。
常用的相似性度量指标
是用户和商品对某一属性行为的矩阵表示,例如购买,点击,好评等行为。(缺陷就是只能表示一种属性。)
简单举例:以用户作为矩阵行坐标,商品作为矩阵的列坐标。矩阵中的元素就是某个用户是否购买了某件商品。
问题:是否推荐物品A给用户X
主要缺陷:
实际在应用过程中,Amazon 和 Netflix 采用的是ItemCF 构建推荐系统。
通过计算物品列向量的相似度–》物品间相似度矩阵
强调:相似物品集合是 目标用户有正反馈的物品的相似物品
注意:多个正反馈物品相似于同一个物品,这个物品的相似度得分取多个相似度的加权平均值。
主要缺陷:推荐结果的头部效应比较明显,处理稀疏向量的能力比较弱
为了解决上述问题,同时增加模型的泛化能力,矩阵分解技术被提出来(下一篇讲)
用更稠密的隐向量表示用户和物品,挖掘用户的隐藏兴趣和隐藏特征。
(想找一个有关于推荐系统所有算法的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 pd
def 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, 0
if(0!=nlist.size):
nv = df.loc[nlist.index,:].T.mean() # 相关有评分人对所有电影的评分的平均值
for index in nlist.index: # 相关评论人userx
ncor = 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]] = None
return 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,recomm
if __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')]