Skip to content

基于标签的推荐系统(MovieLens)— 张量分解与LDA对比实验

基于 MovieLens 数据集,实现并对比了两类标签推荐算法:

  • 张量分解(CP、Tucker)用于建模用户-电影-标签三元交互
  • LDA 主题模型用于建模标签的潜在语义主题

一、项目目标与思路概览

  • 目标:学习并比较“结构化交互建模(张量分解)”与“语义主题建模(LDA)”两类方法的标签推荐效果与代价。
  • 场景:给定用户 u 和电影 i,预测其可能关联的标签 t(如“剧情紧凑”“科幻”“经典”等)。
  • 思路:
    1. 三元交互张量建模:将用户-电影-标签构成三维张量X,通过分解得到低秩因子,完成潜在语义表示与打分预测。
    2. 主题语义建模:将标签视作词,用户与电影分别构成“文档”,用 LDA 学习主题分布,从主题-词分布中生成标签推荐。

二、数据预处理(data_preprocessing.py)

【处理目标与痛点】

  • 标签现实中噪声多、同义多、长尾重:直接建模会显著拉低召回与泛化。
  • 预处理追求三件事:清洗(去噪)、筛选(留强信号)、映射(可建模)。

【流水线与实现要点】

  • 输入与最小依赖
    • 必需 tags.csv(userId, movieId, tag[, timestamp]),代码按需依赖 ratings/movies 进行检查与展示。
  • 清洗策略(TagCleaner.clean_tag_text)
    • 统一小写、去除特殊字符与多余空格、长度过滤(<2 或 >50 直接丢弃)。
    • 例:'Sci-Fi!!! ' → 'sci-fi';' epic classic ' → 'epic classic'。
  • 有效标签筛选(TagCleaner.analyze_tags)
    • 频次阈值 min_frequency:剔除极少出现的标签(弱信号/拼写异常)。
    • 最小用户数 min_users:避免“单人自造词”导致过拟合。
    • 可选 max_tags:仅保留最高频 Top-N,控制词表上限,利于大规模训练。
    • 建议阈值(经验):ml-small 可取 min_frequency∈[3,5]、min_users∈[2,5];大数据适当上调频次、保留 max_tags=1k~5k。
  • ID 映射与稳定性
    • user_mapping, movie_mapping, tag_mapping 采用排序后的唯一值生成,确保训练/评估一致可复现。
    • 注意:任何采样或过滤后需重新生成映射,避免索引错位。
  • 张量构建与内存自适应(MovieLensLoader.create_tensor_data)
    • 密集张量适用于小规模:X ∈ R^{U×I×T},dtype=float32。
    • 内存估算:Mem_MB ≈ U×I×T×4 / 1024^2;若 >500MB 自动切换为稀疏。
    • 稀疏表示(create_sparse_tensor_data):dict[(u,i,t)] → count,非零仅与观测条目线性相关,极大节省内存。
  • 智能采样(MovieLensLoader.sample_data)
    • 按比例 sample_ratio 抽样后,再选 Top 活跃用户与热门电影,最后可限制标签词表规模。
    • 推荐顺序:ratio → users → movies → max_tags;能最大限度保留强交互信号。
  • 复杂度与性能
    • 清洗/统计:O(N) ~ O(N log N)(groupby/count)。
    • 密集张量构建:O(N) 时间,O(U·I·T) 空间(受限于内存)。
    • 稀疏张量构建:O(N) 时间与空间,适合长尾稀疏场景。
  • 边界与鲁棒性
    • 缺失/空标签直接过滤;重复三元组进行计数累加(更贴近多次标注强度)。
    • 跨集映射:评估阶段仅对同时出现在 user/movie/tag 映射中的样本打分,避免冷启动噪音影响指标。

三、算法设计与实现

(1)张量分解(tensor_factorization.py)

【问题形式化】

  • 三元交互张量 X(u,i,t) 表示“用户 u 给电影 i 赋予标签 t”的强度(计数)。
  • 我们学习到低维因子后,对任一 (u,i) 对的每个标签 t 计算预测分数 s(u,i,t),供排序推荐。

