协同过滤算法利用“相似用户喜欢相似物品 or 相似物品被同一用户喜欢”这一协同信号,对用户未接触过的物品进行兴趣预测与推荐。
相似性度量方法
杰卡德相似度
表示用户 u 交互过的物品集合, 表示跟物品 i 交互过的用户集合。
适用于隐式反馈数据(0-1)
余弦相似度
表示用户 u 对物品 i 的分数。
皮尔逊相似度
为用户 u 的所有评分均值。
皮尔逊系数本质是“中心化后”的余弦,换句话说,把每个维度减去各自用户的均值再算余弦,就能得到皮尔逊,这就是皮尔逊能消除“有人爱打高分,有人爱打低分”的打分尺度差异(偏置)的原因。不适用于计算布尔值向量(0-1)之间相关度。
算法评估
召回率
- 系统对用户 u 返回的前K个推荐的物品集合
- 用户 u 实际感兴趣的物品集合(ground truth)
在给定长度为K的候选列表中,系统成功找回了用户真实感兴趣物品的比例。
精确率
返回的前K个候选中,真正感兴趣物品所占比例。
UserCF
基于用户的协同过滤(UserCF):找出和目标用户兴趣模式最相似的一批用户(邻居),用他们对物品的偏好来推测目标用户可能喜欢什么。
| A | B | C | D | |
|---|---|---|---|---|
| U₁ | 5 | 3 | – | 4 |
| U₂ | 4 | – | 3 | – |
| U₃ | – | 4 | – | 5 |
假设以用户为目标用户,推荐他还未评分的物品(相似度用皮尔逊相关系数)。
- 对于而言,共评物品为A。
- 对于而言,共评物品为B,D。
对于用户,我们需要得到他对物品C的评分。
- 若不考虑用户评分的偏置。
- 考虑用户评分偏置。
UserCF存在的两个问题:
- 数据稀疏性问题:在大型电商平台中,物品种类极多,而用户实际购买的物品只占很小一部分,往往不到 1%。这导致用户之间的交集很少,相似用户难以匹配
- 算法扩展性差:UserCF 需要维护一个完整的用户相似度矩阵,用于查找 Top-N 相似用户。这使得存储成本高,计算代价大。 因此,UserCF 不适用于用户规模大、反馈稀疏的系统,这也是为什么很多电商平台在早期推荐系统中更倾向使用 Item-based CF(ItemCF) 来代替 UserCF。
ItemCF
基于物品的协同过滤(ItemCF):两个物品若被同一批用户共同交互,它们就相似;因此若用户喜欢物品A,则它的相似物品B也有可能被用户喜欢。
-
离线阶段:根据用户-物品行为日志,计算物品-物品相似度矩阵
-
在线阶段:对目标用户,找出其已交互物品的相似物品并加权汇总,生成 Top-N 推荐列表
ItemCF和UserCF的算法过程基本一致,唯一的差别是,需要计算的是物品之间的相似度,然后根据目标物品和其他物品的相似度找到top-k个物品,再计算用户对物品的评分。
UserCF:由于是基于用户相似度进行推荐, 所以具备更强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强,追求热点的场合。比如新闻推荐。
ItemCF:- 这个更适用于兴趣变化较为稳定的应用, 更接近于个性化的推荐, 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合。
Swing - 改进的ItemCF
ItemCF存在什么问题?
基于相似度的方法常用于Item-based CF,但是Item-based CF的核心是“用户喜欢A,那么就推荐与A相似的物品B”,这个过程逻辑上等价于走一条路径
但是这个算法把所有 User-Item 行为都“平等对待”
- 并没有区分哪些点击是认真看了,哪种是无意点击。因为实际上每个用户的点击行为价值是不同的。比如用户A是泛娱乐型,什么都点,信号很“水”。用户B只点科技类,信号更加有价值。
因此,容易引入很多噪声,影响推荐精度,存在局限性。
并且对互补性产品的建模不足,可能会导致用户购买过手机之后还继续推荐手机,但用户短时间内不会再继续购买手机,因此产生无效曝光。
Swing算法
Swing算法本质是一种改进的item-item协同过滤算法,通过考虑“用户对”之间的兴趣差异,对物品的相似度进行更精细的加权,从而提升推荐精度和多样性。
Swing的核心思想在于用户对越不相似,它们共同点击两个物品的行为越有价值,应该被重点考虑。
Swing相似度公式:
其中:为点击过物品i的用户集合,为用户u点击过的物品集合,为平滑系数,为用户和的物品交集数。
(平滑系数)的作用
- 避免分母为0。若,分母为0,会导致除零错误
- 约束冷门用户对。 小 α → 冷门用户对的贡献上限越大,模型更敏感、推荐更“激进”。
大 α → 相似度差异被拉平,收敛得更慢、更加保守- 二次惩罚热门用户对
对热门用户(交集大),原本分母就大;再 + 后分母更大,所以这个 可以进一步削弱热门用户对带来的影响。
from itertools import combinations
user_score_dict = {
'A': {'a': 3.0, 'b':4.0, 'c':0.0, 'd':3.5, 'e':0.0},
'B': {'a': 4.0, 'b':0.0, 'c':4.5, 'd':0.0, 'e':3.5},
'C': {'a': 0.0, 'b':3.5, 'c':0.0, 'd':0.0, 'e':3.0},
'D': {'a': 0.0, 'b':4.0, 'c':0.0, 'd':3.5, 'e':3.0},
}
item_user_count = dict() # 每个物品有多少用户产生过行为
user_item_count = dict() # 每个用户交互过哪些物品
for user, score_dict in user_score_dict.items():
for item, score in score_dict.items():
user_item_count.setdefault(user, set())
item_user_count.setdefault(item, set())
if score > 0:
user_item_count[user].add(item)
item_user_count[item].add(user)
item_pairs = list(combinations(item_user_count.keys(), 2))
alpha = 0.5
item_sim_dict = dict()
for (i, j) in item_pairs:
user_pair = list(combinations(item_user_count[i] & item_user_count[j], 2))
result = 0
for (u, v) in user_pair:
result += 1 / (alpha + len(item_user_count[i] & item_user_count[j]))
item_sim_dict.setdefault(i, dict())
item_sim_dict[i][j] = result
item_sim_dict.setdefault(j, dict())
item_sim_dict[j][i] = result
# 计算用户对物品的评分
def UserItemScore(user, item):
score = 0
for i in user_item_count[user]:
if i == item:
continue
score += item_sim_dict[i][item] * user_score_dict[user][i]
return score
# 为用户推荐物品
def recommend(user):
user_item_score = dict()
for item in item_user_count.keys():
user_item_score[item] = UserItemScore(user, item)
# user_item_score = sorted(user_item_score.items(), key=lambda x: x[1], reverse=True)
return user_item_score
print(recommend('C'))
基于swing算法的电影推荐系统实现:github.com/yqqCheergo/…