
简介MMRec是一套面向推荐系统开发者与科研人员的多模态推荐工具箱基于Python实现旨在降低多模态推荐模型的研发与实验门槛。资源包共59个文件以29个py源码、18个yaml配置、4个ipynb实验笔记为主辅以md说明、png示意图与pdf报告压缩包约446KB结构紧凑、便于按模块查阅。工具箱集成协同过滤、矩阵分解及MTransE、CDAE、MMoE等10余个先进模型覆盖从传统方法到深度学习架构并支持Yelp、Amazon等公开数据集方便开展基准测试与对比实验。通过Jupyter Notebook提供数据加载、模型训练、结果评估的完整流程模板配合简洁API与可扩展设计研究者可快速集成自有算法。内容还涉及特征编码、评分转交互、双图神经网络等预处理脚本适合电商、社交媒体、内容推荐等场景的算法验证与项目落地。目前已有267人学习下载可作为多模态推荐入门与进阶的实践参考。1. 多模态推荐工具箱 MMRec10 多个模型开箱即跑Jupyter Notebook 里把实验流程走通如果你正在做推荐系统相关的课程设计、毕设或者论文实验大概率会遇到一个很现实的问题单模态的协同过滤还没调明白导师或者业务方已经开始要求「融合图像和文本特征」了。MMRec 这个工具箱就是冲着这个场景来的——它把 10 多个多模态推荐模型收进一个 Python 工程里用 Jupyter Notebook 串起从评分转交互、特征编码到模型训练评估的完整链路。拿到手的是一个 zip 包解压后能看到main.py、configs、models、data、utils这些目录还有preprocessing下几个预处理 notebook。它适合两类人一是想快速跑通多模态推荐 baseline 的研究生二是不想从零搭数据管线的算法工程师。下面按「资源结构 → 数据预处理 → 模型训练 → 避坑 → 进阶」的顺序拆一遍。2. 拆开 MMRec 的目录从 main.py 到 preprocessing 的完整链路2.1 工程骨架与各目录职责解压之后先别急着跑代码花五分钟把目录结构看清楚后面调参和排错会省很多时间。MMRec 的顶层结构大致是这样路径作用main.py训练入口负责解析配置、构建模型、跑 epochconfigs/各模型的 YAML/JSON 配置文件超参都在这里改models/多模态推荐模型实现每个模型一个文件data/数据集存放与加载逻辑utils/通用工具含数据加载器、评估指标、日志preprocessing/数据预处理 notebook评分转交互、特征编码等README.md项目说明与运行指引main.py是整个工程的调度中心。它做的事情不复杂读配置 → 加载数据 → 实例化模型 → 训练循环 → 评估。你不需要改main.py的主体逻辑改配置文件和模型文件就够了。configs目录下每个模型通常对应一份配置里面写着学习率、embedding 维度、batch size、训练轮数这些。我一般会先把配置复制一份改名比如dualgnn_yelp.yaml避免污染原始文件。models目录是核心资产。协同过滤、矩阵分解、深度学习类的模型都在这里每个文件基本遵循「继承基类 → 实现 forward → 实现 loss」的写法。如果你想加自己的模型照着现有文件的接口抄一个就行这是这个工具箱可扩展性的体现。2.2 预处理 notebook 的执行顺序preprocessing目录下的 notebook 不是随便跑的有先后依赖。从文件名能看出大致顺序0rating2inter.ipynb把评分数据转成隐式交互0/1这是推荐系统常见做法显式评分转成「有没有交互」。1splitting.ipynb划分训练集、验证集、测试集。2reindex-feat.ipynb对特征和 ID 重新索引保证从 0 开始连续。3feat-encoder.ipynb用预训练编码器把图像、文本特征编码成向量。dualgnn-gen-u-u-matrix.py生成用户-用户关系矩阵给 DualGNN 这类用图结构的模型准备输入。常见做法是先跑0再跑1因为划分要在交互转换之后做。2和3的顺序取决于你的特征来源——如果原始特征已经是 ID 对齐的可以先3再2如果特征文件里的 ID 和交互文件不一致必须先2对齐再3编码。这个顺序搞反了后面模型加载特征时会直接报 index out of range。提示每个 notebook 跑完检查一下输出目录确认生成了对应的.inter、.feat文件再往下走不要一口气全跑完再回头找问题。2.3 配置文件里真正要改的参数很多人跑不通 MMRec不是代码有 bug是配置文件没对上自己的数据。以一份典型的模型配置为例需要关注的字段# configs/xxx.yaml 示例结构 dataset: yelp # 数据集名称要和 data/ 下的目录名一致 data_path: ./data/ # 数据根路径 embedding_size: 64 # 嵌入维度显存不够就降到 32 batch_size: 2048 # 批大小图模型建议 1024~4096 lr: 0.001 # 学习率图模型常用 1e-3 epochs: 100 # 训练轮数 reg_weight: 0.01 # 正则化权重过拟合就调大dataset字段必须和data/下实际目录名完全一致大小写敏感。embedding_size和batch_size是最容易翻车的两个参数——图神经网络类的模型对显存占用比较敏感如果你在本地跑先把embedding_size设成 32、batch_size设成 1024 试一轮确认能跑通再往上加。reg_weight是正则项系数训练集 loss 一直降但验证集指标不涨的时候把它从 0.01 调到 0.1 往往有效。3. 跑通第一个模型从数据加载到评估的完整命令3.1 环境准备与依赖安装MMRec 是 Python 工程依赖主要是 PyTorch、NumPy、SciPy、Pandas 以及 Jupyter。建议用 conda 建一个独立环境避免和系统 Python 打架# 创建并激活环境 conda create -n mmrec python3.8 -y conda activate mmrec # 安装核心依赖版本按你 CUDA 情况调整 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy scipy pandas scikit-learn jupyter pyyaml tqdmPython 3.8 是比较稳的选择很多推荐系统的老代码对新版本兼容性一般。PyTorch 的 CUDA 版本根据你显卡驱动来没有 GPU 就用 CPU 版只是训练会慢很多。装完之后python -c import torch; print(torch.cuda.is_available())确认一下 GPU 是否可用。3.2 数据预处理的实际操作假设你已经把数据集放到了data/下接下来按顺序跑预处理。以 Jupyter Notebook 为例启动之后逐个执行# 启动 Jupyter jupyter notebook # 在浏览器里打开 preprocessing/0rating2inter.ipynb # 修改里面的输入路径为你的数据集路径然后 Run All0rating2inter.ipynb的核心逻辑是把评分矩阵转成交互矩阵。代码里通常有一个阈值判断比如评分大于 3 算正交互。这个阈值直接影响正负样本比例阈值太高会导致正样本太少模型学不到东西阈值太低会引入噪声。常见做法是取数据集中位数或者 4 分5 分制。# 评分转交互的核心逻辑示意 import pandas as pd import numpy as np df pd.read_csv(data/yelp/ratings.csv) # 评分 4 视为正交互 df df[df[rating] 4] # 重新索引 user 和 item保证从 0 开始连续 user_ids {uid: idx for idx, uid in enumerate(df[user_id].unique())} item_ids {iid: idx for idx, iid in enumerate(df[item_id].unique())} df[user] df[user_id].map(user_ids) df[item] df[item_id].map(item_ids) df[[user, item]].to_csv(data/yelp/yelp.inter, sep , indexFalse, headerFalse)这段代码做了三件事过滤低评分、重映射 ID、输出交互文件。重映射这步很关键推荐系统的 embedding 层是按 ID 索引的如果 ID 不连续或者不从 0 开始embedding 查表会直接崩。1splitting.ipynb负责按用户或按时间划分数据集通常是 8:1:1 或者 7:1:2。划分时要保证每个用户在训练集里至少有一条交互否则该用户的 embedding 训不起来。3.3 启动训练与日志解读预处理跑完回到工程根目录启动训练# 指定配置文件和 GPU python main.py --config configs/xxx.yaml --gpu 0--config指向你要跑的模型配置--gpu指定显卡编号没有 GPU 就去掉这个参数。训练开始后终端会打印每个 epoch 的 loss 和评估指标。重点看两个东西训练 loss 是否稳定下降验证集的 Recall20 和 NDCG20 是否在涨。如果 loss 降但指标不涨大概率是过拟合调大reg_weight或者加 dropout。如果 loss 震荡厉害把学习率降到 1e-4 试试。评估指标一般在utils里实现常见的是 Recall 和 NDCG。RecallK 衡量的是「前 K 个推荐里命中了多少真实交互」NDCGK 额外考虑了命中位置越靠前权重越高。这两个指标是多模态推荐论文里的标配跑通之后可以直接拿来做对比实验。4. 避坑与排查多模态推荐实验里最容易翻车的五件事4.1 特征文件和交互文件 ID 对不上现象模型加载特征时报IndexError: index out of range或者训练 loss 一直是 NaN。原因2reindex-feat.ipynb没有跑或者跑的时候用的 ID 映射和交互文件不一致。多模态推荐里图像/文本特征是按 item ID 索引的如果特征文件里的 item ID 是原始 ID而交互文件已经重映射过两边就对不上。解决先跑2reindex-feat.ipynb确保特征文件和交互文件用同一套 ID 映射。跑完之后检查两个文件的 item ID 范围是否一致用wc -l看行数用head看前几行 ID 是否从 0 开始。4.2 显存不够导致训练中断现象跑图神经网络类模型时几个 epoch 之后报CUDA out of memory。原因embedding_size或batch_size设太大或者用户-用户关系矩阵太稀疏但维度太高。DualGNN 这类模型要构建邻接矩阵显存占用比普通矩阵分解高不少。解决先把batch_size减半再把embedding_size从 64 降到 32。如果还不行检查dualgnn-gen-u-u-matrix.py生成的矩阵是不是太稠密可以加一个相似度阈值过滤掉弱连接。4.3 预处理 notebook 顺序跑反现象3feat-encoder.ipynb跑完生成的.feat文件加载时报维度不匹配。原因先跑了3再跑2特征编码时用的 ID 和重新索引后的 ID 不一致。或者0和1的顺序反了划分数据集时用的还是原始评分。解决严格按0 → 1 → 2 → 3的顺序执行。如果已经跑乱了把输出目录清空重来。我一般会在每个 notebook 开头加一个检查 cell确认上一步的输出文件存在再往下跑。4.4 评估指标不涨但 loss 在降现象训练 loss 从 0.8 降到 0.1但验证集 Recall20 一直在 0.05 左右不动。原因过拟合或者正负样本比例严重失衡。多模态推荐里如果正样本太少模型会倾向于把所有样本预测为负loss 很低但指标很差。解决调大reg_weight加 dropout或者对负样本做下采样。另外检查一下0rating2inter.ipynb里的评分阈值是不是设太高了导致正样本过少。4.5 配置文件 dataset 名称和目录不匹配现象启动训练直接报FileNotFoundError找不到数据文件。原因configs里的dataset字段和data/下的实际目录名不一致或者data_path写的是相对路径但你在别的目录下执行main.py。解决dataset字段和目录名逐字符核对大小写、下划线都要一致。data_path建议用绝对路径或者在工程根目录下执行命令。这个坑很低级但特别常见血泪经验是每次换数据集先ls data/确认目录名。5. 进阶用法加自己的模型和做对比实验5.1 照着现有模型接口扩展新模型MMRec 的可扩展性体现在模型文件的接口统一。你打开models下任意一个模型文件基本能看到这几个方法__init__里定义 embedding 层和网络结构forward里写前向传播calculate_loss里写损失函数。要加自己的模型复制一个结构最接近的文件改名然后改这三处。# models/my_model.py 骨架示意 import torch import torch.nn as nn from models.base import BaseModel class MyModel(BaseModel): def __init__(self, config, dataset): super().__init__(config, dataset) # 用户和物品的 embedding self.user_embedding nn.Embedding(dataset.user_num, config[embedding_size]) self.item_embedding nn.Embedding(dataset.item_num, config[embedding_size]) # 多模态特征投影层 self.feat_proj nn.Linear(dataset.feat_dim, config[embedding_size]) def forward(self, user, item): u_emb self.user_embedding(user) i_emb self.item_embedding(item) # 融合多模态特征 feat self.feat_proj(self.item_feat[item]) score (u_emb * (i_emb feat)).sum(dim-1) return score def calculate_loss(self, interaction): user interaction[0] item interaction[1] score self.forward(user, item) # BPR loss loss -torch.log(torch.sigmoid(score)).mean() return loss关键是继承基类并实现forward和calculate_loss。基类里通常已经处理了数据加载、优化器构建、评估这些通用逻辑。写完模型文件后在configs下加一份对应配置main.py就能识别到。注意dataset.feat_dim要和你的特征文件维度一致否则投影层会报维度错误。5.2 多模型对比实验的组织方式做对比实验时不要手动一个个跑然后抄结果容易出错。我一般写一个 shell 脚本批量跑#!/bin/bash # run_all.sh 批量跑多个模型 for config in configs/*.yaml; do echo Running $config python main.py --config $config --gpu 0 21 | tee logs/$(basename $config).log done每个模型的日志单独存一份跑完之后从日志里提取最好的 Recall20 和 NDCG20。这样既省事又可追溯。如果要做消融实验比如去掉图像特征看指标掉多少就在配置里加一个use_image: false的开关在模型forward里根据这个开关决定是否融合图像特征。5.3 验证实验可复现性的几个习惯多模态推荐实验最怕结果不可复现。我的习惯是每次跑实验前固定随机种子在main.py开头加import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False然后在配置文件里把seed作为一个参数传进去。这样同一份配置跑两次结果基本一致。另外每次改完预处理或者模型代码把对应的输出文件版本号记一下不然过两天自己都忘了哪个结果对应哪版数据。从那以后我每次跑对比实验都强制走一遍「固定种子 → 记录配置 → 单独存日志」的流程省了很多回头找结果的时间。希望帮到你。本文还有配套的精品资源点击获取