【CP 分解(Parafac)】

  • 模型:X ≈ Σ_{r=1..R} a_r ⊗ b_r ⊗ c_r,对应因子矩阵 U∈R^{U×R}、V∈R^{I×R}、T∈R^{T×R}。
  • 预测:s(u,i,t) = <U[u], V[i], T[t]> ≈ dot(T, U[u]⊙V[i]) 后接 softmax 归一化用于排序。
  • 实现要点
    • 调用 tensorly.parafac(rank=R, n_iter_max=200, tol=1e-7, random_state=42)。
    • 训练后保存 U,V,T;预测阶段采用逐点乘与投影,时间复杂度 O(R·T)。
  • 优势与适用性
    • 参数少、速度快、解释性好(每个维度可理解为“潜语义”)。
    • 对复杂高阶交互的拟合能力有限,过小 R 可能欠拟合。

【Tucker 分解】

  • 模型:X ≈ G ×1 U ×2 V ×3 T,其中 G∈R^{R×R×R} 为核心张量。
  • 预测:先通过 einsum 将 U[u]、V[i] 与 G 融合,再与 T 投影到标签维度,表达力强。
  • 实现要点
    • tensorly.tucker(rank=[R,R,R], n_iter_max=150, tol=1e-7)。
    • 预测复杂度略高于 CP,但能建模维度间耦合(核心张量提供“交互的交互”)。
  • 优势与适用性
    • 表达能力强、对噪声更鲁棒;但参数与训练时间更大,易过拟合,需更强正则与早停。

【稀疏-CP(ALS 自实现)】

  • 背景:U·I·T 极大时,密集分解不可行,需仅在观测三元组上优化。
  • 目标:最小化 Σ_{(u,i,t)∈Ω} (X[u,i,t] − <U[u]⊙V[i], T[t]>)^2 + λ(||U||^2+||V||^2+||T||^2)。
  • 实现细节
    • 初始化:Xavier;数据结构:为每个 u/i/t 预聚合相邻索引,减少重复检索。
    • ALS 更新:固定两组、更新一组,解正规方程 (A^T A + λI)x = A^T b;全程向量化。
    • 收敛:每 10 次计算损失,改善 < 1e-5 早停;max_iter 可配置。
    • 预测同 CP:scores = T dot (U[u]⊙V[i]),softmax 排序。
  • 工程取舍
    • 线性依赖于非零元素 nnz,适合 ml-25m。
    • 可能略逊于密集最优,但在成本-精度间取得良好平衡。

(2)LDA 主题模型(lda_recommendation.py)

【思想与建模】

  • 将“标签”视作词,将“用户”和“电影”分别视作文档,学习文档-主题分布 θ 与主题-词分布 φ。
  • 推荐逻辑:对 (u,i) 融合其主题偏好,进而在 φ 上生成标签排序。

【实现流程】

  • 文档构造:按 userId / movieId 聚合 cleaned_tag,拼接成“文本”。
  • 向量化:CountVectorizer(min_df=2, max_features≤5000) 构建词频矩阵,过滤极稀词。
  • 训练:sklearn LatentDirichletAllocation(n_components=K, doc_topic_prior=α, topic_word_prior=β, max_iter, batch)。
  • 主题解释:打印每个主题 top-words,用于报告展示与错误分析。
  • 推荐细节
    • θ_u = user topic dist;θ_i = movie topic dist;合并 θ_ui = (θ_u + θ_i)/2。
    • 标签打分:p(tag) ∝ θ_ui · φ(components_),按概率排序取 Top-K。
    • 词表对齐:仅返回同时存在于词表与 tag_to_idx 的标签,确保一致性。
  • 优势与适用性
    • 语义可解释性强、对冷启动友好(若用户/电影缺历史则退化为均匀分布)。
    • 对主题数敏感,短文本场景受限;依赖文本假设,可能与交互强度不完全一致。

