基于用户的协同过滤算法详解

Date: October 29, 2025

Author: AI 算法助手

Tags: 推荐系统,协同过滤,机器学习,Python


目录

  1. 什么是协同过滤

  2. 基于用户的协同过滤原理

  3. 相似度计算方法

  4. 算法实现步骤

  5. 实验验证

  6. 性能评估

  7. 优缺点分析

  8. 总结与展望


什么是协同过滤

协同过滤(Collaborative Filtering)是推荐系统中最经典的算法之一,其核心思想是 “相似的用户喜欢相似的物品”。

协同过滤的两种主要类型

  • 基于用户的协同过滤(User-Based CF):找到与目标用户兴趣相似的其他用户,推荐这些相似用户喜欢的物品

  • 基于物品的协同过滤(Item-Based CF):找到与目标用户喜欢的物品相似的其他物品进行推荐

本文将详细介绍基于用户的协同过滤算法。


基于用户的协同过滤原理

核心假设

“如果用户 A 和用户 B 有相似的兴趣偏好,那么用户 A 喜欢的物品很可能也会被用户 B 喜欢”

算法流程

基于用户的协同过滤推荐主要包含两个关键步骤:

  1. 计算用户相似度:找到与目标用户兴趣相似的其他用户

  2. 生成推荐列表:根据相似用户的喜好为目标用户推荐物品


相似度计算方法

在无评分的二值数据场景下(只有物品是否被拥有的信息),常用的相似度计算方法有三种:

1. Common Neighbors(共同邻居)相似度

定义:直接计算两个用户共同拥有的物品数量

def cn_similarity(items1: set, items2: set) -> int:

   """Common Neighbors相似度:共同物品的数量"""

   return len(items1 & items2)

特点:计算简单,适用于二值数据,但缺乏归一化

2. Jaccard(杰卡德)相似度

定义:两个集合交集与并集的比例

def jaccard_similarity(items1: set, items2: set) -> float:

   """Jaccard相似度:交集/并集"""

   intersection = len(items1 & items2)

   union = len(items1 | items2)

   return 0.0 if union == 0 else intersection / union

公式

J(u, v) = |I(u) ∩ I(v)| / |I(u) ∪ I(v)|

特点:取值范围 [0,1],适合衡量集合相似程度

3. 余弦相似度

定义:将用户物品集合视为二进制向量,计算向量夹角的余弦值

import math

def cosine_similarity(items1: set, items2: set) -> float:

   """余弦相似度:共同物品数/(√物品数1 × √物品数2)"""

   intersection = len(items1 & items2)

   norm1 = math.sqrt(len(items1))

   norm2 = math.sqrt(len(items2))

   return 0.0 if (norm1 * norm2) == 0 else intersection / (norm1 * norm2)

公式

cos(u, v) = |I(u) ∩ I(v)| / (√|I(u)| × √|I(v)|)

特点:对向量长度不敏感,注重方向相似性


算法实现步骤

步骤 1:准备实验数据

# 用户-物品关联数据

user_items = {

   'A': {'b', 'd'},      # 用户A拥有物品b和d

   'B': {'a', 'b', 'c'}, # 用户B拥有物品a、b和c

   'C': {'a', 'b', 'd'}, # 用户C拥有物品a、b和d

   'D': {'a', 'e'}       # 用户D拥有物品a和e

}

步骤 2:实现推荐函数

from collections import defaultdict

def recommend_items(

   target_user: str,

   k: int = 2,

   top_n: int = 2,

   sim_method = jaccard_similarity

) -> list:

   """

   为目标用户推荐物品

  

   Args:

       target_user: 目标用户ID

       k: 参考的相似用户数量

       top_n: 返回的推荐物品数量

       sim_method: 相似度计算方法

      

   Returns:

       list: 推荐物品列表

   """

   # 获取目标用户已拥有的物品

   target_owned = user_items[target_user]

  

   # 步骤1:计算与其他用户的相似度

   similarities = []

   for other_user, items in user_items.items():

       if other_user == target_user:

           continue

       similarity = sim_method(target_owned, items)

       if similarity > 0:

           similarities.append((similarity, other_user))

  

   # 步骤2:选择最相似的k个用户

   similarities.sort(key=lambda x: x[0], reverse=True)

   top_k_users = similarities[:k]

  

   if not top_k_users:

       return []

  

   # 步骤3:计算物品推荐分数

   item_scores = defaultdict(float)

   for similarity, user in top_k_users:

       # 相似用户拥有但目标用户没有的物品

       candidate_items = user_items[user] - target_owned

       for item in candidate_items:

           item_scores[item] += similarity

  

   # 步骤4:返回推荐分数最高的top_n个物品

   sorted_items = sorted(item_scores.items(), key=lambda x: x[1], reverse=True)

   return [item for item, score in sorted_items[:top_n]]

