协同过滤算法实战:从原理到代码实现与性能优化
1. 协同过滤算法入门从生活场景到数学原理第一次听说协同过滤这个词时我正坐在咖啡馆里看朋友刷购物APP。他突然抬头问我你说这APP怎么知道我想买登山杖我从来没搜过啊。这个看似神奇的推荐背后很可能就是协同过滤在发挥作用。简单来说协同过滤就像一群口味相似的朋友互相安利好物。算法通过分析大量用户的行为数据发现喜欢A的人也喜欢B的规律。比如在豆瓣电影给《星际穿越》打高分的人往往也喜欢《盗梦空间》系统就会把后者推荐给只看过前者的用户。核心思想其实很直观用户的行为会留下痕迹这些痕迹之间存在隐藏的关联。算法要做的是挖掘三种关键关系用户与用户之间的相似度UserCF物品与物品之间的相似度ItemCF用户与物品之间的交互关系我最早在电商平台实践时用到的评分矩阵长这样用户\电影电影A电影B电影C用户1530用户2402用户3145表格里的0表示未评分。要预测用户1对电影C的评分UserCF会这样做计算用户1与其他用户的相似度找出最相似的K个用户用这些用户对电影C的评分加权平均相似度计算就像比较两个人的购物车重合度。数学上有两个经典方法余弦相似度计算向量夹角的余弦值from sklearn.metrics.pairwise import cosine_similarity cosine_similarity([5,3,0], [4,0,2]) # 输出0.73皮尔逊系数考虑评分偏置的改进版from scipy.stats import pearsonr pearsonr([5,3,0], [4,0,2])[0] # 输出0.66实际项目中我发现当用户评分尺度差异大时比如有人习惯打1-5分有人只用4-5分皮尔逊系数效果更好。这就像比较两个美食家一个给普通餐馆打3分米其林打5分另一个觉得3分就算好吃5分是惊艳。皮尔逊系数能消除这种个人标准差异。2. UserCF实战用Python实现电影推荐去年给本地影院做推荐系统时我用了MovieLens数据集练手。这个经典数据集包含10万条电影评分非常适合练手。先看看数据长什么样import pandas as pd ratings pd.read_csv(ratings.csv) movies pd.read_csv(movies.csv) print(ratings.head())输出结果userId movieId rating timestamp 0 1 1 4.0 964982703 1 1 3 4.0 964981247 2 1 6 4.0 964982224 3 1 47 5.0 964983815 4 1 50 5.0 964982931第一步构建共现矩阵这里有个坑要注意——不是所有用户都评价过相同电影直接计算会导致维度不一致。我的解决办法是用pivot_tablerating_matrix ratings.pivot_table( indexuserId, columnsmovieId, valuesrating ).fillna(0)第二步计算用户相似度直接计算全量用户相似度太耗内存我改用KNN只保留TopN相似用户from sklearn.neighbors import NearestNeighbors knn NearestNeighbors(metriccosine, algorithmbrute) knn.fit(rating_matrix)第三步预测评分对于目标用户找到相似用户后用加权平均预测评分def predict_rating(user_id, movie_id, n_neighbors5): distances, indices knn.kneighbors( rating_matrix.loc[user_id].values.reshape(1,-1), n_neighborsn_neighbors1 ) neighbor_ratings rating_matrix.iloc[indices[0][1:], movie_id] return neighbor_ratings.mean()实际跑下来发现几个问题冷启动用户新用户无法处理计算耗时随用户量增长而剧增当电影数量达到万级时用户共同评分过的电影可能只有个位数后来优化时我加入了评分时间衰减因子——最近3个月的评分权重更高。这就像你更相信朋友最近的安利而不是他五年前推荐的手机型号。3. ItemCF实战电商商品推荐优化在跨境电商项目里我放弃了UserCF转向ItemCF。原因很现实我们有2000万用户但只有10万商品UserCF的相似度矩阵会大到内存爆炸。ItemCF的优势在于商品数量通常远少于用户数商品相似度更稳定不像用户兴趣变化快可解释性强买了牙膏的人也会买牙刷比和你相似的人买了牙刷更有说服力实现步骤有所不同计算物品共现矩阵item_sim_matrix rating_matrix.corr(methodpearson)生成推荐列表def recommend_items(user_id, top_k10): user_ratings rating_matrix.loc[user_id] # 找出用户评分高的物品 high_rated_items user_ratings[user_ratings 3].index # 根据相似度矩阵找到相似物品 similar_items item_sim_matrix[high_rated_items].mean(axis0) # 过滤已评分的 similar_items similar_items.drop(user_ratings[user_ratings 0].index) return similar_items.sort_values(ascendingFalse)[:top_k]实际部署时我做了三点重要优化滑动时间窗口只计算最近3个月的行为数据让推荐更时效性热门商品降权避免总是推荐爆款用TF-IDF思想处理相似度多维度融合加入类目相似度同品类商品更可能相关有个有趣的发现在美妆品类ItemCF准确率比UserCF高23%但在图书品类差异不大。后来分析发现美妆用户常跨品类购买如同时买护肤品和化妆品而图书读者往往专注特定类型。4. 性能优化从单机到分布式的演进当数据量超过百万级时我在笔记本上跑的Python脚本直接卡死。后来逐步摸索出一套优化方案第一阶段算法层优化稀疏矩阵存储用scipy.sparse节省内存from scipy.sparse import csr_matrix sparse_matrix csr_matrix(rating_matrix.values)近似最近邻(ANN)用Spotify的Annoy库加速from annoy import AnnoyIndex annoy_index AnnoyIndex(n_features, angular)第二阶段工程化改造定时离线计算每晚更新相似度矩阵分片处理按用户/商品ID哈希分片缓存热点数据用Redis缓存Top100商品相似度第三阶段Spark分布式最终方案迁移到PySparkfrom pyspark.ml.recommendation import ALS als ALS( rank10, maxIter5, regParam0.01, userColuserId, itemColmovieId, ratingColrating ) model als.fit(ratings)在千万级数据测试中Spark比单机快60倍。但要注意几个坑数据倾斜问题少数热门商品会导致计算卡住参数调优rank值过大容易过拟合冷启动处理需要混合内容推荐策略有一次线上事故让我记忆犹新春节促销期间推荐系统突然响应变慢。查日志发现是某个爆款商品被30%用户点击导致ItemCF计算时数据倾斜。临时方案是对该商品做采样处理长期则改为分段相似度计算。5. 效果评估与AB测试推荐系统不能只追求技术先进关键要看业务指标。我们建立了完整的评估体系离线指标准确率RMSE、MAE覆盖率推荐商品占全集比例多样性推荐列表的品类分布在线指标CTR点击通过率转化率客单价变化AB测试时发现一个反直觉现象在ItemCF中加入购买时间衰减因子后离线指标提升但线上转化率下降。分析后发现母婴用品这类周期性消费品两年前购买过的用户现在可能仍有需求。于是改为按品类设置不同衰减周期。评估代码示例from surprise import Dataset, accuracy from surprise.model_selection import train_test_split data Dataset.load_builtin(ml-100k) trainset, testset train_test_split(data, test_size0.2) predictions algo.test(testset) accuracy.rmse(predictions) # 计算RMSE最近尝试的改进方向是融合用户画像将协同过滤的推荐结果与用户 demographic 特征结合。比如给年轻男性推荐游戏周边时适当降低办公用品的推荐权重。这种混合策略在测试中使CTR提升了15%。