尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
深入解析MMDetection训练入口train.py:从启动到收敛的完整流程
1. 从一次训练中断说起为什么值得花时间读train.py很多人用 MMDetection 跑通第一个 demo 之后就再也没打开过tools/train.py。配置文件改一改命令行敲一下python tools/train.py configs/xxx.py看到日志开始刷 iteration 就觉得自己已经会了。直到某天遇到下面这些情况才发现绕不过去训练到一半中断想恢复却不知道--resume到底从哪里读状态想改一下验证频率翻遍 config 找不到对应字段最后发现是 runner 在管多卡训练时日志只在一个进程里打印想加个自定义输出完全不知道往哪插想接自己的分布式后端或者换一个更细粒度的 checkpoint 策略发现 config 里根本没有对应开关。这些问题的答案几乎全部藏在tools/train.py这个看起来只有一两百行的入口脚本里。它本身不实现任何模型、不写任何 loss但它决定了配置怎么被解析、模型怎么被构建、训练循环怎么被驱动、日志和权重怎么被保存。换句话说train.py是 MMDetection 整个训练体系的“总调度台”。这篇内容面向的是已经能跑通 MMDetection 基础训练、但想真正搞懂训练入口的读者。我会按“一个训练脚本从启动到收敛”的真实执行顺序把train.py拆成几块讲清楚参数解析与配置加载、环境与随机性设置、模型与数据集构建、runner 的组装逻辑、以及那些只有踩过坑才会注意到的细节。读完之后你应该能自己动手改造这个脚本而不是只能被动接受默认行为。需要先说明一点MMDetection 的版本迭代比较快train.py在不同大版本之间结构有差异。下面讲的是以 MMEngine 为训练引擎的 3.x 体系为主同时会点出和旧版 2.x 的关键区别因为现在还有大量项目跑在 2.x 上两边都懂才不至于看别人的代码时一脸懵。2. 启动前的第一道关卡参数解析与配置加载链路2.1 argparse 里那几个参数到底在控制什么打开train.py最先看到的一定是parse_args()。别看参数不多每一个都对应着训练流程里一个明确的开关。我把它整理成一张表方便对照参数作用容易踩的坑config指定配置文件路径相对路径是相对于当前工作目录不是脚本目录--work-dir覆盖配置里的工作目录不指定时用 config 里的work_dir两者都没有会报错--resume从最近一次 checkpoint 恢复传auto和传具体路径行为不同--no-validate训练中关闭验证只在训练期生效不影响最终测试--seed设置随机种子不设置时结果不可复现--deterministic开启确定性训练会牺牲一点速度且部分算子不支持--cfg-options命令行覆盖配置字段嵌套字段用点号连接写错不报错只是不生效--launcher指定分布式启动方式单卡时一般用 none这里面最值得展开的是--resume和--cfg-options。--resume的行为逻辑是这样的如果不传就是从头训练如果传auto脚本会去work_dir里找最新的last_checkpoint文件读出里面记录的 checkpoint 路径再加载如果传一个具体路径就直接加载那个文件。很多人以为--resume是个布尔开关其实它接受字符串值auto是它的一个特殊取值。这个设计的好处是你不需要手动去记上一次存到哪个 epoch 了。--cfg-options则是调试利器。比如你想临时把学习率调小验证一下不用去改配置文件直接python tools/train.py configs/xxx.py --cfg-options optimizer.lr0.001它的解析规则是把keyvalue按点号拆成嵌套字典然后深度合并到原配置上。这里有个坑值会被当成字符串解析如果你写optimizer.lr0.001它会被转成浮点数但如果你写一个列表或者更复杂的结构就得用类似 JSON 的写法。而且如果 key 路径写错了它不会报错只是默默加了一个没人用的字段你会纳闷为什么改了没效果。2.2 Config 从文件到对象的完整旅程参数解析完之后核心动作是Config.fromfile(args.config)。这一步看起来简单实际上做了不少事。MMDetection 的配置文件支持继承一个典型的 config 会长这样_base_ [ ../_base_/models/faster-rcnn_r50_fpn.py, ../_base_/datasets/coco_detection.py, ../_base_/schedules/schedule_1x.py, ../_base_/default_runtime.py ]fromfile会递归地把_base_里的文件先加载进来然后再用当前文件的字段去覆盖。覆盖是深度合并不是简单替换。举个例子base 里model.backbone.depth50你在当前文件写modeldict(backbonedict(depth101))最终depth会变成 101而 backbone 里其他没写的字段保持不变。这个机制是 MMDetection 配置体系好用的根本原因但也是新手最容易困惑的地方——你看到的最终配置和你写的文件往往长得不一样。加载完之后脚本还会做几件事把args.work_dir覆盖进去、把--cfg-options合并进去、处理--resume相关的路径。这里有个细节值得注意work_dir 的确定时机。如果你既没在命令行传--work-dirconfig 里也没写work_dir脚本会直接报错退出。这是有意为之的因为训练日志和权重必须有个明确的落盘位置不能靠默认值蒙混过关。提示调试配置合并结果最直接的办法是在train.py里cfg构建完成之后加一行print(cfg.pretty_text)把最终配置完整打印出来。比对着看比猜哪里被覆盖了高效得多。2.3 一个被忽视的环节配置里的变量引用MMDetection 的 config 支持在文件内部定义变量并被引用比如num_classes 80 model dict(bbox_headdict(num_classesnum_classes))这个能力来自Config类对 Python 执行环境的封装。它本质上是用exec执行配置文件所以配置文件里能写 Python 表达式。这带来灵活性的同时也有代价配置文件不是纯数据它会被执行。所以不要在里面写有副作用的代码也不要指望它能被静态解析工具完美处理。理解这一点你就能明白为什么有些 config 里能写if判断、能引用外部变量而有些工具却读不懂它。3. 环境、随机性与分布式训练开始前的隐形准备3.1 随机种子与确定性复现性的地基在模型构建之前train.py会调用init_random_seed()之类的逻辑来设置随机种子。这一步的重要性怎么强调都不过分。目标检测训练里涉及大量随机操作数据增强的随机裁剪、随机翻转、anchor 的随机采样、dataloader 的 shuffle。任何一个环节的随机源没固定住两次训练的结果就对不上。MMDetection 的做法是如果命令行传了--seed就用它否则从系统时间或者某个默认值生成一个种子然后把这个种子同步到random、numpy、torch三个库并且在分布式场景下保证所有进程用同一个种子。这里有个容易忽略的点dataloader 的 worker 进程也需要设置种子否则每个 worker 的增强行为会不一致。框架通过 worker_init_fn 处理了这件事但如果你自己写了自定义 dataset 并且用了额外的随机源就得自己保证它也被固定。--deterministic则是更进一步它会设置 cuDNN 为确定性模式并关闭一些非确定性的算子。代价是速度可能下降而且某些操作在确定性模式下会直接报错。我的建议是日常训练不开需要严格复现实验结论时再开。而且开了之后要接受“同样的代码在不同硬件上仍可能有微小差异”这个现实确定性不等于跨平台完全一致。3.2 分布式初始化单卡和多卡的分叉点接下来是分布式环境的初始化。MMDetection 3.x 把这部分交给了 MMEngine 的init_dist但逻辑本身没变根据--launcher参数决定用哪种后端。none单进程不初始化分布式pytorch使用 PyTorch 原生的分布式启动slurm在集群调度环境下使用mpi使用 MPI 后端。初始化完成之后会拿到当前进程的 rank、world_size、local_rank 等信息。这些信息后续会决定每个进程读数据的哪一部分、日志在哪个 rank 上打印、checkpoint 在哪个 rank 上保存。这里有个非常经典的坑日志重复打印。如果你在自定义代码里直接print多卡时每个进程都会打印一遍日志会乱成一团。正确做法是用 MMEngine 提供的MMLogger它内部会根据 rank 判断是否输出。同理任何写文件的操作都要考虑是不是只有主进程该做。还有一个隐蔽的问题分布式采样器的行为。多卡训练时dataloader 会用DistributedSampler它会把数据集切分到各个进程。如果你自己改了 dataset 的长度或者顺序要确保 sampler 的逻辑还成立否则会出现某些样本被重复采样、某些样本永远采不到的情况。3.3 一个实操建议先用单卡小数据跑通我见过太多人一上来就多卡 全量数据结果报错之后根本分不清是配置问题、分布式问题还是数据问题。稳妥的做法是先用单卡、把数据集截取一小部分、把训练轮数调到 1完整跑一遍。确认整个链路通了再逐步放大。train.py的很多报错信息在单卡下更清晰多卡下会被进程间的日志淹没。4. 模型、数据集与 runner训练主体的组装逻辑4.1 模型构建从 config 字典到 nn.Module配置准备好之后脚本会调用类似build_detector(cfg.model)的逻辑来构建模型。这一步背后是注册器机制config 里的type字段对应注册器里注册的类名构建函数根据type找到对应的类再把剩余字段作为参数传进去。这个机制的好处是解耦配置文件不需要 import 任何类只需要写类名字符串。代价是类名写错时的报错不够直观通常是一句“xxx is not in the registry”你得自己去确认是拼写问题还是没 import 对应的模块。模型构建完之后还会做几件事加载预训练权重如果 config 里指定了load_from或pretrained、把模型搬到 GPU、如果是分布式则用MMDistributedDataParallel包装。这里要注意load_from和resume_from的区别前者是加载预训练权重但重新开始训练epoch 从 0 算后者是恢复训练epoch 接着上次算。这两个概念混用是新手常犯的错误。4.2 数据集构建与 dataloader 的细节数据集部分由build_dataset(cfg.data.train)完成。MMDetection 的数据集构建有个特点它支持“数据集包装”的嵌套结构比如RepeatDataset、ClassBalancedDataset、ConcatDataset。这些包装器会改变数据集的长度和采样行为理解它们的嵌套顺序很重要。dataloader 的构建则涉及几个关键参数samples_per_gpu每张卡的 batch size、workers_per_gpu每张卡的数据加载进程数、persistent_workers等。workers_per_gpu的设置是个经验活设太小数据加载跟不上 GPU 计算设太大内存和 CPU 开销上去了。一般来说每个 GPU 配 2 到 4 个 worker 是常见起点具体要看你的数据增强复杂度和磁盘 IO 速度。注意samples_per_gpu是每张卡的 batch size不是全局 batch size。全局 batch size 等于samples_per_gpu × num_gpus。学习率的设置通常要跟着全局 batch size 走改了卡数不改学习率收敛行为会变。4.3 runner 的组装训练循环的真正驱动者这是train.py里最核心也最容易被略过的一段。在 MMEngine 体系下脚本会构建一个Runner对象把模型、数据集、优化器、学习率调度器、日志配置、checkpoint 配置全部塞进去然后调用runner.train()。Runner 内部封装了训练循环什么时候做前向、什么时候反向、什么时候更新学习率、什么时候验证、什么时候保存 checkpoint。config 里的train_cfg、val_cfg、test_cfg以及default_hooks都是在配置这个 runner。以 checkpoint 保存为例CheckpointHook控制保存频率default_hooks dict( checkpointdict( typeCheckpointHook, interval1, max_keep_ckpts3, save_bestcoco/bbox_mAP ) )interval1表示每个 epoch 存一次max_keep_ckpts3表示只保留最近 3 个save_best表示额外保存指标最好的那个。这些字段在旧版 2.x 里是写在checkpoint_config里的位置变了但语义基本一致。日志方面LoggerHook控制打印频率interval50表示每 50 个 iteration 打印一次。如果你觉得日志刷得太快或太慢改的就是这里而不是去改train.py本身。理解 runner 的意义在于大部分训练行为的定制应该通过配置 hook 来实现而不是改 train.py。train.py 负责组装hook 负责具体行为。这个分工想清楚了你就能判断一个问题该在哪里解决。5. 那些只有踩过才知道的坑与定制技巧5.1 恢复训练时学习率为什么“跳”了这是被问得最多的问题之一。你从第 10 个 epoch 恢复训练发现学习率不是接着第 10 个 epoch 的值而是从头开始或者跳到了别的地方。原因通常有两个一是--resume没有正确加载优化器状态二是学习率调度器的配置和恢复逻辑不匹配。正确的恢复训练需要同时恢复模型权重、优化器状态、学习率调度器状态、以及当前的 epoch 和 iteration。MMEngine 的 resume 机制会把这些都存进 checkpoint恢复时一起读回来。如果你用的是自定义的学习率策略要确保它实现了state_dict和load_state_dict否则恢复时状态丢失学习率就会从头算。另一个相关坑warmup 在恢复时会被重新触发。如果你的配置里有 warmup恢复训练时要注意 warmup 的 iteration 计数是不是被重置了。这个在跨版本时行为有差异建议恢复后先观察几十个 iteration 的日志确认学习率曲线正常。5.2 自定义 hook 的插入位置想加一个自定义行为比如每个 epoch 结束后把某些中间特征可视化出来最规范的做法是写一个自定义 hook然后注册到 runner 里。hook 的执行时机由priority和注册的 stage 决定比如before_train_epoch、after_train_iter。写 hook 的时候有个原则不要在 hook 里做阻塞时间过长的操作因为它会卡住整个训练循环。如果确实要做重活考虑异步或者放到单独的进程里。另外hook 里访问 runner 的状态要小心比如runner.iter、runner.epoch这些值在分布式下每个进程都有一份但只有主进程该做写操作。5.3 从 2.x 迁移到 3.x 时 train.py 的变化如果你手上有旧项目迁移时train.py层面主要注意这几点2.x 写法3.x 写法cfg.optimizer_config合并进optim_wrappercheckpoint_configdefault_hooks.checkpointlog_configdefault_hooks.loggerlr_configparam_schedulerrunner dict(typeEpochBasedRunner)train_cfg dict(typeEpochBasedTrainLoop)结构变了但概念是一一对应的。迁移时不要试图逐行翻译而是理解每个配置块在管什么然后用新体系的写法重新表达。这样迁移出来的配置更干净也更容易维护。5.4 一个实用技巧把 train.py 当模板复制出来改最后分享一个我常用的做法。当你需要做比较深的定制时不要直接改框架自带的tools/train.py而是把它复制到你的项目目录下改成一个自己的入口脚本。这样做的好处是框架升级时不会覆盖你的改动而且你的定制逻辑集中在一个文件里别人接手时一眼能看懂。复制出来之后你可以放心地加日志、加断点、加自定义的构建逻辑。等定制稳定了再考虑把通用部分抽成 hook 或工具函数。这个“先复制再抽象”的路径比一上来就追求优雅架构要务实得多。我在实际项目里就是这么干的一开始只是想在训练开始时打印一下数据集的类别分布后来慢慢加上了自定义的模型初始化、特殊的权重加载逻辑、以及训练中途的指标快照。如果一开始就去改框架源码这些改动早就随着版本升级丢光了。
RELATED

