ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

协同过滤与强化学习融合的电影推荐系统实战指南

协同过滤与强化学习融合的电影推荐系统实战指南 简介这是一篇面向计算机、数据科学、人工智能等专业学生及推荐算法研究者的学位毕业论文完整研究基于协同过滤与强化学习相结合的电影推荐系统。论文从协同过滤基本原理入手系统阐述基于用户与基于物品两种实现并引入Q-Learning强化学习来动态优化推荐策略深入讨论算法优缺点、冷启动与稀疏性问题同时包含数据集介绍、实验设计、结果分析和对比实验等关键内容。压缩包仅含1个docx文档大小29KB为西南财经大学学士学位论文全文目录包括绪论、协同过滤、强化学习、综合方法、实验与结果分析等章节结构清晰可直接用于毕业论文撰写、课程设计或推荐算法落地参考。目前已有216人学习下载适合研究生、本科生及对个性化推荐技术感兴趣的学者阅读。1. 协同过滤和强化学习都救不了的电影推荐系统差在哪电影推荐做到最后往往不是算法不够而是目标函数写错了。协同过滤在 MovieLens 这类评分数据集上表现很好RMSE 能压到 0.9 左右但把它原样搬上线用户活跃度往往会掉。原因在于协同过滤拟合的是“用户过去给什么电影打了高分”而推荐系统真正要解决的是“用户下一次打开推荐位时更可能点开哪一部”。前者是静态拟合后者是一个持续交互的动态决策问题——用户兴趣会漂移片库不断上新推荐位被多条内容竞争一次推荐的结果还会影响用户下一次的行为。强化学习恰恰是面向这种长期收益的框架但它也有自己的问题从零开始学用户偏好样本效率和冷启动都撑不住。这篇博文按一条可落地的路径来讲协同过滤负责召回和物品表征强化学习负责在排序和重排层做动态决策最后给出离线评估指标和容易翻车的三个细节。内容适合正在做推荐系统、想引入强化学习但不知道怎么和现有协同过滤链路衔接的工程师。2. 协同过滤召回矩阵分解还是近邻选型与落地2.1 从 ItemCF 和 UserCF 说起为什么电影推荐默认选物品相似度协同过滤两大分支 UserCF 和 ItemCF核心区别在于相似度计算的对象。UserCF 找“和我口味相似的用户”把这些用户喜欢的电影推给我ItemCF 找“我历史上喜欢的电影的相似电影”把相似电影推给我。电影推荐场景里ItemCF 是更稳妥的起点原因很具体电影物品数量相对稳定物品相似度矩阵可以离线算好线上只需要查表而用户数量大且行为变化快UserCF 的用户相似度矩阵更新频繁计算成本高实时性也差。维度UserCFItemCF相似度计算对象用户评分向量物品评分向量矩阵更新成本用户行为变化即需重算新电影上线才需局部更新推荐解释“和你口味相似的人还喜欢”“因为你喜欢《盗梦空间》所以推荐《星际穿越》”新用户冷启动无历史行为无法计算相似用户只要有少量行为就能找到相似物品新电影冷启动无用户评分无法被推荐无共现数据同样难推荐ItemCF 的相似度公式最常见的是余弦相似度计算前一般会做评分均值中心化避免用户打分尺度不同带来的偏差。工程实现上物品相似度矩阵通常用 Spark 或离线批任务每天算一次结果写到 Redis 或内存表线上服务直接读取。矩阵大小是物品数的平方电影规模在十万量级时没问题百万量级需要分片或改用向量检索。2.2 矩阵分解 SVD 在这条链路里到底在拟合什么2.2.1 用 Surprise 跑出第一个 SVD 模型矩阵分解是协同过滤的另一种实现它不直接算物品相似度而是把用户和物品映射到同一个隐向量空间用隐向量的内积拟合评分。相比 ItemCF矩阵分解的泛化能力更强能捕捉到“用户没看过但和看过电影共享潜在因子”的关联。Surprise 库是 Python 里最常用的推荐算法库先跑通最小示例from surprise import SVD, Dataset, Reader from surprise.model_selection import train_test_split from surprise import accuracy # MovieLens 100K 的 u.data 格式user item rating timestamp reader Reader(line_formatuser item rating timestamp, sep\t) data Dataset.load_from_file(ml-100k/u.data, readerreader) trainset, testset train_test_split(data, test_size0.2, random_state42) # n_factors 隐向量维度决定模型的表达能力 # n_epochs 训练轮数轮数太少欠拟合太多容易过拟合 # lr_all 学习率0.005 是 MovieLens 上比较稳的起点 # reg_all 正则化系数防止隐向量和偏置项过大 # biasedTrue 额外拟合用户偏置和物品偏置评分预测任务里通常要开 model SVD(n_factors20, n_epochs20, lr_all0.005, reg_all0.02, biasedTrue) model.fit(trainset) preds model.test(testset) print(RMSE, accuracy.rmse(preds)) print(MAE, accuracy.mae(preds))这段代码里Reader负责告诉 Surprise 数据文件的列格式train_test_split按比例切分训练集和测试集。SVD的核心参数是n_factors它决定隐向量的维度设太小模型表达力不足用户和物品的潜在特征压不进这么低的维度设太大训练慢且容易过拟合测试集上的 RMSE 反而上升。MovieLens 100K 上 20 到 50 是一个合理区间具体可以用交叉验证选。from surprise.model_selection import cross_validate # 用 5 折交叉验证替代单次切分结论更稳定 result cross_validate(model, data, measures[RMSE, MAE], cv5, verboseTrue) print(result[test_rmse].mean())2.2.2 评分模型和推荐排序模型的错位一个容易被忽略的问题是SVD 优化的是评分预测误差而推荐系统最终要的是排序质量。评分预测 RMSE 低不代表 Top-K 推荐列表的用户满意度高。原因在于评分预测是逐点回归每个用户物品对独立计算损失而推荐列表是集合级决策用户只会看到排在前面的几条排在后面的预测误差没有实际影响。实际项目中我一般会用训练好的 SVD 生成用户隐向量和物品隐向量再用向量内积或余弦相似度做召回而不是直接用预测评分排序。原因是预测评分排序天然偏向热门物品——热门物品的偏置项高预测评分容易被顶上去导致推荐结果趋同。隐向量召回配合物品相似度做多样性控制效果通常好于直接用评分排序。2.3 召回侧的天花板正是强化学习要补的位置协同过滤不管哪种实现本质上都在回答“用户过去喜欢什么”。它有两个结构性问题第一没有对“推荐后用户的反馈”建模推荐结果会进入用户历史影响下一次召回这个反馈循环在静态模型里是闭环的无法感知第二推荐的每一次动作都被当作独立事件不考虑这次推荐是否能带来用户更长时间的活跃。这两个问题恰好是强化学习的建模对象。协同过滤适合做第一阶段的候选生成产出用户向量、物品向量和候选集强化学习在第二阶段根据当前用户状态选择最优推荐策略。3. 强化学习层如何建模长期价值状态、动作、奖励3.1 用强化学习框架重看推荐问题3.1.1 推荐问题的 MDP 映射强化学习把推荐过程建模成马尔可夫决策过程。用户每次打开推荐页系统观察到当前状态选择一个动作推荐哪些电影用户给出反馈点击、观看、评分系统获得奖励状态转移到下一个时刻。这里的关键是目标不是最大化本次点击率而是最大化一段时期内的累计奖励。具体映射关系状态 s 是用户最近的行为序列包括最近看过的电影 ID、评分、类别分布、以及用户的静态画像特征动作 a 是本次从候选集里选出的电影列表或者列表的排序策略奖励 r 是用户对推荐结果的反馈点击可以记 0.5完整观看记 1评分按分值折算负反馈如快速划走记 -0.2。转移概率 P(s|s,a) 不需要显式建模它由用户的真实行为决定。3.1.2 状态价值函数的作用是什么状态价值函数 V(s) 表示从状态 s 出发按当前策略行动未来能获得的期望累计奖励。在推荐系统里V(s) 回答的问题是“用户带着当前兴趣状态进来系统预期能从他身上获得多少互动价值。”动作价值函数 Q(s,a) 则更细一层回答“在状态 s 下执行动作 a 比默认策略好多少”。搞清楚这两个函数的区别很重要。策略梯度方法直接优化策略Actor-Critic 方法用 V(s) 做基线来降低梯度方差Q-Learning 系列方法则直接逼近 Q(s,a)。推荐系统里更常用 Q 函数因为动作空间是有限的候选集Q 函数可以直接比较不同候选的动作价值。V(s) 的作用在于评估当前状态的价值以及作为离线强化学习里评估策略好坏的指标。3.2 用于推荐的 DQN一个可跑的简化版推荐系统的状态空间和动作空间都是高维的表格型 Q-Learning 不适用常见做法是用深度网络逼近 Q 函数。下面是一个基于 PyTorch 的 DQN 骨架适合在模拟环境里先跑通import torch import torch.nn as nn import numpy as np class DQN(nn.Module): 输入用户状态向量输出每个候选动作的 Q 值 def __init__(self, state_dim, action_dim, hidden_dim64): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, action_dim) ) def forward(self, state): return self.net(state)state_dim 是用户状态向量的维度action_dim 是候选动作的数量。训练循环里需要关注四个组件# 训练步从经验池取一个 batch计算 TD 误差 batch_states torch.tensor(replay_buffer.sample_states()).float() batch_actions torch.tensor(replay_buffer.sample_actions()).long() batch_rewards torch.tensor(replay_buffer.sample_rewards()).float() batch_next_states torch.tensor(replay_buffer.sample_next_states()).float() q_values dqn(batch_states).gather(1, batch_actions.unsqueeze(1)).squeeze(1) with torch.no_grad(): # target_net 是周期性同步的旧网络用来稳定训练 target_q batch_rewards gamma * target_net(batch_next_states).max(1).values loss nn.MSELoss()(q_values, target_q) optimizer.zero_grad() loss.backward() optimizer.step()gamma是折扣因子控制模型看多远0.9 偏重近期收益0.99 偏重长期。推荐场景一般取 0.9 到 0.95因为用户兴趣变化快远期收益的不确定性太大。target_net每 N 步从dqn复制一次权重避免自举带来的 Q 值振荡。动作选择用 epsilon-greedy训练初期 epsilon 设 0.9逐步衰减到 0.05保证探索和利用的平衡。3.3 从 IQL 离线强化学习着眼在线交互很难绕过真实推荐系统没法像游戏 AI 那样在线试错推荐错一部电影对用户是实际损耗而且动作的结果要等用户反馈才能知道反馈延迟还长。所以工业场景里强化学习模块大多是离线训练IQLImplicit Q-Learning这类离线强化学习算法更贴近可用状态。IQL 的核心思路是用静态日志数据学习 Q 函数不做在线交互。它用 expectile 回归替代标准 Q-Learning 里的 max 操作避免对日志中未出现过的状态动作对产生过高估计。和 CQL、BCQ 等离线强化学习算法相比IQL 实现简单对超参数不敏感更适合推荐系统这种奖励稀疏、数据分布覆盖不全的场景。如果你正在从在线 DQN 转向离线方案IQL 是性价比最高的起点。4. 协同过滤与强化学习双通道融合的实战配置4.1 推荐系统的常规衔接召回-排序-重排工业级推荐系统很少用单一算法走完全链路常见结构是“召回-排序-重排”三层。召回层从全量电影池里选出千级候选排序层预估点击率或评分重排层考虑多样性、新鲜度和商业约束。协同过滤适合放在召回层因为它计算快、可解释性强强化学习适合放在重排层因为重排层动作空间小需要做的决策正好是“这几部电影怎么排最优”。融合的第一步是明确边界协同过滤负责产出候选集和物品表征强化学习负责在候选集上做排序决策。两者不是竞争关系而是前后衔接。4.2 用协同过滤输出给强化学习做状态与动作空间协同过滤训练完的模型可以拆出三类中间产物用户隐向量、物品隐向量、候选集。这些产物直接决定强化学习的状态向量和动作空间怎么构造。状态向量由三部分拼接而成SVD 训练出的用户隐向量svd.pu[user_id]用户最近交互过的电影隐向量的均值以及用户画像特征如最常看的电影类型分布。动作空间则用协同过滤的候选集固定下来ItemCF 的 Top-K 和 SVD 的 Top-K 做并集作为候选池强化学习每次从候选池里选一个重排策略或一组展示列表。# 从训练好的 SVD 模型中提取用户状态向量 user_vec svd.pu[user_id] # 用户最近交互过的电影取隐向量均值作为短期兴趣表征 recent_items user_history[-10:] item_vecs [svd.qi[iid] for iid in recent_items if iid in svd.qi] hist_vec np.mean(item_vecs, axis0) # 拼接成强化学习的状态输入 state np.concatenate([user_vec, hist_vec])4.3 结合 SVD 和 DQN 的代码骨架下面这段代码展示了候选集合并与动作选择的完整逻辑# 候选集 SVD TopK 与 ItemCF TopK 的并集固定大小 K def build_candidates(user_id, svd_model, itemcf_sim, k20): svd_scores {iid: svd_model.predict(user_id, iid).est for iid in all_movie_ids} svd_topk set(sorted(svd_scores, keysvd_scores.get, reverseTrue)[:k]) # itemcf_topk 通过查询物品相似度表获得 itemcf_topk set() for iid in user_history[-5:]: itemcf_topk.update(itemcf_sim[iid][:k]) return list(svd_topk | itemcf_topk)[:k]# 用 DQN 从候选集中选择重排动作 candidates build_candidates(user_id, svd, itemcf_sim, k20) state_tensor torch.tensor(state).float() q_values dqn(state_tensor) # 输出每个候选动作的 Q 值 action_idx q_values.argmax().item() ranked_list candidates[action_idx]这段代码里有个容易忽略的细节候选集必须固定排序方式否则同一个用户每次进来的候选集顺序不同DQN 的输入输出对应关系就不稳定训练很难收敛。工程上候选集可以在召回阶段确定后缓存重排时直接按固定顺序传给强化学习模型。4.4 基于模型强化学习在电影推荐里的适用边界基于模型强化学习在推荐场景里争议较大——用户行为很难用显式环境模型刻画预测不准反而引入偏差。但 ItemCF 产出的物品相似度矩阵可以当做一个轻量环境模型给定一个动作预测用户对结果的反馈概率。这种用法在候选集规模小、反馈路径明确的场景下能加速训练但如果用户行为复杂度过高不建议先上基于模型方案直接走 IQL 这类免模型离线强化学习更稳。5. 落地前需要盯住的评估指标、调参和三个容易翻车的细节5.1 用离线指标判断改动是否值得上线评估推荐系统改动离线指标只是第一道关卡不是充分条件。下面这张表是我在电影推荐项目里常用的评估体系指标计算口径回答的问题HRKTop-K 中命中的用户数 / 总用户数推荐结果是否覆盖用户真实偏好NDCGK按排序位置加权的命中率排在前面的结果是否更相关覆盖率被推荐到的物品数 / 物品总数长尾电影有没有机会曝光多样性推荐列表中不同类别的数量推荐结果是否过于同质化注意离线评估必须和时间切片结合。用 t 时刻之前的数据训练t 时刻之后的数据做测试才能模拟真实的时间线。随机切分的评估结果往往会高估模型性能因为训练集和测试集共享同一时间段的用户兴趣分布。5.2 调整奖励时要注意的奖惩尺度强化学习的效果高度依赖奖励信号的质量。常见错误是把点击、评分、观看时长直接相加量纲不同会导致某个信号主导训练。我一般会做三件事点击设为 0.5 固定值观看时长取对数后归一化到 0 到 1负反馈快速划走、不点击给一个小的负奖励防止模型只推安全但无聊的内容。奖励稀疏是另一个大问题。用户行为中 90% 以上是“无反馈”如果默认给 0模型梯度几乎全来自少数正样本。一个工程技巧是把无反馈设成轻微的负奖励比如 -0.05让模型明白“推荐但没被看到”也消耗了展示位是有代价的。5.3 三个容易翻车的细节第一个是候选集振荡。协同过滤每天的离线更新会改变候选集内容导致同一个用户的状态对应不同候选集强化学习训练的 Q 值方差变大。解决办法是候选集变化时保留旧候选集的 embedding 特征而不是直接替换。第二个是曝光偏差。离线日志里只包含被推荐过的电影未曝光电影没有反馈直接拿日志训练会强化已有推荐策略的马太效应。IQL 等方法部分缓解了这个问题但仍需在训练时对未曝光样本做负采样时控制比例避免模型过度悲观。第三个是折扣因子的选择。取太大模型会追求远期收益导致推荐结果偏向用户可能喜欢但短期不会点击的内容拉低在线 CTR取太小强化学习就退化成监督学习失去建模长期价值的优势。0.9 到 0.95 是推荐场景的合理区间具体值需要通过小流量实验调。本文还有配套的精品资源点击获取
返回列表