显示/隐式反馈
类似视频的评分、商品的评分这些为显示反馈;浏览日志、购买日志这些为隐式反馈。
用户行为的统一表示方法
userId、itemId、behaviorType、context(上下文,时间地点等)、behaviorWeight、behaviorContent
常见数据集种类
- 无上下文的隐式反馈数据集:仅记录用户和物品的ID
- 无上下文的显示反馈数据集:记录物品和用户的ID以及评分
- 有上下文的隐示反馈数据集:记录用户和物品的ID以及物品产生行为的时间戳
- 有上下文的显示反馈数据集:记录用户和物品的ID以及评分和时间戳
评价指标
- 召回率:正确推荐的数目/总共喜欢的数目
- 准确率:正确推荐的数目/推荐出来的数目
- 覆盖率:推荐的种类/总类数
- 流行度:顾名思义
- AUC:正确的样本排在错误样本前面的概率
基于用户的协同过滤算法(UserCF)
- 给用户推荐和他兴趣相似的其他用户喜欢的物品,比较社会化。适用于时效性较强的领域,如新闻推荐。
- 步骤:(1)找到和目标用户兴趣类似的用户集合。(2)找到这个集合中用户喜欢的,且目标用户还没有听说过的物品推荐给目标用户。
- 用户相似度计算:(1)Jaccard公式/余弦相似度,适用于用户较少的情况。(2)物品-用户倒排表,一个二维矩阵,行列都是用户ID,以物品为纽带,如果两个用户之间有关联则在矩阵中加一。C[u][v] += 1, W[u][v] = Cuv / math.sqrt(N[u] * N[v])
- 对推荐结果有较大影响的因素:推荐的数目。
- 算法的改进:(1)User-IIF,减少流行物品对相似度的影响。C[u][v] += 1 / math.log(1 + len(user)), W[u][v] = Cuv / math.sqrt(N[u] * N[v])
基于物品的协同过滤算法(ItemCF)
- 给用户推荐和他之前喜欢物品类似的物品,比较个性化。适用于个性化需求较强烈的领域,如一些购物平台。
- 步骤:(1)计算物品之间的相似度。(2)根据物品的相似度和用户的历史行为给用户生成推荐列表。
-
物品相似度计算:用户-物品倒排表(对每个用户建立一个包含他喜欢的物品列表),然后对于每个用户将它物品列表中的物品两两在共现矩阵C加一。
C[i][j] += 1, W[i][j] = Cij / math.sqrt( N[i] * N[j])
-
算法优化:(1)User-IUF,活跃用户对物品相似度的贡献应该小于不活跃用户。
C[i][j] += 1 / math.log(1 + len(item) * 1.0 ), W[i][j] = Cij / math.sqrt( N[i] * N[j])。
(2)User-Norm,归一化相似度矩阵。
UserCF与ItemCF的优缺点对比
UserCF | ItemCF | |
---|---|---|
性能 | 适用于用户较少的场合 | 适用于物品数目明显小于用户数的场合 |
领域 | 时效性较强,个性化兴趣不太明显 | 长尾物品丰富 |
实时性 | 用户有新行为不一定导致推荐结果立即变化 | 用户有新行为一定会导致推荐结果实时变化 |
冷启动 | 在新用户对很少物品产生行为后不能立即进行个性化推荐,因为用户相似度是每隔一段时间离线计算的;新物品上线一段时间后,一旦有用户对物品产生行为就可以将它推给其他人 | 新用户只要对一个物品产生行为就可以给他推荐相关物品;没有办法在不离线更新物品相似度表的情况下将新物品退给用户 |
推荐理由 | 难以提供 | 可以解释 |
核心代码(基于ml-100k数据集)
计算相似度矩阵
def ItemCF_Norm(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
#C[i][j] += 1 / math.log(1 + len(items) * 1.0) # IUF
# 计算相似度矩阵
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]))
# 归一化
for item1, dic in self.W.items():
max = sorted(self.W[item1].items(), key=lambda x: x[1], reverse=True)[0][1]
for item2, sim in dic.items():
self.W[item1][item2] = self.W[item1][item2] / max
self.SaveDict('data/itemcf_norm.data', self.W)
return self.W
带理由的推荐
def Recommend(self, user, K=10, num_recommend=10, num_recent=5):
rank = dict()
watched_item = self.train[user] # 用户user产生过行为的item和评分
recent_item = self.getRecent(user, num_recent) # 看过的并且喜欢(4分以上)的电影按时间排序
for movie, list in recent_item:
for related_movie, w in sorted(self.W[movie].items(), key=lambda x: x[1], reverse=True)[:K]:
if related_movie in watched_item.keys():
continue
rank.setdefault(related_movie, [0, movie])
rank[related_movie][0] += list[0] * w # list[0]是评分 list[1]是时间
return dict(sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:num_recommend])
召回率与准确率的计算
def Recall(self, N=30):
hit = 0
all = 0
for user_id in self.train.keys():
if user_id in self.test.keys():
tu = self.test[user_id]
rank = self.Recommend(user_id, num_recommend=N)
for item_id, pui in rank.items():
if item_id in tu.keys():
hit += 1
all += len(tu)
return hit / (all * 1.0)
def Precision(self, N=30):
hit = 0
all = 0
for user_id in self.train.keys():
if user_id in self.test.keys():
tu = self.test[user_id]
rank = self.Recommend(user_id, num_recommend=N)
for item_id, pui in rank.items():
if item_id in tu.keys():
hit += 1
all += N
return hit / (all * 1.0)