相关推荐

Unity射击小游戏压缩包:零基础跑通与修改指南

Unity射击小游戏压缩包:零基础跑通与修改指南

简介:这是一份基于Unity3D制作的初级射击游戏完整项目,面向刚接触Unity引擎或希望了解小型游戏开发流程的读者。压缩包共含28个文件,包括可直接运行的exe客户端、dll运行库、config配置文件、assets资源文件与场景数据等,整体体积…

📅 2026/10/17 15:19:56
鸿蒙Toast自定义圆角与样式实现指南

鸿蒙Toast自定义圆角与样式实现指南

很多人在群里问我:鸿蒙里那套默认的Toast样式到底怎么改?为什么设置了圆角不生效?为什么明明自定义了背景色,弹出的还是系统那个灰扑扑的小黑条? 这期咱们就来解决这个典型问题。标题是《鸿蒙常见问题分析三十九&…

📅 2026/10/17 15:19:56
8个AI写作软件实测推荐:自考毕业论文写作全流程指南

8个AI写作软件实测推荐:自考毕业论文写作全流程指南

先跟大家说个结论:用 AI 写毕业论文,不是让你复制粘贴,而是帮你把 40 分的框架搭到 85 分。我见过太多自考朋友一上来就卡在“第二章 理论基础”整整一星期,也见过有人用工具半天列完提纲第二天就开始写正文,差距就在工…