实验验证

实验设计

实验参数

  • 目标用户:A、D

  • 相似用户数量 k:2、3

  • 推荐物品数量 top_n:2

  • 相似度算法:Jaccard、余弦、Common Neighbors

实验结果

用户 A 的推荐结果
# 为用户A推荐物品

print("用户A推荐结果(Jaccard):", recommend_items('A', k=2, sim_method=jaccard_similarity))

print("用户A推荐结果(余弦):", recommend_items('A', k=2, sim_method=cosine_similarity))

print("用户A推荐结果(CN):", recommend_items('A', k=2, sim_method=cn_similarity))

输出

用户 A 推荐结果(Jaccard): [‘a’, ‘c’]

用户 A 推荐结果(余弦): [‘a’, ‘c’]

用户 A 推荐结果(CN): [‘a’, ‘c’]

用户 D 的推荐结果
# 为用户D推荐物品

print("用户D推荐结果(Jaccard):", recommend_items('D', k=2, sim_method=jaccard_similarity))

print("用户D推荐结果(余弦):", recommend_items('D', k=2, sim_method=cosine_similarity))

print("用户D推荐结果(CN):", recommend_items('D', k=2, sim_method=cn_similarity))

输出

用户 D 推荐结果(Jaccard): [‘b’, ‘c’]

用户 D 推荐结果(余弦): [‘b’, ‘c’]

用户 D 推荐结果(CN): [‘b’, ‘c’]


性能评估

评估指标

def evaluate_recommendation(recommended_items, expected_items):

   """评估推荐结果的性能"""

   recommended_set = set(recommended_items)

   expected_set = set(expected_items)

  

   intersection = recommended_set & expected_set

   precision = len(intersection) / len(recommended_set) if recommended_set else 0

   recall = len(intersection) / len(expected_set) if expected_set else 0

   f1 = 2 * precision * recall / (precision + recall) if (precision + recall) != 0 else 0

  

   return {

       'precision': precision,

       'recall': recall,

       'f1': f1,

       'hit': intersection

   }

评估结果

用户 A 的性能评估
# 预期推荐物品

expected_a = ['a', 'c']

result_a = recommend_items('A', k=2)

eval_a = evaluate_recommendation(result_a, expected_a)

print(f"用户A评估结果:")

print(f"推荐物品: {result_a}")

print(f"预期物品: {expected_a}")

print(f"命中物品: {eval_a['hit']}")

print(f"精确率: {eval_a['precision']:.4f}")

print(f"召回率: {eval_a['recall']:.4f}")

print(f"F1分数: {eval_a['f1']:.4f}")

输出

用户 A 评估结果:

推荐物品: [‘a’, ‘c’]

预期物品: [‘a’, ‘c’]

命中物品: {‘a’, ‘c’}

精确率: 1.0000

召回率: 1.0000

F1 分数: 1.0000

用户 D 的性能评估
# 预期推荐物品

expected_d = ['b', 'c', 'd']

result_d = recommend_items('D', k=2)

eval_d = evaluate_recommendation(result_d, expected_d)

print(f"用户D评估结果:")

print(f"推荐物品: {result_d}")

print(f"预期物品: {expected_d}")

print(f"命中物品: {eval_d['hit']}")

print(f"精确率: {eval_d['precision']:.4f}")

print(f"召回率: {eval_d['recall']:.4f}")

print(f"F1分数: {eval_d['f1']:.4f}")

输出

用户 D 评估结果:

推荐物品: [‘b’, ‘c’]

预期物品: [‘b’, ‘c’, ‘d’]

命中物品: {‘b’, ‘c’}

精确率: 1.0000

召回率: 0.6667

F1 分数: 0.8000


优缺点分析

优点

  1. 实现简单:算法原理直观,易于理解和实现

  2. 无需物品特征:只需要用户行为数据,不需要物品的内容特征

  3. 发现新兴趣:能够为用户推荐意想不到的兴趣物品

  4. 实时性好:当用户行为发生变化时,可以快速更新推荐结果

缺点

  1. 数据稀疏性:当用户 - 物品矩阵稀疏时,难以找到相似用户

  2. 冷启动问题:新用户或新物品缺乏行为数据,无法有效推荐

  3. 计算复杂度:随着用户数量增加,相似度计算复杂度呈 O (n²) 增长

  4. 推荐多样性:可能导致推荐结果缺乏多样性


总结与展望

实验结论

  1. 算法有效性:基于用户的协同过滤算法在无评分二值数据场景下表现良好

  2. 性能表现:用户 A 的 F1 分数达到 1.0,用户 D 达到 0.8,推荐效果理想

  3. 算法对比:三种相似度算法在小规模数据集上表现基本一致

  4. 参数影响:k 值(相似用户数量)对推荐结果影响较小

