itemCF与SVD混合推荐算法:电影推荐系统的原理与实现 📅 发布时间:2026/9/11 23:58:05 👁 浏览次数: 简介基于itemCF与SVD的电影推荐算法本科毕业论文设计资源包面向机器学习、人工智能方向的本科生及推荐系统研究者。该设计围绕协同过滤与矩阵分解两类主流技术系统探讨item-based collaborative filtering与奇异值分解在电影推荐场景中的建模过程、优势互补及效果评估适合作为毕业论文撰写、算法复现或课程设计的参考范例。资源包共5个文件压缩后6.59MB。其中包含2个Python脚本分别实现itemCF和SVD算法1个DOCX论文正文和1个DOC文献综述另有1个DAT评分数据集可用于实际训练。内容覆盖代码、数据与文档三部分便于对照学习。目前已有280人学习下载。通过该资源可掌握推荐系统核心算法的Python实现理解矩阵分解与物品相似度计算的细节同时学习论文中的实验设计、性能评估指标以及冷启动问题处理思路对完成相关毕业设计或科研入门有实际帮助。1. 从 itemCF 到 SVD为什么电影推荐需要两种算法互补做电影推荐相关研究第一次跑通 itemCF 时结果通常不错但把评分矩阵放大到真实规模后基于物品的协同过滤会暴露出相似度噪声大、未评分位置等于默认值的问题。SVD 奇异值分解则擅长把用户与电影映射到低维隐因子空间用全局信息补全局部缺失。把 itemCF 与 SVD 放在一起做混合推荐算法既能保留物品间可解释的共现关系又能获得矩阵分解的泛化能力。下面围绕本科毕设任务“基于itemCF与SVD的电影推荐算法研究”展开讲清楚两套方法的选型边界和实现路径并给出可以直接复现的 Python 代码。适合正在写推荐系统论文、或者想将协同过滤与矩阵分解落实到生产环境的工程师。阅读时可以重点抓住三件事相似度如何计算、SVD 如何训练、两者如何融合。2. 基于 itemCF 与 SVD 的核心原理与选型边界2.1 itemCF 的计算逻辑与相似度公式itemCF 全称是 item-based Collaborative Filtering即基于物品的协同过滤。它的假设很直观很多用户给电影 A 打了高分同时也给电影 B 打了高分那么在行为空间里 A 和 B 就是相似的。用户看过 A 之后系统就把与 A 最像的 B 推给用户。注意这里说的“相似”不是类型、导演、演员这些内容特征而是评分共现关系。最基本的相似度是余弦相似度。下面代码输入用户-物品评分矩阵输出物品之间的相似度矩阵import numpy as np def cosine_similarity(ratings_matrix): # ratings_matrix: (users, items)0 表示未评分 item_matrix ratings_matrix.T # 转成 (items, users) norm np.sqrt((item_matrix ** 2).sum(axis1)).reshape(-1, 1) norm[norm 0] 1e-6 # 防止全 0 列除零 sim (item_matrix item_matrix.T) / (norm * norm.T) np.fill_diagonal(sim, 0) # 物品自身相似度置零 return simnorm * norm.T是列向量与行向量的外积得到任意两个物品的范数之积分子是共同评分向量的内积。这段代码没有对评分做中心化如果用户 A 普遍打 3 分、用户 B 普遍打 5 分余弦相似度会被用户打分尺度带偏。所以实际实现里一般先减去每个用户的平均分用残差计算相关等价于皮尔逊相关系数。另一个容易被忽略的细节是相似度矩阵的行归一化。itemCF 在做加权平均时如果某个物品与很多物品相似它的相似度分数天然会高导致推荐结果偏向“高连接度”物品。因此很多实现会在得到 sim 后按行减去均值或除以 max让相似度分布更均衡。在电影场景中这个操作也会让续集、同系列电影更容易排在前面。电影推荐里还有一个常见问题是热门电影跟所有电影都有共现相似度矩阵会堆向少数爆款冷门电影即使相关也很难进候选。常用的修正方式是给相似度乘一个热门惩罚系数例如用sim * np.power(1 - item_popularity_ratio, 0.5)削弱高频物品的相似度。2.2 SVD 的矩阵分解思路与目标函数SVD 奇异值分解把用户-电影矩阵 R 分解为 R ≈ U Σ V^T。U 的每一行可以理解成用户隐因子V 的每一行可以理解成电影隐因子Σ 是奇异值对角线矩阵。在推荐场景中评分矩阵缺失项占绝大多数如果直接把缺失位置填 0 再调用np.linalg.svd会得到一个有偏的重建矩阵。比如下面这个 3×3 的示例R np.array([[5, 4, 0], [0, 3, 4], [2, 0, 1]], dtypefloat) U, S, Vt np.linalg.svd(R, full_matricesFalse) k 2 R_approx U[:, :k] np.diag(S[:k]) Vt[:k, :] print(R_approx[:, 2]) # 原来缺失的列会被非零值污染这个输出说明直接 SVD 会用全局低秩近似去“猜测”缺失值但训练目标不是真实评分而是带了大量零值项。因此推荐系统里常说的 SVD 实际是 FunkSVD只针对已经观测到的评分建目标函数用随机梯度下降求解目标是最小化min Σ_ui (r_ui - p_u · q_i^T)^2 λ (||p_u||^2 ||q_i||^2)p_u 是用户 u 的 k 维隐向量q_i 是电影 i 的 k 维隐向量。FunkSVD 不再要求中间矩阵是严格对角阵而是直接学两个低秩因子矩阵这也是第 4 章实现的基础。FunkSVD 的梯度更新有一点和传统 SVD 不同真实评分 r_ui 不是分解后的某个奇异值而是 user-item 点积的目标值因此训练误差能直接对应 RMSE。实战中往往加上早停每轮在验证集上计算一次 RMSE连续 5 轮不下降就停止避免隐因子过拟合。2.3 两种算法的选型边界与组合动机对比维度itemCFSVD 奇异值分解输入用户-物品评分矩阵用户-物品评分矩阵建模对象物品间共同评分向量用户与物品的低维隐因子稀疏性处理差共同评分为 0 时相似度失效较好利用全局评分泛化新物品冷启动无评分行为时没法计算相似度没有隐因子向量也无法推荐可解释性高能解释“喜欢 A 的也喜欢 B”低隐因子没有明确语义主要开销物品数平方k 维度 × 迭代轮数把两者组合看中的是互补性itemCF 对冷门物品更敏感可解释性也强但在用户-电影矩阵稀疏时相似度矩阵大量行是空行召回不足。SVD 通过隐因子把稀疏矩阵压缩成 k 维向量能对未评分项给出相对平滑的预估但隐因子很难解释。两个模型的误差来源不同混合后通常能带来可观的离线指标提升。3. 构造评分矩阵并用 itemCF 计算电影相似度3.1 从原始评分日志构造稀疏评分矩阵正式实现第一步是把评分日志整理成三元组user_id、movie_id、rating。下面代码以 MovieLens 风格数据为例先过滤掉打分次数过少的用户和评分次数过少的电影。这个过滤是必要的否则相似度矩阵会被只评过一两次的用户噪声主导。import pandas as pd from scipy.sparse import coo_matrix df pd.read_csv(ratings.csv) # 用户最少评分 20 次过滤冷启动用户 user_count df[userId].value_counts() df df[df[userId].isin(user_count[user_count 20].index)] # 电影最少被评 5 次过滤无人问津的长尾 item_count df[movieId].value_counts() df df[df[movieId].isin(item_count[item_count 5].index)] users sorted(df[userId].unique()) items sorted(df[movieId].unique()) user2id {u: i for i, u in enumerate(users)} item2id {m: i for i, m in enumerate(items)} id2item {i: m for m, i in item2id.items()} rows df[userId].map(user2id).values cols df[movieId].map(item2id).values values df[rating].values.astype(np.float32) R coo_matrix((values, (rows, cols)), shape(len(users), len(items))).toarray()user2id和item2id是离散 id 到连续下标的映射id2item用于最后输出电影编号。min_user_count20、min_item_count5 是常见起步值数据量大时可以提高数据特别稀疏时也可以适当降低但不要低于 3否则相似度不可信。这里没有做评分归一化因为 itemCF 和 SVD 对原始评分尺度各有自己的建模方式。如果一开始就做 z-score 归一化反而会丢失用户评分的真实语义。实验时可以把原始评分和归一化后的评分各跑一遍再决定用哪种。3.2 itemCF 相似度计算的完整实现与参数说明第 2 章的余弦相似度没有做中心化实际代码里我会先计算每个用户的平均分再用残差代替原始评分。这样可以消除打分尺度差异让“给谁都打 5 分”的用户不会主导相似度。def build_item_sim(R): # R: (users, items)0 表示缺失 user_mean R.sum(axis1, keepdimsTrue) / (R 0).sum(axis1, keepdimsTrue) residual np.where(R 0, R - user_mean, 0) norm np.sqrt((residual ** 2).sum(axis0)) norm[norm 0] 1e-6 sim (residual.T residual) / np.outer(norm, norm) # 共同评分人数太少时相似度不可信直接置 0 co_count (R 0).T.astype(np.float32) (R 0).astype(np.float32) sim[co_count 5] 0 np.fill_diagonal(sim, 0) return simco_count统计两件商品被多少用户共同评过分少于 5 对样本算出的相关系数不稳定直接置 0 是降低噪声的常用操作。norm外积做归一化后sim矩阵每行都能看成电影 i 与所有电影的相似度分布可以直接缓存线上推荐时只查表。3.3 基于 itemCF 生成 top-N 推荐候选有了相似度矩阵对用户 u 预测电影 i 的分数采用加权平均公式权重是相似度分数是用户已评电影的评分。代码实现为def itemcf_predict(u, scored, R, sim, top_n20): pred np.zeros(R.shape[1]) for i in range(R.shape[1]): if scored[i]: pred[i] -np.inf continue sim_u sim[:, i][scored] if sim_u.size 0 or np.abs(sim_u).sum() 1e-12: continue pred[i] (sim_u * R[u, scored]).sum() / np.abs(sim_u).sum() return np.argsort(pred)[::-1][:top_n]循环版本方便阅读但生产环境建议改成矩阵运算sim[scored][:, ~scored]一次得到所有候选电影对。还要注意如果用户已评分电影太少加权平均会被单个相似度放大因此第 3.1 节的过滤阈值同样约束了在线推荐质量。参数推荐如下参数含义起步值调整方向min_user_count用户最少评分次数20稀疏数据下调到 10min_item_count电影最少被评次数5长尾数据下调到 3co_count_threshold最小共同评分人数5噪声多时提高到 10热门惩罚 alpha相似度降权强度0.50.30.8 网格搜索4. 用 SVD 完成隐因子分解与混合推荐打分4.1 FunkSVD 的训练过程与正则化SVD 系列算法在推荐里的核心问题是评分矩阵存在大量缺失不能直接用np.linalg.svd。常见做法是只优化有评分的位置用随机梯度下降同时更新两个隐因子矩阵。下面是一个可以直接跑的实验版 FunkSVDdef train_funksvd(R, k20, lr0.01, reg0.1, epochs50): m, n R.shape P np.random.normal(0, 0.1, size(m, k)) Q np.random.normal(0, 0.1, size(n, k)) entries [(u, i) for u in range(m) for i in range(n) if R[u, i] 0] for epoch in range(epochs): np.random.shuffle(entries) for u, i in entries: err R[u, i] - P[u] Q[i] # 负梯度更新带 L2 正则 P[u] - lr * (-err * Q[i] reg * P[u]) Q[i] - lr * (-err * P[u] reg * Q[i]) return P, Qk是隐因子维度lr 是学习率reg 是正则强度epochs 是训练轮数。这段代码没有加用户和物品偏置实际使用中可以把评分拆成global_mean b_u b_i P[u]Q[i]让偏置项先吸收用户习惯和物品热度隐因子专心建模剩余部分。初始化用均值为 0、标准差 0.1 的随机正态分布比全 0 初始化更快打破对称。训练收敛的判断不要只看训练集 RMSE否则 k 增大到一定程度后会出现过拟合。更实用的做法是每 10 轮在验证集上计算一次 RMSE连续 3 次不降则停止。由于 FunkSVD 的随机性每次初始化和遍历顺序不同最终模型会略有波动论文里固定 random_state 更利于复现。4.2 混合推荐的融合方式itemCF 和 SVD 的预测分数虽然都在 1-5 分区间但分布不一致。直接加权会被量纲带偏所以先对两个分数做 min-max 归一化再线性融合def normalize_score(scores): mn, mx scores.min(), scores.max() return (scores - mn) / ((mx - mn) 1e-8) itemcf_scores normalize_score(itemcf_scores) svd_scores normalize_score(svd_scores) alpha 0.6 final alpha * itemcf_scores (1 - alpha) * svd_scores top_idx np.argsort(final)[::-1][:20]alpha 就是混合权重表示更信任 itemCF 还是 SVD。调参顺序建议先固定 SVD 和 itemCF 的参数在验证集上从 0.2 到 0.8 按步长 0.1 扫 alpha。不要同时调三个模型的参数否则无法定位是哪一侧出了问题。如果两个模型分数相关性很高混合收益会变小此时可以检查是不是数据划分泄漏了。混合分数归一化时要注意极小值。当 itemCF 某个候选没有相似邻居时分数会是 0min-max 后变成负值这与 SVD 的平滑预测冲突。我一般在归一化前先把未产生分数的候选置为一个很小的负值例如 -1保证它们排在最后但不会破坏排序区间。4.3 训练集/测试集划分避免数据泄漏离线实验中数据划分方式直接影响指标的置信度。最简单的随机划分会带来泄漏同一用户的评分可能一半在训练集、一半在测试集itemCF 在测试时通过相似度矩阵间接“看到”了测试评分。推荐做法是按用户分层或按时间切分。from sklearn.model_selection import train_test_split # 以用户为粒度切分训练和测试用户不重叠 train_users, test_users train_test_split( df[userId].unique(), test_size0.2, random_state42 ) train_df df[df[userId].isin(train_users)] test_df df[df[userId].isin(test_users)]如果目标是模拟时间线可以给每个用户取最后 10% 的评分作为测试集。划分方式对最终指标影响很大论文里一定要写清楚。下表总结了四种常见划分划分方式训练/测试关系适合评估随机行划分同一用户可分到两侧快速基线用户分层划分训练测试用户不重叠新用户冷启动LeaveOneOut每个用户只留一个测试项top-N 排序时间序划分按时间先后切分近线上场景5. 评估指标、参数调优与 itemCF-SVD 混合的落地技巧5.1 评估指标怎么选评分预测和列表推荐要分开看。RMSE、MAE 评估预测分和真实分的差距PrecisionN、RecallN 评估 top-N 列表的命中效果。建议至少报 RMSE 和 Precision10 两个指标一个说明回归误差一个说明排序质量。覆盖率统计被推荐到的电影占全集的比例用于观察是否只推爆款。5.2 参数调优的优先级调参顺序固定为先调 SVD 的 k、lr、reg再调 itemCF 的最小共同评分人数和热门惩罚最后调融合权重 alpha。SVD 的 k 一般从 20 开始k 太小欠拟合、太大过拟合lr 从 0.005 起步reg 从 0.1 起步。itemCF 的 co_count_threshold 不要超过 20否则大部分电影都会因为没有足够共同评分而失去候选资格。5.3 混合推荐的落地技巧把 itemCF 相似度矩阵和 SVD 的 P、Q 存成 npz 文件线上加载后只需要做矩阵乘法和查表不需要重训。保存代码很简单np.savez(model_cache.npz, itemcf_simitemcf_sim, PP, QQ) cache np.load(model_cache.npz, allow_pickleTrue)最后在验证集上跑完一轮后将 alpha 从 0 调到 1画出最终指标曲线。最佳权重往往会偏向某一侧这比单独看一组 RMSE 更能说明 itemCF 和 SVD 各自的贡献边界。本文还有配套的精品资源点击获取