📅 2026/10/17 15:19:56
MORE NEWS

更多资讯

📰

生产级Shell脚本一键部署YUM源实战指南

1. 项目概述:为什么一个yum源部署脚本值得专门写一篇长文? “Shell脚本一键部署yum源”——这八个字背后,藏着运维工程师、系统管理员、DevOps新手甚至高校Linux课程实验者每天都在面对的真实痛点。不是“能不能配”,而是“要不要…

📰

AI降重工具怎么用才不翻车?从查重原理到实操避坑指南

又到了一年里最折磨人的时候。不少人找我聊天,第一句就是“重复率太高了怎么办”,紧跟着一句“听说用AI工具能降重,到底靠不靠谱”。这里我直接给结论:靠谱,但用法不对照样翻车。我自己帮周围朋友改论文、改报告、改投…

📰

i2 Analyst‘s Notebook 8 系统学习指南:从知识地图到实战演练

简介:这份资源是 i2 Analysts Notebook 8 的中文培训教材,面向从事犯罪侦查、金融调查、反欺诈分析等工作的初、中级分析人员,帮助其掌握这款由 IBM 开发的专业数据可视化与关联分析工具。压缩包内仅含 1 个 doc 文档,体积约 25.9…

📰

Kafka消费者读取数据全解析:从核心机制到实战排坑

从事Kafka消费端开发这几年,我最大的感受是:生产端写起来是真的简单,send一把梭,但消费端才是考验功力的时候。偏移量提交、消费组协调、分区再平衡、消息幂等,每一个点都可能让你在凌晨三点对着日志发呆。这篇文章不聊…

📰

BERT微调实战全解:参数设置、数据预处理与踩坑排查

做NLP的人,八成绕不过BERT微调这道坎。我最早接触它的时候也是从教程里照搬了一套流程,数据集处理好、模型加载、跑几个epoch,看起来好像没什么问题。可等真正用到自己的任务上,问题就全冒出来了:loss降了但指标纹丝不…

📰

Impala工程设计本质:低延迟交互式分析的物理约束与取舍

1. 这不是普通读书笔记:Impala论文背后的真实工程逻辑“impala paper笔记”这五个字,最近在数据平台、大数据架构和实时分析相关的技术圈里频繁出现。但如果你真去翻那篇2012年发表在VLDB上的《Impala: A Modern Analytic Database for Hadoop》&#xf…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