【超参与调优建议】

  • R(因子数):小数据 30~50;大数据/复杂关系 50~100。
  • K(主题数):20~80,过小欠分辨、过大易稀疏;结合困惑度与可解释性取折中。
  • λ(正则):0.001~0.05;数据越稀疏越需要正则。
  • 迭代:CP/Tucker 150~300;ALS 50~150;LDA 50~150。
  • 实践技巧:先用较小 R/K 快速对齐流程,再加大参数做最终跑表。

【何时选谁?】

  • 更看重交互结构:优先 CP/Tucker;数据稀疏大规模:稀疏-CP。
  • 更看重语义解释/冷启动:优先 LDA;也可将 LDA 主题作为张量侧特征融合。

四、评估指标与流程

【数据切分策略】

  • 随机划分(main_comparison.py):train_test_split(test_size=0.2, seed=42),快速基线。
  • 时间划分(TensorEvaluator.train_test_split_temporal):按时间先后切分,更贴近线上部署。
  • 建议:报告对比两种切分的指标差异,突出时序泄露的影响与业务意义。

【指标定义与计算】

  • 以用户-电影为单位进行集合命中评估,最终对全体样本做微平均(micro-averaging)。

  • 对 LDA(多标签集合):

    • Precision@K = 命中总数 / 预测总数
    • Recall@K = 命中总数 / 测试真值标签总数
    • F1@K = 2PR / (P+R)
    • 实现:按 (userId, movieId) 分组,将 cleaned_tag 作为真集,对比 top-K 推荐集合。
  • 对张量分解(简化近似)

    • 当前实现将每条测试 (u,i,t) 视作单标签事件,计算 top-K 是否命中 true tag,precision≈recall≈hit-rate。
    • 局限:未对“同一 (u,i) 的多真标签”做集合评估,偏向乐观。可在后续按 LDA 的评估方式做多标签聚合,得到更一致的比较。
  • K 的选择:常用 K∈{5,10,20};报告中建议展示指标-曲线(随 K 变化),体现排序稳健性。

【流程串联与产出物】

  • 加载与清洗 → 采样(可选)→ 映射与张量/文档构建 → 模型训练(CP/Tucker/稀疏-CP/LDA)→ 评估与对比 → 可视化出图。
  • recommendation_comparison_results.png 四宫格解读
    • 左上(Precision@K):观察随 K 变化的相对优势;CP 更稳、Tucker 在高维时更强,LDA 受主题数影响。
    • 右上(训练时间):体现“表达力 vs 代价”的权衡;为工程落地提供依据。
    • 左下(摘要表):一图复盘关键指标,便于在答辩中快速引用数值。
    • 右下(Top-15 标签分布):展示数据语义谱系,帮助解释模型行为与错误来源。
  • 置信度与稳健性(可作为加分点在汇报中说明)
    • 建议做 3~5 次不同随机种子的重复实验,报告均值±标准差。
    • 可选:对用户-电影对做 bootstrap 采样,给出指标置信区间(未在代码中实现,报告可口头说明方法与价值)。

五、调参与性能建议

  • 精度优先
    • 增加 n_factors(如 30->40)
    • 提高迭代次数(CP/Tucker:n_iter_max;稀疏ALS:max_iter)
    • 适当降低 TagCleaner 阈值(min_frequency、min_users)
  • 速度/内存优先
    • 启用 use_sparse(大数据集自动)
    • 使用 sample_config 采样活跃用户/热门电影
    • 降低 LDA n_topics 与 max_iter
  • 可解释性
    • 使用 LDA 打印主题 top-words(print_top_topics)
    • 张量分解因子可用于分析用户/电影/标签的潜在维度

六、结果解读与展示建议

  • 图表 recommendation_comparison_results.png
    • 左上:Precision@5/10/20 对比
    • 右上:训练时间对比(秒)
    • 左下:关键指标摘要表
    • 右下:Top-15 标签分布
  • 汇报要点
    • 数据清理与采样策略如何影响有效信号与泛化
    • CP 与 Tucker 的表达力-代价权衡
    • LDA 在主题可解释性与冷启动上的优势
    • 小数据集与大数据集下最佳实践的差异 F34231B5F6AB47200F45C8E2999A5754.png