基于协同过滤的图书推荐系统Python实战:从ItemCF到矩阵分解
简介这份资源是面向计算机相关专业学生与Python开发者的图书推荐系统完整项目以协同过滤算法为核心重点实现物品-物品相似度计算与个性化图书推荐适合用作毕业设计参考或推荐算法入门实践。压缩包共46个文件约985KB包含7个Python脚本负责数据处理、模型训练与后端接口21个JavaScript与6个CSS文件支撑前端交互与样式7个HTML页面覆盖登录、注册、图书列表、详情及后台管理等模块另有数据库文件与说明文档整体结构清晰、便于二次开发。目前已有2712人学习下载。项目完整呈现了从评分数据预处理、余弦相似度计算到推荐结果展示的全流程读者可据此理解协同过滤原理掌握Flask后端与前端页面的衔接方式并在此基础上尝试引入深度学习或社交因素优化推荐精度对完成毕设与提升工程能力均有较高参考价值。1. 从零拆一套协同过滤图书推荐它到底能解决什么问题如果你正在找一套能跑通、能改、能写进毕设的推荐系统源码大概率绕不开「基于协同过滤的图书推荐系统 python」这个方向。我拿到这套资源的第一反应不是看界面而是先翻它的数据流用户对图书的评分矩阵怎么建、相似度怎么算、冷启动怎么兜底。因为推荐系统这东西界面再花哨核心就一句话——把「和你口味相近的人喜欢的书」推给你。这套资源的价值在于它把协同过滤从公式落成了可运行的 python 工程包含数据加载、相似度计算、评分预测、TopN 推荐和可视化展示几个模块。适合谁做毕设的学生、想入门推荐算法的后端、需要快速搭 demo 的从业者。它不解决海量数据的工业级性能问题但能让你在一个下午内理解推荐系统到底怎么转起来。2. 协同过滤的两条路线UserCF 和 ItemCF 到底怎么选2.1 用户相似度和物品相似度的本质区别协同过滤分两支基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。UserCF 的逻辑是「找到和你评分习惯相似的一群人把他们喜欢但你没看过的书推给你」ItemCF 的逻辑是「找到和你历史上喜欢的书相似的其它书推给你」。听起来差不多但适用场景完全不同。UserCF 更适合用户数量远小于物品数量的场景比如新闻推荐因为新闻更新极快物品相似度矩阵根本来不及维护。ItemCF 更适合物品相对稳定、用户兴趣变化慢的场景图书推荐就是典型——一本书的受众不会今天喜欢明天讨厌而且图书数量通常远小于用户数量物品相似度矩阵可以离线算好反复用。这套资源默认走的是 ItemCF 路线原因很实际图书评分数据里用户对书的评分是稀疏的但书与书之间的共现关系相对稳定。我翻了下它的相似度计算模块用的是调整余弦相似度Adjusted Cosine而不是普通的余弦相似度。为什么因为不同用户打分尺度不一样有人习惯打 3 分有人习惯打 5 分普通余弦会把这种偏差算进去。调整余弦先减去用户平均分再算相似度能消掉一部分主观偏差。2.2 相似度计算的代码实现与参数说明下面这段是资源里相似度计算的核心逻辑我按自己的理解加了注释方便你对照着改import numpy as np from sklearn.metrics.pairwise import cosine_similarity def adjusted_cosine_similarity(ratings_matrix): ratings_matrix: 用户-物品评分矩阵行是用户列是物品 返回物品-物品相似度矩阵 # 计算每个用户的平均评分只对已评分物品求均值 user_means np.true_divide( ratings_matrix.sum(axis1), (ratings_matrix ! 0).sum(axis1) ) # 把评分矩阵中心化每个评分减去该用户的平均分 # 未评分的位置保持为 0不参与相似度计算 centered ratings_matrix - user_means[:, np.newaxis] centered[ratings_matrix 0] 0 # 对中心化后的矩阵做归一化避免量纲影响 norms np.linalg.norm(centered, axis0, keepdimsTrue) norms[norms 0] 1 # 防止除零 normalized centered / norms # 物品-物品相似度 归一化矩阵的列向量点积 item_sim normalized.T normalized np.fill_diagonal(item_sim, 0) # 自己和自己不算相似 return item_sim逻辑说明先算用户平均分再把评分矩阵中心化这一步是调整余弦的关键。参数上ratings_matrix的 0 表示未评分不是打分为 0这个约定要贯穿整个工程。np.fill_diagonal把对角线置零避免推荐时把书自己推给自己。如果你换成皮尔逊相似度效果在稀疏数据上可能更稳但计算量会大一些常见做法是先用调整余弦跑通再根据评测指标决定要不要换。2.3 评分预测和 TopN 推荐的衔接相似度算完只是第一步接下来要预测用户对未评分图书的分数。公式是预测分 用户平均分 加权相似度 × 邻居评分偏差。资源里用了一个可调参数k控制邻居数量默认取 20。k 太小推荐结果不稳定k 太大会把不相关的书也拉进来。我一般会从 10 到 50 之间扫一遍看 RMSE 和 Recall 的变化曲线选拐点位置。TopN 推荐则是在预测分基础上排序取前 N 本。这里有个坑如果用户已经评过分的书还留在候选集里推荐结果会出现「已经看过的书」体验很差。资源里在生成推荐列表前做了一次过滤把用户已评分物品从候选集中剔除。这个过滤逻辑必须放在排序之前否则你取完 TopN 再过滤可能只剩三五本。3. 把源码跑起来环境配置、数据加载和第一个推荐结果3.1 python 环境与依赖库的安装细节这套资源是纯 python 工程依赖 numpy、pandas、scikit-learn 和 matplotlib。如果你还没装 python建议直接去 python 官网下载 3.8 以上版本安装时勾选「Add Python to PATH」不然后面在命令行里会提示「python was not found」。装完 python 后用 pip 装依赖pip install numpy pandas scikit-learn matplotlib如果你用 vscode 配置 python 环境记得在设置里把 python 解释器指向你刚装的那个版本别用系统自带的旧版本。pycharm 配置 python 环境也类似在 Project Interpreter 里选对路径就行。有个常见问题是 sklearn 安装慢可以换国内镜像源pip install scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple装完后在命令行里跑python -c import sklearn; print(sklearn.__version__)能打印出版本号就说明环境通了。3.2 数据加载与评分矩阵构建资源里的数据集是常见的图书评分格式三列用户 ID、图书 ID、评分。加载和建矩阵的代码如下import pandas as pd import numpy as np def load_ratings(file_path): 读取评分数据返回用户-物品矩阵和映射字典 df pd.read_csv(file_path, sep,, header0, names[user_id, item_id, rating]) # 构建用户和物品的索引映射把原始 ID 转成矩阵下标 user_ids df[user_id].unique() item_ids df[item_id].unique() user_to_idx {uid: i for i, uid in enumerate(user_ids)} item_to_idx {iid: i for i, iid in enumerate(item_ids)} # 初始化全零矩阵0 表示未评分 matrix np.zeros((len(user_ids), len(item_ids))) for row in df.itertuples(): matrix[user_to_idx[row.user_id], item_to_idx[row.item_id]] row.rating return matrix, user_to_idx, item_to_idx参数说明sep,要根据你的数据实际分隔符改有的是制表符。names里列的顺序必须和文件一致否则用户和物品会搞反。矩阵用 0 填充未评分位置这是后面所有计算的约定。如果你的数据里有 0 分评分需要把未评分标记改成 -1 或 NaN不然会混淆。3.3 跑出第一个推荐结果并验证数据加载完调用相似度计算和推荐生成就能拿到结果def recommend(user_idx, matrix, item_sim, top_n10, k20): 给指定用户生成 TopN 推荐 user_ratings matrix[user_idx] # 只取用户评过分的物品作为种子 rated_items np.where(user_ratings 0)[0] scores {} for item in rated_items: # 取相似度最高的 k 个邻居 sim_items np.argsort(item_sim[item])[::-1][:k] for sim_item in sim_items: if user_ratings[sim_item] 0: # 只预测未评分的 scores[sim_item] scores.get(sim_item, 0) \ item_sim[item][sim_item] * user_ratings[item] # 按预测分排序取前 top_n ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_n]逻辑说明遍历用户评过分的每一本书找和它最相似的 k 本书如果这些书用户没评过就累加相似度乘以评分的值作为预测分。最后排序取前 N。验证方法很简单从用户已评分里随机抽几本藏起来用剩下的数据训练看推荐列表里有没有藏起来的那几本。命中率能到 15% 以上就算正常低于 5% 要检查相似度矩阵是不是算错了。4. 避坑与排查协同过滤落地时最容易翻车的五个地方4.1 现象推荐结果全是冷门书热门书一本不推原因相似度矩阵被长尾物品主导。冷门书因为评分人数少相似度计算时噪声大反而容易和很多书产生高相似度。解决在相似度计算后加一个流行度惩罚项或者对相似度做阈值截断低于 0.1 的直接置零。我一般会在item_sim上做一次item_sim[item_sim 0.1] 0效果立竿见影。4.2 现象程序跑起来内存直接爆掉原因用户-物品矩阵用稠密 numpy 数组存储如果用户数和物品数都上万矩阵大小是 用户数 × 物品数 × 8 字节很容易超过内存。解决改用 scipy 的稀疏矩阵csr_matrix只存非零元素。资源里默认用稠密矩阵是为了教学清晰实际数据量大时必须换稀疏存储。4.3 现象RMSE 很低但推荐结果用户不买账原因RMSE 衡量的是评分预测准确度但推荐系统真正关心的是排序质量。预测分 4.2 和 4.1 的两本书RMSE 差别很小但排序位置可能差很多。解决评测指标换成 RecallK 或 NDCGK直接看 TopN 列表里命中多少用户真正喜欢的书。资源里预留了评测脚本把指标从 RMSE 改成 Recall 就能看到真实效果。4.4 现象新用户进来完全没有推荐原因UserCF 和 ItemCF 都依赖历史行为新用户没有评分记录相似度算不出来。解决做混合推荐新用户走热门榜兜底等积累到 5 条以上评分再切协同过滤。资源里在推荐入口加了一个判断如果用户评分数小于阈值直接返回全局最高分的 N 本书。4.5 现象每次跑出来的推荐结果都不一样原因相似度计算时用了随机采样或没有固定随机种子。解决在代码开头加np.random.seed(42)并把所有涉及随机的步骤固定下来。另外检查数据加载时有没有用shuffle如果有关掉或固定种子。推荐系统可复现是调试的前提不然你根本不知道改的参数有没有生效。5. 进阶技巧用矩阵分解给协同过滤加一层保险协同过滤的硬伤是稀疏矩阵下相似度算不准。我一般会在 ItemCF 跑通后再叠一层矩阵分解MF做融合。具体做法是用 sklearn 的TruncatedSVD对评分矩阵做降维把用户和物品映射到低维隐向量空间然后用隐向量点积预测评分。代码不复杂from sklearn.decomposition import TruncatedSVD def mf_recommend(matrix, user_idx, n_components20, top_n10): 矩阵分解推荐作为协同过滤的补充 svd TruncatedSVD(n_componentsn_components, random_state42) user_factors svd.fit_transform(matrix) item_factors svd.components_.T # 预测评分 用户隐向量 点积 物品隐向量 pred_scores user_factors[user_idx] item_factors.T # 过滤已评分物品 pred_scores[matrix[user_idx] 0] -np.inf top_items np.argsort(pred_scores)[::-1][:top_n] return top_items参数上n_components控制隐向量维度太小欠拟合太大过拟合一般从 20 开始试看 Recall 曲线。融合策略有两种加权融合ItemCF 得分 × 0.6 MF 得分 × 0.4或切换融合评分数多的用户走 ItemCF评分数少的走 MF。我习惯用切换融合因为新用户用 MF 效果更稳。验证融合有没有用不能只看离线指标。我一般会做一次 A/B 对比同一批用户一半走纯 ItemCF一半走融合看点击率或收藏率的变化。离线 Recall 提升 2% 不一定代表线上有效但离线都降了就别上线了。从那以后我每次搭推荐系统都强制先跑一遍纯 ItemCF 拿到基线再叠 MF 看增量最后用 Recall10 卡一道门槛。这套流程帮我省了很多「看起来很美但上线就崩」的后悔药。希望帮到你。本文还有配套的精品资源点击获取