协同过滤
2.1 简介
协同过滤(Collaborative Filtering)是推荐系统中最基础的技术之一。它的基本想法很简单:如果两个用户过去喜欢相似的物品,那么他们未来也可能有相似的偏好。或者反过来说,如果两个物品被相似的用户群体喜欢,那么喜欢其中一个物品的用户也可能喜欢另一个。
协同过滤主要分为两大类:基于用户的协同过滤(User-Based Collaborative Filtering)和基于物品的协同过滤(Item-Based Collaborative Filtering)。无论是哪种方法,核心思想都是利用用户与物品之间的交互数据来进行推荐。
这是两种传统方法,复杂一些的有矩阵分解这种基于模型的方法,通过学习用户和物品的低维向量表示来进行推荐。
2.2 基于用户的协同过滤
具有相似历史行为的用户,未来偏好也相似。
UserCF是最早提出的协同过滤方法。它的工作原理很直观:先找到与目标用户购买偏好相似的“邻居用户”,然后基于这些邻居对商品的选择来预测目标用户的偏好。
2.2.1 相似度计算
首先我们两两配对用户的时候,需要衡量他们的“相似”程度,常用的方法有杰卡德相似度、余弦相似度、皮尔逊相关系数等。
杰卡德相似度:
余弦相似度:
皮尔逊相关系数:
2.2.2 候选物品推荐
那有了相似度指标之后,我们需要为目标用户推荐物品,预测目标用户对未评分物品的兴趣度。
**简单加权平均:**最直接的方法是用相似度作为权重,对邻居的评分进行加权平均:
这里,是用户对物品的预测评分,是与用户相似的邻居用户集合,是用户和之间的相似度,是用户对物品的实际评分。
考虑评分偏置的版本:为了进一步消除个人评分习惯的影响,我们可以加入偏置修正:
这里,和分别是用户和的平均评分。先看邻居们对这个物品的评分相比他们平均水平如何,然后根据目标用户的平均评分水平进行调整。
2.2.3 算法优化策略
基于物品倒排表的优化:
- 构建倒排表:为每个物品维护一个用户列表,记录哪些用户对这个物品有过行为。这样就可以通过物品快速找到相关用户。
- 稀疏矩阵计算:创建一个矩阵来记录用户和的共同物品数量。遍历每个物品的用户列表,将列表中的用户两两配对,对应的值加1。
- 计算最终相似度:矩阵给出了余弦相似度公式的分子,再除以分母就得到了用户相似度。
- 生成推荐:使用以下公式计算用户对物品的兴趣分数:
其中,是与用户最相似的用户集合,是对物品的物品有过行为的用户集合实际推荐时,针对目标用户未交互过的物品计算上述兴趣度量值,并按分值降序排列,选择Top-N物品作为推荐结果。
新算法的时间复杂度约为。其中,是总的用户-物品交互记录数,是每个物品的平均用户数,在稀疏数据场景下显示出了更好的性能。
2.2.4 code实践
import numpy as np
user_data = {
'user1': {'item1': 5, 'item2': 3, 'item3': 4, 'item4': 4},
'user2': {'item1': 3, 'item2': 1, 'item3': 2, 'item4': 3, 'item5': 3},
'user3': {'item1': 4, 'item2': 3, 'item3': 4, 'item4': 3, 'item5': 5},
'user4': {'item1': 3, 'item2': 3, 'item3': 1, 'item4': 5, 'item5': 4},
'user5': {'item1': 1, 'item2': 5, 'item3': 5, 'item4': 2, 'item5': 1},
}
# 我们计划预测用户1对物品5的评分
similarity_matrix = pd.DataFrame(
np.identity(len(user_data)),
index=user_data.keys(),
columns=user_data.keys(),
)
# 首 先计算他们的相似度:
# 遍历每条用户-物品评分数据
for u1, items1 in user_data.items():
for u2, items2 in user_data.items():
if u1 == u2:
continue
vec1, vec2 = [], []
for item, rating1 in items1.items():
rating2 = items2.get(item, -1)
if rating2 == -1:
continue
vec1.append(rating1)
vec2.append(rating2)
# 计算不同用户之间的皮尔逊相关系数
similarity_matrix[u1][u2] = np.corrcoef(vec1, vec2)[0][1]
print(similarity_matrix)
target_user = 'user1'
num = 2
# 由于最相似的用户为自己,去除本身
sim_users = similarity_matrix[target_user].sort_values(ascending=False)[1:num+1].index.tolist()
print(f'与用户{target_user}最相似的{num}个用户为:{sim_users}')
weighted_scores = 0.
corr_values_sum = 0.
target_item = 'item5'
# 基于皮尔逊相关系数预 测用户评分
for user in sim_users:
corr_value = similarity_matrix[target_user][user]
user_mean_rating = np.mean(list(user_data[user].values()))
weighted_scores += corr_value * (user_data[user][target_item] - user_mean_rating)
corr_values_sum += corr_value
target_user_mean_rating = np.mean(list(user_data[target_user].values()))
target_item_pred = target_user_mean_rating + weighted_scores / corr_values_sum
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred:.4f}')
import os
import sys
import funrec
from funrec.utils import build_metrics_table
# 加载配置
config = funrec.load_config('user_cf')
# 加载数据
train_data, test_data = funrec.load_data(config.data)
# 准备特征
feature_columns, processed_data = funrec.prepare_features(config.features, train_data, test_data)
# 训练模型
models = funrec.train_model(config.training, feature_columns, processed_data)
# 评估模型
metrics = funrec.evaluate_model(models, processed_data, config.evaluation, feature_columns)
print(build_metrics_table(metrics))
2.4 基于物品的协同过滤
ItemCF关注的是“和你喜欢的物品相似的还有什么”。
实际上ItemCF的计算过程与UserCF类似,只是计算的对象从用户到物品,相似度的计算也从用户之间到物品之间。
- 物品相似度计算(余弦相似度计算一个共现矩阵出来)
- 候选物品推荐(皮尔逊相关系数评分,基于该评分预测)
2.4.1 code实践
import numpy as np
item_data = {
'item1': {'user1': 5, 'user2': 3, 'user3': 4, 'user4': 3, 'user5': 1},
'item2': {'user1': 3, 'user2': 1, 'user3': 3, 'user4': 3, 'user5': 5},
'item3': {'user1': 4, 'user2': 2, 'user3': 4, 'user4': 1, 'user5': 5},
'item4': {'user1': 4, 'user2': 3, 'user3': 3, 'user4': 5, 'user5': 2},
'item5': {'user2': 3, 'user3': 5, 'user4': 4, 'user5': 1},
}
similarity_matrix = pd.DataFrame(
np.identity(len(item_data)),
index=item_data.keys(),
columns=item_data.keys(),
)
# 遍历每条物品-用户评分数据
for i1, users1 in item_data.items():
for i2, users2 in item_data.items():
if i1 == i2:
continue
vec1, vec2 = [], []
for user, rating1 in users1.items():
rating2 = users2.get(user, -1)
if rating2 == -1:
continue
vec1.append(rating1)
vec2.append(rating2)
similarity_matrix[i1][i2] = np.corrcoef(vec1, vec2)[0][1]
print(similarity_matrix)
target_user = 'user1'
target_item = 'item5'
num = 2
sim_items = []
sim_items_list = similarity_matrix[target_item].sort_values(ascending=False).index.tolist()
for item in sim_items_list:
# 如果target_user对物品item评分过
if target_user in item_data[item]:
sim_items.append(item)
if len(sim_items) == num:
break
print(f'与物品{target_item}最相似的{num}个物品为:{sim_items}')
target_user_mean_rating = np.mean(list(item_data[target_item].values()))
weighted_scores = 0.
corr_values_sum = 0.
for item in sim_items:
corr_value = similarity_matrix[target_item][item]
user_mean_rating = np.mean(list(item_data[item].values()))
weighted_scores += corr_value * (item_data[item][target_user] - user_mean_rating)
corr_values_sum += corr_value
target_item_pred = target_user_mean_rating + weighted_scores / corr_values_sum
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred}')
import os
import sys
import funrec
from funrec.utils import build_metrics_table
# 加载配置
config = funrec.load_config('item_cf')
# 加载数据
train_data, test_data = funrec.load_data(config.data)
# 准备特征
feature_columns, processed_data = funrec.prepare_features(config.features, train_data, test_data)
# 训练模型
models = funrec.train_model(config.training, feature_columns, processed_data)
# 评估模型
metrics = funrec.evaluate_model(models, processed_data, config.evaluation, feature_columns)
print(build_metrics_table(metrics))
2.5 Swing算法
如果用户在不小心点到或者出于好奇心驱使,但并不想自己的首页充斥着无关内容,Swing算法就能派上用场。它关注的是鲁棒性较强的共同购买关系,以此度量物品相似性。
如果多个用户在其他共同购买行为较少的情况下,同时购买了同一对物品,那么这对物品之间的关联性就更可信。
Swing算法的基本步骤是:
- 构建用户-物品二部图
- 分析图中用户对的共同交互模式(即swing结构)来计算物品相似度。
2.5.1 用户-物品二部图构建
这个二部图描述的是用户与物品的交互关系。
在这个图中,一边是所有用户节点,另一边是所有物品节点。如果用户对某个物品发生了点击、购买等行为,就在对应的用户节点与物品节点之间添加一条边。这个二部图为后续的相似度计算提供了结构化的数据基础。
2.5.2 物品相似度计算
我们基于该图计算任意一对物品i与j的相似度。设