改进方向

  1. 混合推荐:结合基于物品的协同过滤或内容推荐,提高推荐效果

  2. 降维技术:使用 PCA、SVD 等降维技术降低计算复杂度

  3. 时间因素:考虑用户行为的时间衰减,近期行为给予更高权重

  4. 冷启动处理:结合物品特征信息处理新用户和新物品问题

  5. 多样性优化:在保证准确性的同时提高推荐结果的多样性

应用场景

基于用户的协同过滤算法适用于:

  • 电商平台的商品推荐

  • 音乐、视频、新闻等内容推荐

  • 社交网络的好友推荐

  • 在线教育的课程推荐


完整代码

import math

from collections import defaultdict

# 1. 实验数据

user_items = {

   'A': {'b', 'd'},

   'B': {'a', 'b', 'c'},

   'C': {'a', 'b', 'd'},

   'D': {'a', 'e'}

}

# 2. 相似度计算函数

def cn_similarity(items1: set, items2: set) -> int:

   """Common Neighbors相似度:共同物品的数量"""

   return len(items1 & items2)

def jaccard_similarity(items1: set, items2: set) -> float:

   """Jaccard相似度:交集/并集"""

   intersection = len(items1 & items2)

   union = len(items1 | items2)

   return 0.0 if union == 0 else intersection / union

def cosine_similarity(items1: set, items2: set) -> float:

   """余弦相似度:共同物品数/(√物品数1 × √物品数2)"""

   intersection = len(items1 & items2)

   norm1 = math.sqrt(len(items1))

   norm2 = math.sqrt(len(items2))

   return 0.0 if (norm1 * norm2) == 0 else intersection / (norm1 * norm2)

# 3. 推荐函数

def recommend_items(

   target_user: str,

   k: int = 2,

   top_n: int = 2,

   sim_method = jaccard_similarity

) -> list:

   """为目标用户推荐物品"""

   target_owned = user_items[target_user]

  

   # 计算与其他用户的相似度

   similarities = []

   for other_user, items in user_items.items():

       if other_user == target_user:

           continue

       similarity = sim_method(target_owned, items)

       if similarity > 0:

           similarities.append((similarity, other_user))

  

   # 选择最相似的k个用户

   similarities.sort(key=lambda x: x[0], reverse=True)

   top_k_users = similarities[:k]

  

   if not top_k_users:

       return []

  

   # 计算物品推荐分数

   item_scores = defaultdict(float)

   for similarity, user in top_k_users:

       candidate_items = user_items[user] - target_owned

       for item in candidate_items:

           item_scores[item] += similarity

  

   # 返回推荐结果

   sorted_items = sorted(item_scores.items(), key=lambda x: x[1], reverse=True)

   return [item for item, score in sorted_items[:top_n]]

# 4. 评估函数

def evaluate_recommendation(recommended_items, expected_items):

   """评估推荐结果"""

   recommended_set = set(recommended_items)

   expected_set = set(expected_items)

  

   intersection = recommended_set & expected_set

   precision = len(intersection) / len(recommended_set) if recommended_set else 0

   recall = len(intersection) / len(expected_set) if expected_set else 0

   f1 = 2 * precision * recall / (precision + recall) if (precision + recall) != 0 else 0

  

   return {

       'precision': precision,

       'recall': recall,

       'f1': f1,

       'hit': intersection

   }

# 5. 主函数

if __name__ == "__main__":

   print("基于用户的协同过滤算法演示")

   print("=" * 50)

  

   # 为用户A推荐

   print("n用户A推荐结果:")

   a_recommendations = recommend_items('A', k=2)

   print(f"推荐物品: {a_recommendations}")

  

   # 为用户D推荐 

   print("n用户D推荐结果:")

   d_recommendations = recommend_items('D', k=2)

   print(f"推荐物品: {d_recommendations}")

  

   # 评估结果

   print("n性能评估:")

   expected_a = ['a', 'c']

   eval_a = evaluate_recommendation(a_recommendations, expected_a)

   print(f"用户A F1分数: {eval_a['f1']:.4f}")

  

   expected_d = ['b', 'c', 'd']

   eval_d = evaluate_recommendation(d_recommendations, expected_d)

   print(f"用户D F1分数: {eval_d['f1']:.4f}")

参考资料

  1. Resnick, P., Iacovou, N., Suchak, M., Bergstrom, P., & Riedl, J. (1994). GroupLens: an open architecture for collaborative filtering of netnews.

  2. Sarwar, B., Karypis, G., Konstan, J., & Riedl, J. (2001). Item-based collaborative filtering recommendation algorithms.

  3. Su, X., & Khoshgoftaar, T. M. (2009). A survey of collaborative filtering techniques.


如果这篇文章对您有帮助,请点赞分享!如有疑问或建议,欢迎留言讨论。

更多推荐