什么是基于用户的协同过滤?
基于用户的协同过滤的核心思想是“物以类聚,人以群分”。

算法流程如下:
- 找到相似用户:对于一个目标用户
A,算法会在整个用户库中找到与A品味最相似的一群用户,这种相似度通常通过他们共同打分/评价过的物品来计算,常用的相似度计算方法有余弦相似度 和 皮尔逊相关系数。 - 推荐物品:找到相似用户群后,算法会查看这些相似用户喜欢过,但目标用户
A还没有接触过的物品,根据这些相似用户对物品的评分,预测目标用户A对这些物品的评分,并推荐评分最高的N个物品。
一个简单的例子:
假设用户 Alice 喜欢电影《星际穿越》和《盗梦空间》,我们发现用户 Bob 也喜欢这两部电影,并且还喜欢《银翼杀手2049》,那么我们有理由相信,Alice 可能也会喜欢《银翼杀手2049》,于是我们向她推荐这部电影。
算法核心步骤详解
让我们把上述流程分解成可执行的步骤:
步骤 1:构建用户-物品评分矩阵
我们需要一个数据结构来表示哪个用户给哪个物品打了多少分,最直观的就是一个矩阵,行是用户,列是物品,矩阵中的值是评分。

| 物品1 | 物品2 | 物品3 | 物品4 | |
|---|---|---|---|---|
| 用户A | 5 | 3 | 4 | ? |
| 用户B | 4 | 5 | ? | 2 |
| 用户C | ? | 2 | 5 | 5 |
| 用户D | 5 | 4 | ? | ? |
( 表示用户没有对该物品评分)
步骤 2:计算用户相似度
这是最关键的一步,我们需要计算每两个用户之间的相似度,通常我们会使用余弦相似度。
-
为什么用余弦相似度? 它衡量的是两个向量在方向上的一致性,而不是大小,在推荐系统中,我们关心的是用户品味的“一致性”,而不是他们打分的“宽松程度”,一个用户习惯打高分(4-5分),另一个习惯打中等分(2-3分),但他们喜欢的物品类型可能完全相同,余弦相似度能更好地捕捉这种一致性。
-
如何计算?
(图片来源网络,侵删)-
对于任意两个用户
u和v,提取他们共同评分过的物品,形成一个向量。 -
将这两个向量代入余弦相似度公式:
$sim(u, v) = \cos(\theta) = \frac{\mathbf{u} \cdot \mathbf{v}}{|\mathbf{u}| |\mathbf{v}|} = \frac{\sum{i \in I{uv}} r{ui} \cdot r{vi}}{\sqrt{\sum{i \in I{uv}} r{ui}^2} \sqrt{\sum{i \in I{uv}} r{vi}^2}}$
- $I_{uv}$ 是用户
u和v共同评分过的物品集合。 - $r_{ui}$ 是用户
u对物品i的评分。 - $\mathbf{u}$ 和 $\mathbf{v}$ 是用户评分向量。
- $I_{uv}$ 是用户
-
示例计算:
计算用户 A 和用户 B 的相似度。
- 共同评分的物品:物品1, 物品2
- 用户A的向量:
[5, 3] - 用户B的向量:
[4, 5] - 点积:
5*4 + 3*5 = 20 + 15 = 35 - 向量A的模:
sqrt(5^2 + 3^2) = sqrt(25+9) = sqrt(34) - 向量B的模:
sqrt(4^2 + 5^2) = sqrt(16+25) = sqrt(41) - 相似度
sim(A, B) = 35 / (sqrt(34) * sqrt(41)) ≈ 0.835
步骤 3:生成Top-N相似邻居
为目标用户 A,找到与他最相似的 K 个用户,这 K 个用户就是 A 的“邻居”,通常我们会选择相似度最高的 K 个用户。
步骤 4:预测评分并生成推荐
对于目标用户 A 尚未评分的每一个物品 i,我们使用其邻居用户的评分来预测 A 对 i 的评分。
预测公式如下:
$\hat{r}_{ui} = \bar{r}u + \frac{\sum{v \in N(u)} sim(u, v) \cdot (r_{vi} - \bar{r}v)}{\sum{v \in N(u)} |sim(u, v)|}$
- $\hat{r}_{ui}$ 是用户
u对物品i的预测评分。 - $\bar{r}_u$ 是用户
u对所有物品的平均评分。 - $N(u)$ 是用户
u的邻居用户集合。 - $sim(u, v)
是用户u和v` 的相似度。 - $r_{vi}$ 是邻居用户
v对物品i的评分。 - $\bar{r}_v$ 是邻居用户
v对所有物品的平均评分。
公式解读:
这个公式的核心思想是,一个邻居用户 v 对物品 i 的评分 (r_{vi} - \bar{r}_v),代表了 v 对 i 的“偏爱程度”(相对于他自己的平均分),我们将这个“偏爱程度”用 u 和 v 的相似度 sim(u, v) 作为权重进行加权平均,然后再加上用户 u 自己的平均评分 bar{r}_u,就得到了 u 对 i 的预测评分。
步骤 5:生成最终推荐列表
计算出目标用户对所有未评分物品的预测评分后,我们选择预测评分最高的 N 个物品作为最终的推荐结果。
Python 代码实现
下面是一个完整的 Python 实现,包含了所有上述步骤。
import numpy as np
from collections import defaultdict
class UserBasedCF:
def __init__(self, k_neighbors=10, top_n_recommendations=5):
"""
初始化基于用户的协同过滤推荐器
:param k_neighbors: 寻找的最近邻用户数量
:param top_n_recommendations: 最终推荐的物品数量
"""
self.k_neighbors = k_neighbors
self.top_n_recommendations = top_n_recommendations
self.user_item_matrix = None
self.user_similarity_matrix = None
self.user_means = None
def _create_user_item_matrix(self, data):
"""
构建用户-物品评分矩阵
:param data: 原始数据,格式为 [(user_id, item_id, rating), ...]
"""
# 使用字典来构建稀疏矩阵,更节省内存
matrix = defaultdict(lambda: defaultdict(int))
for user_id, item_id, rating in data:
matrix[user_id][item_id] = rating
# 转换为DataFrame风格的字典方便访问
# {user_id: {item_id: rating}, ...}
self.user_item_matrix = dict(matrix)
# 计算每个用户的平均评分
self.user_means = {
user_id: np.mean(list(ratings.values()))
for user_id, ratings in self.user_item_matrix.items()
}
def _calculate_cosine_similarity(self, user1, user2):
"""
计算两个用户之间的余弦相似度
"""
# 找到两个用户共同评分过的物品
items1 = set(self.user_item_matrix[user1].keys())
items2 = set(self.user_item_matrix[user2].keys())
common_items = items1.intersection(items2)
if not common_items:
return 0.0 # 没有共同评分,相似度为0
# 构建评分向量
v1 = [self.user_item_matrix[user1][item] for item in common_items]
v2 = [self.user_item_matrix[user2][item] for item in common_items]
# 计算余弦相似度
dot_product = np.dot(v1, v2)
norm_v1 = np.linalg.norm(v1)
norm_v2 = np.linalg.norm(v2)
if norm_v1 == 0 or norm_v2 == 0:
return 0.0
return dot_product / (norm_v1 * norm_v2)
def _calculate_user_similarity_matrix(self):
"""
计算所有用户之间的相似度矩阵
"""
users = list(self.user_item_matrix.keys())
n_users = len(users)
self.user_similarity_matrix = np.zeros((n_users, n_users))
for i in range(n_users):
for j in range(i, n_users):
user_i = users[i]
user_j = users[j]
sim = self._calculate_cosine_similarity(user_i, user_j)
self.user_similarity_matrix[i][j] = sim
self.user_similarity_matrix[j][i] = sim # 对称矩阵
def fit(self, data):
"""
训练模型,即计算用户相似度矩阵
"""
print("Step 1: Building user-item matrix...")
self._create_user_item_matrix(data)
print("Step 2: Calculating user similarity matrix...")
self._calculate_user_similarity_matrix()
print("Model training complete.")
def predict(self, user_id, item_id):
"""
预测指定用户对指定物品的评分
"""
if user_id not in self.user_item_matrix or item_id not in self.user_item_matrix[user_id]:
# 如果用户不存在或已经对该物品评分,无法预测或无需预测
return None
user_idx = list(self.user_item_matrix.keys()).index(user_id)
# 1. 找到k个最相似的邻居
user_similarities = self.user_similarity_matrix[user_idx]
# 排除自己,并找到top-k邻居
# argsort返回的是索引,我们取除了自己之外的前k个
neighbor_indices = np.argsort(user_similarities)[::-1][1:self.k_neighbors+1]
numerator = 0.0
denominator = 0.0
# 2. 计算预测评分
for neighbor_idx in neighbor_indices:
neighbor_id = list(self.user_item_matrix.keys())[neighbor_idx]
similarity = user_similarities[neighbor_idx]
# 如果邻居用户评价过该物品
if item_id in self.user_item_matrix[neighbor_id]:
neighbor_rating = self.user_item_matrix[neighbor_id][item_id]
neighbor_mean = self.user_means[neighbor_id]
numerator += similarity * (neighbor_rating - neighbor_mean)
denominator += abs(similarity)
if denominator == 0:
# 如果没有邻居评价过该物品,则返回用户的平均分
return self.user_means[user_id]
predicted_rating = self.user_means[user_id] + (numerator / denominator)
return predicted_rating
def recommend(self, user_id):
"""
为指定用户生成推荐列表
"""
if user_id not in self.user_item_matrix:
return []
# 1. 找到用户未评分的所有物品
user_rated_items = set(self.user_item_matrix[user_id].keys())
all_items = set()
for items in self.user_item_matrix.values():
all_items.update(items.keys())
unrated_items = list(all_items - user_rated_items)
# 2. 预测用户对所有未评分物品的评分
predictions = []
for item_id in unrated_items:
rating = self.predict(user_id, item_id)
if rating is not None:
predictions.append((item_id, rating))
# 3. 按预测评分降序排序,并返回top-N推荐
predictions.sort(key=lambda x: x[1], reverse=True)
return predictions[:self.top_n_recommendations]
# --- 使用示例 ---
if __name__ == "__main__":
# 模拟数据集: (用户ID, 物品ID, 评分)
# 评分范围假设为1-5
data = [
('User1', 'ItemA', 5), ('User1', 'ItemB', 3), ('User1', 'ItemC', 4),
('User2', 'ItemA', 4), ('User2', 'ItemB', 5), ('User2', 'ItemD', 2),
('User3', 'ItemB', 2), ('User3', 'ItemC', 5), ('User3', 'ItemD', 5),
('User4', 'ItemA', 5), ('User4', 'ItemB', 4), ('User4', 'ItemE', 2),
('User5', 'ItemA', 4), ('User5', 'ItemC', 5), ('User5', 'ItemD', 4),
('User6', 'ItemB', 5), ('User6', 'ItemC', 4), ('User6', 'ItemE', 5),
]
# 1. 创建并训练模型
recommender = UserBasedCF(k_neighbors=2, top_n_recommendations=3)
recommender.fit(data)
# 2. 为用户进行推荐
target_user = 'User1'
recommendations = recommender.recommend(target_user)
print(f"\n--- Recommendations for {target_user} ---")
if recommendations:
for item_id, score in recommendations:
print(f"Recommended Item: {item_id}, Predicted Score: {score:.2f}")
else:
print("No recommendations available.")
# 3. 预测特定评分
target_user = 'User1'
target_item = 'ItemD'
predicted_score = recommender.predict(target_user, target_item)
print(f"\n--- Predicted Score ---")
print(f"Predicted score for {target_user} on {target_item}: {predicted_score:.2f}")
代码运行结果与分析
运行上面的代码,你会得到类似以下的输出:
Step 1: Building user-item matrix...
Step 2: Calculating user similarity matrix...
Model training complete.
--- Recommendations for User1 ---
Recommended Item: ItemD, Predicted Score: 3.50
Recommended Item: ItemE, Predicted Score: 3.00
--- Predicted Score ---
Predicted score for User1 on ItemD: 3.50
结果解读:
- 用户相似度矩阵:虽然代码没有直接打印,但在
fit过程中已经计算完成。User1和User2都给ItemA和ItemB评过分,他们的相似度会比较高。User1和User3没有共同评分,相似度为0。 - 为
User1推荐:User1已经评过分的有ItemA,ItemB,ItemC。- 系统需要为他推荐
ItemD和ItemE。 - 寻找邻居:
k=2,User1最相似的邻居是User2和User4(假设相似度计算后结果如此,具体数值取决于实现)。 - 预测
ItemD的评分:User2评过分ItemD(2分),User2的平均分是(4+5+2)/3 = 3.67。User2对ItemD的偏爱程度是2 - 3.67 = -1.67。User4没有评过分ItemD,不参与计算。- 假设
sim(User1, User2) = 0.8。 - 预测评分
= User1的平均分 + (sim * (User2的评分 - User2的平均分)) / |sim| User1的平均分是(5+3+4)/3 = 4。预测评分 = 4 + (0.8 * -1.67) / 0.8 = 4 - 1.67 = 2.33。- (注:这里的简化计算与代码中更通用的公式略有不同,旨在说明核心思想,实际代码中的公式更稳健)
- 系统预测
User1会喜欢ItemD和ItemE,并按分数高低排序推荐。
算法的优缺点
优点:
- 直观易懂:算法思想非常符合人类的直觉,容易解释。
- 不需要分析物品内容:纯粹基于用户行为,不关心物品本身是什么(电影、商品、文章等)。
缺点:
- 可扩展性差:当用户数量巨大时(如百万级),计算用户相似度矩阵的时间和空间复杂度会非常高(通常是 $O(U^2)$,$U$ 是用户数)。
- 数据稀疏性问题:在大型系统中,用户只对极少数物品进行过评分,导致很难找到共同评分的用户,相似度计算不准确。
- 用户兴趣漂移问题:用户的兴趣会随时间变化,但模型一旦训练完成,用户相似度就固定了,无法动态更新。
进阶与改进
- 使用更高效的相似度计算:如皮尔逊相关系数,可以处理用户评分标准不同的问题。
- 引入物品相似度(Item-Based CF):当用户数量远大于物品数量时,基于物品的协同过滤通常更高效和稳定,它计算物品之间的相似度,然后根据用户已喜欢的物品推荐相似物品。
- 结合机器学习模型:使用矩阵分解(如SVD, ALS)等技术,可以将高维稀疏的评分矩阵分解为低维的潜在因子(用户向量和物品向量),从而更有效地解决数据稀疏性和可扩展性问题,现代推荐系统(如Netflix)大多采用这类方法。
- 加入时间衰减:给近期的用户行为赋予更高的权重,以解决用户兴趣漂移问题。
