推荐系统已经成为现代互联网服务的标配功能,从电商平台到内容社区,几乎无处不在。基于物品的协同过滤(Item-Based CF)作为推荐系统领域的经典算法,以其直观的原理和稳定的效果,在实际业务中有着广泛应用。这次我将带大家从零开始,完整实现一个基于物品的协同过滤推荐系统。
不同于学院派的纯理论讲解,我会以一个实际业务场景为背景,分享我在多个推荐系统项目中积累的实战经验。我们将使用Python作为实现语言,重点解决三个核心问题:如何计算物品相似度?如何处理稀疏矩阵?如何平衡推荐结果的多样性和准确性?
协同过滤的核心思想是"物以类聚,人以群分"。基于物品的协同过滤主要关注物品之间的相似性,通过用户历史行为数据,找出相似的物品进行推荐。举个例子,如果用户A购买了物品1和物品2,而用户B购买了物品1和物品3,那么系统可能会认为物品2和物品3存在某种相似性,从而向购买物品3的用户推荐物品2。
在实现Item-Based CF时,相似度计算是关键环节。常见的相似度计算方法包括:
经过实际项目验证,在用户评分数据上,皮尔逊相关系数通常表现更好;而在隐式反馈数据(如点击、购买)上,余弦相似度更为适用。本次实现我们将重点使用余弦相似度,因为它的计算效率高,且对稀疏数据友好。
真实场景中的用户-物品矩阵往往非常稀疏(填充率通常低于1%),这会导致相似度计算不准确。常见的解决方案包括:
考虑到实现复杂度和效果平衡,我们会采用简单的基线填充方法:用物品的平均评分填充缺失值。虽然这不是最优方案,但在中小规模数据集上已经能显著改善效果。
首先我们需要准备用户-物品交互数据。以MovieLens数据集为例:
python复制import pandas as pd
from scipy.sparse import csr_matrix
# 加载评分数据
ratings = pd.read_csv('ratings.csv')
movies = pd.read_csv('movies.csv')
# 创建用户-物品评分矩阵
user_item_matrix = ratings.pivot(index='userId', columns='movieId', values='rating')
# 处理缺失值 - 用物品平均分填充
item_means = user_item_matrix.mean(axis=0)
user_item_matrix = user_item_matrix.fillna(item_means)
# 转换为稀疏矩阵节省内存
sparse_matrix = csr_matrix(user_item_matrix.values)
接下来计算物品之间的相似度矩阵:
python复制from sklearn.metrics.pairwise import cosine_similarity
# 计算物品相似度矩阵
item_sim_matrix = cosine_similarity(sparse_matrix.T)
# 将相似度矩阵转换为DataFrame便于查询
item_sim_df = pd.DataFrame(item_sim_matrix,
index=user_item_matrix.columns,
columns=user_item_matrix.columns)
这里有几个优化点需要注意:
基于相似度矩阵生成推荐:
python复制def recommend_items(user_id, user_item_matrix, item_sim_df, top_n=10):
# 获取用户历史交互物品
user_ratings = user_item_matrix.loc[user_id]
# 找出用户未评分的物品
unrated_items = user_ratings[user_ratings.isna()].index
# 计算预测评分
item_scores = {}
for item in unrated_items:
# 获取与当前物品最相似的k个物品
similar_items = item_sim_df[item].sort_values(ascending=False)[1:11]
# 计算加权平均评分
weighted_sum = 0
sim_sum = 0
for similar_item, sim_score in similar_items.items():
if similar_item in user_ratings and not pd.isna(user_ratings[similar_item]):
weighted_sum += sim_score * user_ratings[similar_item]
sim_sum += sim_score
if sim_sum > 0:
item_scores[item] = weighted_sum / sim_sum
# 返回评分最高的n个物品
return sorted(item_scores.items(), key=lambda x: x[1], reverse=True)[:top_n]
当物品数量很大时(如超过1万),相似度矩阵的计算和存储会成为瓶颈。我们可以采用以下优化策略:
优化后的相似度计算代码:
python复制from sklearn.neighbors import NearestNeighbors
# 使用KNN计算近似相似度
knn = NearestNeighbors(n_neighbors=20, metric='cosine', algorithm='brute')
knn.fit(sparse_matrix.T)
# 获取每个物品的最近邻
distances, indices = knn.kneighbors(sparse_matrix.T)
在实际生产环境中,推荐请求往往是实时的。我们可以预计算相似度矩阵,然后在服务内存中维护:
python复制import pickle
from flask import Flask, request
app = Flask(__name__)
# 加载预计算的模型
with open('item_sim_model.pkl', 'rb') as f:
model = pickle.load(f)
@app.route('/recommend', methods=['GET'])
def recommend():
user_id = int(request.args.get('user_id'))
top_n = int(request.args.get('top_n', 10))
# 获取推荐结果
recommendations = recommend_items(user_id, model['user_item_matrix'],
model['item_sim_df'], top_n)
return {'recommendations': recommendations}
推荐系统的常用评估指标包括:
以NDCG为例的实现代码:
python复制from sklearn.metrics import ndcg_score
def evaluate_model(test_ratings, recommendations):
# 准备真实评分和预测评分
true_relevance = []
pred_relevance = []
for user_id in test_ratings.index:
true_ratings = test_ratings.loc[user_id]
pred_items = [item for item, _ in recommendations[user_id]]
# 构建评分向量
true_vec = []
pred_vec = []
for item in pred_items:
true_vec.append(true_ratings.get(item, 0))
pred_vec.append(1) # 假设所有推荐物品都是相关的
true_relevance.append(true_vec)
pred_relevance.append(pred_vec)
return ndcg_score(true_relevance, pred_relevance)
新物品或新用户的冷启动是推荐系统的常见挑战。我们可以采用以下策略:
在实际部署Item-Based CF时,有几个关键点需要注意:
重要提示:在计算相似度时,务必对数据进行适当的去噪和归一化处理。我在一个实际项目中发现,没有处理极端用户(如给所有物品打5分的用户)会导致相似度计算偏差很大。
传统的Item-Based CF可以结合深度学习技术进行增强:
要实现更实时的个性化推荐,可以考虑:
我在实际项目中发现,简单的时间衰减策略(如最近7天的行为权重是30天前的2倍)就能显著提升推荐效果。
问题:当物品数量很大时,相似度矩阵会消耗大量内存。
解决方案:
问题:少数热门物品占据了大部分推荐位置。
解决方案:
问题:当用户和物品数量增长时,计算时间呈指数增长。
解决方案:
在实际项目中,我通常会先在小样本数据上验证算法效果,然后再扩展到全量数据。这能节省大量开发和调试时间。