推荐系统早已渗透进我们数字生活的每个角落,但多数开发者对协同过滤的理解仍停留在调用sklearn的阶段。今天我们不谈抽象公式,直接动手用Python实现一个完整的UserCF推荐系统,你会看到那些看似复杂的数学概念如何转化为几十行清晰的代码。
任何推荐系统的起点都是数据。我们首先模拟一个真实的用户-物品交互场景:
python复制import numpy as np
import pandas as pd
from collections import defaultdict
# 模拟数据生成
np.random.seed(42)
users = [f'user_{i}' for i in range(1, 101)]
items = [f'item_{j}' for j in range(1, 51)]
# 生成稀疏交互矩阵
interactions = defaultdict(dict)
for u in users:
interacted = np.random.choice(items, size=np.random.randint(5,15), replace=False)
for i in interacted:
interactions[u][i] = np.random.randint(1, 5) # 1-4分表示兴趣程度
注意:实际工业级系统会使用Redis或HBase存储这类稀疏矩阵,这里用字典简化实现
将数据转为DataFrame更易处理:
python复制df = pd.DataFrame.from_dict(interactions, orient='index').fillna(0)
print(df.head()[items[:5]]) # 展示前5个物品的交互情况
用户相似度计算是UserCF的命脉,我们实现带热门惩罚的余弦相似度:
python复制def cosine_sim_with_penalty(user1, user2, df, item_popularity):
"""
带热门物品惩罚的余弦相似度
:param item_popularity: 物品被交互次数统计
"""
common_items = set(df.loc[user1].nonzero()[0]) & set(df.loc[user2].nonzero()[0])
if not common_items:
return 0
# 计算惩罚权重
numerator = sum(
1 / np.log1p(item_popularity[item])
for item in common_items
)
# 计算分母(用户向量模长)
norm1 = np.sqrt(sum(
(1 / np.log1p(item_popularity[item]))**2
for item in df.loc[user1].nonzero()[0]
))
norm2 = np.sqrt(sum(
(1 / np.log1p(item_popularity[item]))**2
for item in df.loc[user2].nonzero()[0]
))
return numerator / (norm1 * norm2)
计算物品热度分布:
python复制item_popularity = df.astype(bool).sum(axis=0).to_dict() # 每个物品被多少用户交互过
为避免重复计算,我们预先计算并缓存相似度:
python复制from tqdm import tqdm # 进度条显示
user_sim_matrix = pd.DataFrame(index=users, columns=users, dtype=float)
for u1 in tqdm(users):
for u2 in users:
if u1 == u2:
user_sim_matrix.loc[u1, u2] = 1.0
elif u2 in user_sim_matrix.index and u1 in user_sim_matrix[u2]:
user_sim_matrix.loc[u1, u2] = user_sim_matrix.loc[u2, u1]
else:
user_sim_matrix.loc[u1, u2] = cosine_sim_with_penalty(u1, u2, df, item_popularity)
# 只保留每个用户最相似的50个邻居
top_k_users = {}
for u in users:
top_k_users[u] = user_sim_matrix[u].nlargest(51).iloc[1:].to_dict() # 排除自己
结合相似用户和兴趣度生成推荐:
python复制def generate_recommendations(target_user, k=5):
"""
为目标用户生成top-k推荐
"""
candidate_scores = defaultdict(float)
# 聚合相似用户的兴趣
for sim_user, similarity in top_k_users[target_user].items():
for item, rating in interactions[sim_user].items():
if item not in interactions[target_user]: # 排除已交互物品
candidate_scores[item] += similarity * rating
# 按得分排序返回
return sorted(candidate_scores.items(), key=lambda x: x[1], reverse=True)[:k]
测试推荐效果:
python复制sample_user = np.random.choice(users)
print(f"为用户 {sample_user} 生成的推荐:")
for item, score in generate_recommendations(sample_user):
print(f"- {item} (预测兴趣度: {score:.2f})")
实际落地时还需考虑以下关键点:
内存优化方案
python复制# 使用scipy的稀疏矩阵
from scipy.sparse import csr_matrix
row_ind, col_ind, data = [], [], []
for i, u1 in enumerate(users):
for j, u2 in enumerate(users):
if user_sim_matrix.loc[u1, u2] > 0.1: # 只存储显著相似度
row_ind.append(i)
col_ind.append(j)
data.append(user_sim_matrix.loc[u1, u2])
sparse_sim_matrix = csr_matrix((data, (row_ind, col_ind)), shape=(len(users), len(users)))
线上服务架构
评估推荐系统不能只看准确率,还需考虑:
| 指标类型 | 具体指标 | 实现代码片段示例 |
|---|---|---|
| 预测准确度 | RMSE, MAE | sklearn.metrics.mean_squared_error |
| 排序质量 | NDCG, MAP | lightfm.evaluation.ndcg_score |
| 多样性 | 推荐物品的覆盖率 | len(set(recommended_items)) / total_items |
| 新颖性 | 推荐物品的平均流行度 | np.mean([item_popularity[i] for i in rec_items]) |
关键参数调优经验:
python复制# 带时间衰减的相似度计算示例
def time_aware_sim(u1, u2, time_decay=0.9):
# time_decay: 每天兴趣衰减率
common_items = find_common_items(u1, u2)
time_weights = [time_decay**(days_ago) for days_ago in get_interaction_days()]
return weighted_cosine(common_items, time_weights)
冷启动问题
数据稀疏性
python复制# 使用LightFM处理稀疏数据示例
from lightfm import LightFM
model = LightFM(no_components=30, loss='warp')
model.fit(sparse_interactions, epochs=20)
在电商平台实际应用中,UserCF的召回结果通常会和ItemCF、Embedding等方法融合。一个典型的工作流是:先用UserCF快速筛选出几百候选物品,再用更复杂的模型进行精排。