
复现一篇计算机视觉顶会论文最耗时的从来不是读懂公式而是把环境搭起来、把数据喂进去、把模型跑通这一整套工程流水线。我自己刚入行那会儿光是在一台新服务器上从零复现一篇检测类论文就折腾了将近两周其中一半时间浪费在环境冲突和数据集格式上。后来用上mmdetection这个周期被压缩到一到两天。这篇内容就是围绕用mmdetection快速复现顶会论文这件事把我踩过的坑、验证过好用的流程、以及一些不太会写进官方文档的经验整理出来。这套方法论适合这几类人刚入门想跑通一篇检测论文做baseline的研究生、需要快速验证idea是否有效的工程师、以及想系统学习检测框架但不知道从哪里下手的同学。文章不会停留在安装-训练-测试这种官方demo层面而是把从论文到代码、从数据集到配置文件、从单卡训练到结果评估的完整链路拆开讲清楚。1. 复现顶会论文先想明白这三件事1.1 复现不等于从零写模型很多人拿到一篇论文第一反应是照着论文里的公式和网络结构图从数据加载开始一行行手写模型。这个思路放在五六年前还算可行但放在今天效率太低。顶会论文里的模型结构绝大多数都是基于几个主流框架的模块组合尤其是检测方向backbone、neck、head这三段式结构几乎成了标配。mmdetection的价值就在于把这套标配做成了标准化乐高积木。你不需要自己实现ResNet的前向传播不需要手写FPN的特征融合逻辑更不用纠结RPN的anchor分配细节。论文里说我们用ResNet-50作为backboneFPN作为neck对应的就是你配置文件里几行参数的事。我曾经帮一个师弟复现一篇CVPR的检测论文他花了两周手写模型结果训练效果始终不对后来我让他直接基于mmdetection改配置三天就跑出了和论文接近的指标。这不是说理解论文本身不重要而是说工程实现层面积累已经足够成熟你应该把精力花在理解论文的贡献点上而不是重复造轮子。1.2 复现目标决定你的投入度复现论文之前先问自己一个问题我要复现到什么程度如果你的目标是跑通训练流程、拿到一个baseline指标那直接找官方仓库或者mmdetection里已经集成的模型即可重点是跑通数据链路如果你的目标是验证自己的改进idea那需要在官方实现基础上修改特定模块这时候你只需要理解你要改的那部分代码逻辑如果你的目标是把论文里的每个细节都吃透那就需要逐行阅读代码甚至要对比论文里的消融实验。想清楚这个目标能避免你陷入两个极端一是什么都看不懂就瞎改配置训完发现指标不对也找不到原因二是过分纠结论文里的一句话、一个参数反复尝试一个月还没开始训练。1.3 选对论文比努力更重要不是所有论文都适合用mmdetection快速复现。本身就用mmdetection或者类似框架做的论文复现成本最低你只需要找到官方配置改改数据集路径其次是用PyTorch实现、但结构比较标准的检测论文这类需要你写少量自定义模块难度最高的是那些用了非常特殊的算子或者自定义CUDA实现的论文即便有mmdetection帮助你也可能要花大量时间做适配。我的建议是刚开始练手时选择那些已经被mmdetection官方收录的论文比如Faster R-CNN、RetinaNet、FCOS、ATSS这类经典模型。这些模型在框架里都有完整实现和预训练权重你可以先跑通一个完整流程建立对框架的熟悉度再去复现更新的论文。我见过太多人一上来就挑战最新最好的论文结果卡在环境里一个月出不来信心严重受挫。2. 环境搭建版本匹配才是最大的坑2.1 先搞清CUDA、PyTorch、MMCV三者的关系mmdetection的环境安装最大的坑不是装不上而是版本之间的匹配关系。PyTorch、CUDA、MMCV这三者的版本必须严格对应否则轻则报出莫名其妙的错误重则训练到一半直接崩掉。我用一个生活化的类比来解释这三者的关系CUDA是显卡的驱动程序PyTorch是你的开发框架MMCV是mmdetection依赖的核心工具库。你装软件的时候PyTorch需要知道怎么通过CUDA调用显卡MMCV又需要知道PyTorch的API长什么样。这三方任何一方版本对不上就像你拿着最新款的充电线去充老款手机——物理上插不上或者插上了也充不进电。以我目前常用的组合为例CUDA 11.7 PyTorch 1.13 MMCV 2.0.1 mmdetection 3.x这套组合在RTX 3090、A100上都跑得很稳。如果你用的是更新的显卡比如RTX 4090那可能需要CUDA 11.8以上甚至12.x具体以PyTorch官方支持为准。2.2 一套验证过的安装流程下面是我在全新服务器上安装mmdetection 3.x的完整流程这套流程我用了很多次基本不会出问题。第一步创建独立的conda环境。这一步非常重要千万不要图省事直接装到base环境里。不同项目的依赖经常冲突独立环境能让你少踩很多坑。conda create -n mmdet python3.8 -y conda activate mmdet第二步安装PyTorch。这里的核心是选择与你CUDA版本匹配的PyTorch版本建议去PyTorch官网用它的配置器生成命令而不是凭记忆敲。以CUDA 11.7为例pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117第三步安装MMCV。mmdetection 3.x要求MMCV版本通常为2.0以上推荐用mim安装它可以自动帮你选择与当前环境匹配的MMCV版本pip install -U openmim mim install mmcv2.0.0第四步安装mmdetection。从GitHub克隆源码并以开发模式安装这样你改代码不用重新安装git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection pip install -v -e .整个安装过程一般需要10到20分钟取决于网络和机器性能。2.3 安装失败的常见原因及对策我在不同机器上装过很多次总结出三个最容易出问题的地方。第一个是网络问题。pip下载PyTorch和MMCV时有时候会因为网络原因中断。解决办法是使用国内镜像源比如清华源。但注意PyTorch官方whl包在镜像源上不一定全建议PyTorch本体用官方源其他依赖用镜像源。第二个是GCC版本问题。如果你需要从源码编译mmcvGCC版本太老会编译失败。mmcv 2.0以上版本对GCC版本要求较高建议GCC 7.0以上。检查方式gcc --version第三个是Python版本问题。mmdetection 3.x支持Python 3.7到3.10不要用3.11有时代码兼容性会有问题。我遇到过用Python 3.11装mmdetection之后某些第三方库装不上的情况最后老老实实退回3.8。3. 数据集转换是复现的第一道坎3.1 为什么COCO格式成为了通用语言绝大多数顶会检测论文都在COCO数据集上评估这也意味着mmdetection默认支持的数据格式就是COCO格式。但很多时候你要复现的论文用的是其他数据集或者你自己的私人数据这时候第一步就是转换格式。COCO格式的核心是一个JSON文件里面包含几个关键字段images图片信息、annotations标注信息、categories类别信息。每个标注都包含bbox坐标左上角x、y宽w、高h注意不是中心点坐标和宽高、area面积、category_id类别ID、iscrowd标志。我见过不少人在数据转换这一步栽跟头主要原因是没搞清楚两种常见的标注格式差异VOC格式用XML文件存标注每个目标一个XMLYOLO格式用TXT文件存标注每行一个目标坐标是归一化后的中心点加宽高。转换的核心就是把这些不同格式统一到COCO的JSON结构。3.2 从VOC格式转COCO格式的完整代码下面是一段将VOC格式XML标注转换为COCO JSON格式的代码我实际用过很多次直接复制改路径就能用import xml.etree.ElementTree as ET import os import json voc_root path/to/VOCdevkit/VOC2007 img_dir os.path.join(voc_root, JPEGImages) ann_dir os.path.join(voc_root, Annotations) save_path coco_format.json categories [{id: 1, name: car}, {id: 2, name: person}] cat_map {car: 1, person: 2} images [] annotations [] img_id 0 ann_id 0 for xml_name in os.listdir(ann_dir): xml_path os.path.join(ann_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) width int(size.find(width).text) height int(size.find(height).text) images.append({ id: img_id, file_name: filename, width: width, height: height }) for obj in root.iter(object): name obj.find(name).text if name not in cat_map: continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) w xmax - xmin h ymax - ymin annotations.append({ id: ann_id, image_id: img_id, category_id: cat_map[name], bbox: [xmin, ymin, w, h], area: w * h, iscrowd: 0 }) ann_id 1 img_id 1 coco_format { images: images, annotations: annotations, categories: categories } with open(save_path, w) as f: json.dump(coco_format, f, indent2) print(f转换完成共{len(images)}张图片{len(annotations)}个目标)这段代码不复杂但你需要注意几个细节。第一个细节是bbox的坐标体系COCO用的是左上角坐标加宽高而非中心点坐标第二个细节是类别ID从1开始而不是00通常保留给背景类第三个细节是iscrowd标志如果你的数据集中有紧密挨着的同类目标这个标志会影响到评估结果。3.3 数据目录组织方式转换完标注格式之后还需要把数据集组织成mmdetection能识别的方式。mmdetection的数据集加载方式是配置文件里指定数据集的根目录和标注文件路径没有强制规定目录结构但建议遵循一个清晰的组织方式。以复现一篇在COCO数据集上的论文为例典型的目录结构如下data/coco/ ├── annotations/ │ ├── instances_train2017.json │ └── instances_val2017.json ├── train2017/ │ ├── 000000000009.jpg │ └── ... └── val2017/ ├── 000000000042.jpg └── ...如果你用自己的数据集建议也保持类似结构根目录下一个annotations文件夹放JSON标注下面按train和val分两个图片文件夹。3.4 数据格式验证一个让我崩溃半天的教训格式转换完成之后不要急着训练先用可视化验证一下标注是否正确。mmdetection自带一个可视化脚本可以帮你快速检查# visualize.py from mmdet.apis import init_detector, inference_detector from mmdet.visualization import DetVisualizer import mmcv config_file configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py checkpoint_file path/to/checkpoint.pth model init_detector(config_file, checkpoint_file, devicecuda:0) img mmcv.imread(demo.jpg, channel_orderrgb) result inference_detector(model, img) visualizer DetVisualizer() visualizer.add_datasample(result, img, result, draw_gtFalse, showTrue)如果你还没有训练好的模型也可以用COCO自带的标注可视化工具直接把JSON里的bbox画到图片上。我第一次自己转数据集时就是因为没做可视化验证直接开始训练训练完一看结果发现所有目标的检测框都偏了排查了半天才想起来是坐标转换时把x和y搞反了。这种错误如果能提前可视化三秒钟就能发现。4. config系统拆解真正要改的就那几个字段4.1 理解config的继承机制mmdetection最强大的地方在于它的配置文件系统。但很多新手第一次打开一个config文件看到几百行配置直接懵了。你不用全部看懂只需要抓住几个核心模块。mmdetection的config支持继承机制也就是说一个config文件可以继承另一个config文件然后只修改需要改动的部分。这在复现论文时非常有用因为绝大多数论文都是在某个baseline模型上做改进你只需要继承这个baseline的配置然后改掉论文里不同的部分。例如你想复现一篇基于Cascade R-CNN的改进论文可以先看mmdetection自带的cascade_rcnn配置configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py这个文件内容很短因为它继承自base配置_base_ [ ../_base_/models/cascade-rcnn_r50_fpn.py, ../_base_/datasets/coco_detection.py, ../_base_/schedules/schedule_1x.py, ../_base_/default_runtime.py ]这四行分别代表了模型结构配置、数据集配置、训练策略配置、运行时配置。你如果只是想跑通baseline只需改数据集路径。如果你想复现论文的改进点根据需要修改对应的模块文件或直接在配置里override。4.2 从论文到config的映射方法复现论文时你需要在论文的实验部分找到关键信息然后映射到config文件里。我习惯把下面几个关键信息做一个表格梳理论文信息config中的位置说明Backbone及预训练model.backboneresnet101还是resnet50是否加载预训练权重Neck结构model.neckFPN还是PANet输出通道数Head结构model.bbox_head分类分支和回归分支结构训练轮数train_cfg.max_epochs12即1x24即2x学习率optimizer.lr0.02是单卡8张常用的初始学习率数据增强train_pipeline随机翻转、多尺度训练等比如论文里写着我们使用ResNet-101作为骨干网络在COCO train2017上训练12个epoch初始学习率为0.02权重衰减0.0001那你的config改动如下model dict( backbonedict( depth101, init_cfgdict(typePretrained, checkpointtorchvision://resnet101) ) ) optimizer dict(typeSGD, lr0.02, momentum0.9, weight_decay0.0001) train_cfg dict(typeEpochBasedTrainLoop, max_epochs12, val_interval1)4.3 最容易忽略的预训练权重问题复现论文时有一个细节特别容易被忽略backbone是否使用ImageNet预训练权重。绝大多数论文都会使用ImageNet预训练权重来初始化backbone这样能显著加快收敛速度并提升最终精度。mmdetection的稳定版本中预训练权重的指向通常在backbone配置里backbonedict( typeResNet, depth50, init_cfgdict(typePretrained, checkpointtorchvision://resnet50) )这里有个大坑mmdetection官方提供的config里默认使用自己训练发布的权重有些论文如果用了不同的初始化方式复现时指标会有明显差异。我的经验是除非论文明确说了从头训练train from scratch否则无脑用ImageNet预训练权重就对了。另外还有一点第一次跑训练时预训练权重会自动下载到~/.cache/torch/hub/checkpoints/目录。如果服务器没有外网你需要提前下载好权重文件放到指定目录否则会在训练启动时报错。4.4 修改config的推荐方式不建议直接修改官方config文件因为你可能会随时回退比较。更推荐的方式是新建一个自己的config文件然后继承官方配置# my_exp.py _base_ [ ../configs/cascade_rcnn/cascade-rcnn_r50_fpn_1x_coco.py ] # 修改数据集路径 data_root data/my_dataset/ train_dataloader dict( datasetdict( data_rootdata_root, ann_fileannotations/instances_train.json, data_prefixdict(imgtrain/) ) ) val_dataloader dict( datasetdict( data_rootdata_root, ann_fileannotations/instances_val.json, data_prefixdict(imgval/) ) ) # 修改类别数 model dict( roi_headdict( bbox_headdict(num_classes2) ) ) # 修改训练轮数和学习率 train_cfg dict(typeEpochBasedTrainLoop, max_epochs24, val_interval1) optim_wrapper dict(optimizerdict(lr0.01))这样修改的好处是官方配置始终保持原样你自己只维护增量改动git管理起来非常清晰论文里要对比的每项实验对应一个config文件互不干扰。5. 训练与评估让实验结果接近论文数值5.1 单卡训练的正确打开方式环境配置好、数据准备好、config改好后就可以开始训练了。单卡训练最简单命令如下python tools/train.py my_exp.py --work-dir work_dirs/my_exp--work-dir指定了日志和权重文件的保存路径建议每个实验单独建一个目录方便管理。训练启动后你会看到终端持续输出日志包括当前的epoch、iter、loss、学习率等信息。我习惯用两条命令并行查看训练状态# 查看实时日志 tail -f work_dirs/my_exp/20240101_120000.log # 查看已保存的权重和评估结果 ls work_dirs/my_exp/mmdetection默认在每个epoch结束后会跑一次验证输出当前模型在验证集上的mAP等指标。这个默认行为你在config里看到的那行val_interval1就控制着含义是每个epoch都做验证。5.2 多卡训练batch size与学习率的线性缩放如果你的机器有多张显卡强烈建议用多卡训练。多卡训练的启动命令是bash tools/dist_train.sh my_exp.py 4 --work-dir work_dirs/my_exp其中4是GPU数量。多卡训练的核心原则是batch size翻了几倍学习率就要跟着翻几倍。mmdetection官方配置默认batch size为16每张卡2张图8张卡学习率为0.02。如果你用4张卡每张卡2张图总batch size就是8学习率应该相应调整为0.01如果你只有1张卡总batch size是2学习率可能要降到0.0025左右才能稳定训练。这个规则不绝对但多数情况下遵循它能得到和论文接近的结果。我曾经在只有2张卡的情况下直接沿用官方0.02的学习率结果训练到第3个epoch loss开始剧烈震荡最后不得不中断重新调整。5.3 评估阶段正确解读COCO指标训练完成后用测试集评估模型的最终性能python tools/test.py my_exp.py work_dirs/my_exp/best_coco_bbox_mAP_epoch_12.pth --work-dir work_dirs/my_exp/test_result评估结果会输出一系列COCO指标。新手最容易犯的错是只盯着mAP0.5:0.95这一个数忽略了其他信息。实际上COCO官方指标体系中mAP0.5:0.95是综合指标mAP0.5是一个比较宽松的指标能看到模型大致的定位能力AR100和AR300用于判断模型对目标的召回能力如果你的任务对召回率要求高这两个指标比mAP更值得关注。有一个很实用的技巧如果你想复现论文的实验结果去论文里找到它的mAP0.5:0.95、mAP0.5、AP_s、AP_m、AP_l然后和你自己的结果对比。不同尺寸目标上的AP值差异能帮你定位问题所在比如论文里小目标AP是20%你只有15%那问题很可能出在数据增强策略或anchor配置上如果所有指标都低那可能是学习率策略或训练轮数有差异。5.4 可视化预测结果验证而不只是看数字评估指标只能告诉你好不好但要告诉你哪里不好还得可视化预测结果。mmdetection提供了专门的推理脚本可以对单张图片进行推理并保存可视化结果from mmdet.apis import init_detector, inference_detector import mmcv config_file my_exp.py checkpoint_file work_dirs/my_exp/best_coco_bbox_mAP_epoch_12.pth model init_detector(config_file, checkpoint_file, devicecuda:0) img mmcv.imread(test.jpg, channel_orderrgb) result inference_detector(model, img) model.show_result(img, result, out_fileresult.jpg)打开可视化结果图你很快就能发现指标背后的问题是不是有很多漏检是不是检测框比实际目标大一圈是不是某个类别完全没检出来这些问题光看指标是发现不了的。我每次做完一个实验都会从测试集里随机挑几十张图片可视化快速定位模型的系统性缺陷比盯着指标盲猜效率高得多。6. 实测中那些折腾人的报错6.1 训练半天loss一直是NaN这个问题我在复现论文时遇到过不止一次。loss出现NaN通常有三个原因学习率太大、数据中有异常标注比如bbox的width或height为负数、或者模型结构实现有数值稳定性问题。排查顺序建议先看log里loss是不是从第一个iter就是NaN如果是优先检查数据和标注用可视化脚本看标注有没有画错位置如果前几个iter正常、后面才变NaN优先怀疑学习率尝试降到原来的十分之一重新训练如果还不行再看模型的初始化方式或某些特殊算子是否有数值溢出风险。6.2 分布式训练时Address already in use多卡训练偶尔会报这个错通常是你上一次训练没有正常退出端口还被占用。解决办法是杀掉残留进程ps aux | grep python | grep -v grep | awk {print $2} | xargs kill -9这个命令会杀掉所有Python进程用的时候小心点确认当前机器上没有其他人在跑任务。6.3 数据加载慢导致GPU利用率上不去训练时如果发现GPU利用率只有30%到50%大概率是数据加载成了瓶颈。第一步先检查是不是没有开多进程加载config里dataloader应该包含num_workers参数建议设置为CPU核心数的一半第二步确认数据是否存储在机械硬盘上如果条件允许把数据放到SSD上能明显加快加载速度第三步检查是否开启了pin_memory设置为True可以减少数据传输开销。6.4 显存不足OOM时的几个实用招数显存不足是复现论文时最常见的问题之一尤其是你想用比较大的模型或比较大的batch size。在不能换更大显存显卡的前提下以下方法按推荐顺序排列降低batch size并同步调整学习率最简单直接开启梯度累积在batch size不变的情况下模拟更大batch的训练效果使用混合精度训练AMP半精度训练能省约一半显存且精度损失很小检查输入图像尺寸是否过大可以适当降低test_pipeline中的缩放尺寸。需要提醒的是mmdetection 3.x中混合精度训练只需要在config中加几行配置optim_wrapper dict( typeAmpOptimWrapper, optimizerdict(typeSGD, lr0.02, momentum0.9, weight_decay0.0001) )6.5 为什么复现的结果和论文对不上这是很多人在复现时最崩溃的时刻明明代码没问题、数据没问题可指标就是比论文低一两个点甚至更多。我想说这是完全正常的不用太焦虑。论文里的实验往往在大量GPU上跑了很多次报告的是最佳结果或多次实验的平均结果而你用不同GPU、不同随机种子、不同CUDA版本跑出来的结果自然会存在轻微浮动。一般来说和论文指标差距在1个点以内属于正常范围差异在1到2个点也可以接受如果差距超过2个点就需要仔细排查了。排查时优先检查三个地方数据增强策略是否一致、训练schedule是否一致、优化器超参数是否一致。这三个地方是复现实验中差异的最大来源。特别要注意的是mmdetection不同小版本之间默认的数据增强或训练策略可能有细微差异比如有些版本默认开启EMA指数移动平均有些版本默认不开启。7. 复现论文之外把mmdetection变成你的实验平台7.1 自定义模块的正确姿势当你准备在mmdetection基础上加自己的改进时核心流程可以概括为四步用Python写好自己的模块类注册到对应的注册器修改config指向你的模块跑通一个最小实验验证逻辑正确。以添加一个新的backbone为例你需要创建一个my_backbone.py文件内容框架如下from mmdet.registry import MODELS from mmengine.model import BaseModule MODELS.register_module() class MyBackbone(BaseModule): def __init__(self, **kwargs): super().__init__() # 初始化你的网络层 def forward(self, x): # 前向传播逻辑 return [x] # 返回多尺度特征图列表然后在config里替换model dict( typeMyDetector, backbonedict(typeMyBackbone, ...) )不要小看这个注册机制它是mmdetection整个生态扩展性的基础。你不需要修改框架的任何源码只需要把自己的模块注册进去框架就能像使用内置模块一样使用你的模块。7.2 多实验管理的经验复现和实验多了之后你会发现最头疼的不是写代码而是管理一堆实验结果。我现在遵循一个简单的规范每个实验一个独立文件夹文件夹命名包含实验名和日期例如my_exp_20240115config文件放在configs/my_exp/目录下和官方config分开每次实验启动时用--work-dir指定输出目录关键实验的结果和总结记录在一个markdown或Notion文档里方便随时回溯。这套习惯帮我避免了很多次咦这个结果到底是哪个配置跑出来的的尴尬。如果你准备长期做检测方向的研究强烈建议从一开始就养成这个习惯。7.3 一个判断你的复现是否成功的小技巧最后分享一个小技巧判断复现是否接近论文下载论文作者发布的模型权重用同样的测试脚本在同一个测试集上评估得到作者的代码能达到的指标。然后你再用自己的训练权重评估两个指标对比如果差距很小说明你的复现流程本身没有问题指标差异归结于训练细节或随机性如果差距大说明你的训练流程还有需要调整的地方。这个技巧的字面试探性极强但确实是最快定位问题的方式。我自己复现论文走下来最大的体会是工具只是工具run起来只是一个起点真正有价值的是在run的过程中对模型结构和训练细节产生的直觉。希望这篇内容能帮你少踩一些我已经替你们踩过的坑把精力花在研究本身而不是和框架搏斗上。