1. 项目背景与整体思路
先说结论:Ubuntu 16.04装mmdetection,最大的坑不是mmdetection本身,而是系统自带的Python版本和CUDA环境。很多人在这一步卡住,不是代码问题,是环境问题。
mmdetection是OpenMMLab系列里做目标检测的核心框架,基于PyTorch实现,支持Faster R-CNN、Mask R-CNN、RetinaNet、Cascade R-CNN等一系列主流检测模型,统一了数据加载、模型搭建、训练评估的流程。简单说:别人写好的检测模型代码,你拿来改改配置文件就能训练自己的数据集,不用从零手写检测头、锚框生成、NMS这些繁琐的部分。
这篇内容面向的是:有一块NVIDIA显卡、想在Linux环境里跑目标检测训练,但对conda虚拟环境和CUDA版本匹配不太熟悉的同学。我用的系统是Ubuntu 16.04,显卡是常用消费级型号(如GTX 1080/2080/3090都试过),下面讲的都是这一路实测下来的结果。
整体流程分四大块:第一步准备Python虚拟环境和CUDA,第二步安装mmdetection本体;第三步准备自己的数据集并写配置文件;第四步启动训练并看懂日志。第四步里我会重点讲训练过程怎么把控,因为很多人拿到框架就急着跑,跑起来之后看不懂loss曲线和输出日志,出了问题也不知道是数据问题还是模型问题。
我当时选择mmdetection而不是自己搭Faster R-CNN,原因很简单:训练一个检测模型涉及的东西太多了,数据增强策略、学习率调整、锚框匹配逻辑、NMS参数,这些都有成熟的默认配置,自己实现既容易出错又浪费时间。框架的默认参数基本都是从论文复现实验里调出来的,直接拿来用可以少走很多弯路。
而且mmdetection的模块化设计做得好,骨干网络、颈部、检测头都可以单独替换。比如我今天想用ResNet50做主干,明天想换成ResNeXt,只需要改配置里的两三行,不需要动训练代码。这对算法实验来说太关键了,能让你把时间花在模型设计上,而不是花在工程实现上。
2. 基础环境准备:Python、CUDA与PyTorch版本匹配
2.1 虚拟环境是必须的,别偷懒
Ubuntu 16.04系统自带的Python版本是3.5,这个版本直接抛弃Class,Proceed的一切,PyTorch新版本根本不支持。而且系统层面的Python环境是系统工具(比如apt)依赖的,你直接在系统Python里pip install一堆包,轻则依赖冲突,重则系统工具挂掉。
强烈建议用Anaconda来管理环境。Anaconda的conda create可以创建独立的Python解释器和独立的包环境,各环境之间互不干扰。安装Anaconda本身没风险,下载安装包后直接跑安装脚本,安装路径建议放家目录,比如/home/你的用户名/anaconda3,这样不需要sudo权限。
wget https://repo.anaconda.com/archive/Anaconda3-2020.02-Linux-x86_64.sh bash Anaconda3-2020.02-Linux-x86_64.sh安装完成后,source ~/.bashrc激活conda命令。然后创建mmdetection专用的虚拟环境:
conda create -n mmdet python=3.7 -y conda activate mmdet为什么选Python 3.7?因为mmdetection的早期版本(1.x)官方测试过的版本就是3.6/3.7,PyTorch 1.5以下的版本对Python 3.8支持也不好。选3.7可以同时兼容后续需要用到的大多数PyTorch版本,不挑版本。
如果你现在用的是mmdetection新版本(3.x),就需要Python 3.8以上甚至3.10,这个后面再说,本文主要按2.x版本讲解,兼容性和可复现性都更稳定。
2.2 CUDA和PyTorch版本匹配
mmdetection是跑在PyTorch上的,PyTorch又是通过CUDA调用显卡算力的,所以版本匹配就是一条链:显卡驱动版本 → CUDA版本 → PyTorch版本 → mmdetection版本。
确定你现在显卡驱动支持的最高CUDA版本,用nvidia-smi命令查看右上角的CUDA Version,这是驱动支持的上限,不是你已经装好的版本。比如显示CUDA Version: 11.4,说明你的驱动最高支持CUDA 11.4及以下的所有版本,那装CUDA 11.3甚至10.2都没问题。
判断好驱动上限后,再选PyTorch版本。mmdetection 2.x时代,我用得比较多的是PyTorch 1.5到1.10这个区间,CUDA对应10.2或11.3都行。不建议用最新版(当时的最新版),因为新版本对torchvision的接口要求也变了,容易触发兼容性问题。
安装PyTorch用conda装比pip省心得多,conda会自动帮你匹配好CUDA相关的依赖库:
conda install pytorch==1.8.0 torchvision==0.9.0 cudatoolkit=11.1 -c pytorch -c conda-forge注意这里装的是一种“CUDA运行时环境”(cudatoolkit),它不是独立的CUDA Toolkit安装,而是PyTorch运行所依赖的CUDA库。只要你显卡驱动版本够新,程序就能正常运行,不需要额外去NVIDIA官网装完整的CUDA Toolkit。
装完之后验证一下CUDA和PyTorch是否配合正常:
python -c "import torch; print(torch.__version__); print(torch.cuda.is_available()); print(torch.zeros(1).cuda())"输出里如果最后一行是tensor([0.], device=‘cuda:0’),就说明GPU通道没问题。如果cuda.is_available()返回False,多半是PyTorch的CUDA编译版本和驱动版本不匹配,需要换一个cudatoolkit版本重新安装。
装完CUDA生态里另一个重要组件是mmcv,它是OpenMMLab系列框架共用的一套基础库,提供编译好的CUDA算子,包括NMS、ROIAlign、Deformable Convolution这些。没有mmcv,mmdetection检测模型根本跑不起来。
2.3 mmcv安装:最容易踩坑的一步
mmcv有两种安装方式:mmcv(全功能版)和mmcv-lite(纯Python版)。检测模型训练必须要用全功能版,因为NMS、ROIAlign都是CUDA算子,纯Python实现太慢而且没有对应接口。
装mmcv最稳妥的方式是用官方预编译好的wheel包,注意版本号和PyTorch以及CUDA的对应关系。我用的命令是这样:
pip install mmcv-full==1.4.0 -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.8.0/index.html这里cu111对应CUDA 11.1,torch1.8.0对应你刚装的PyTorch版本,两者必须和实际环境一致,否则装上后要么import报错,要么运行时报CUDA错误。
如果你不想去找这个链接,也可以直接从源码编译:
git clone https://github.com/open-mmlab/mmcv.git cd mmcv MMCV_WITH_OPS=1 pip install -e .源码编译更耗时(大概10到20分钟,视CPU性能而定),但能保证算子和你本机的CUDA完全匹配。我建议:能用预编译包就用预编译包,省时省力;源码编译作为一种备用方案。
3. mmdetection安装与依赖检查全流程
3.1 安装mmdetection本体
首先从GitHub拉源码:
git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection这里有个细节:clone下来默认是master分支,可能是更新版本,不一定能和你装的mmcv版本兼容。保险做法是切换到稳定版tag,我用的是2.18.1版本,可以在release页面找到历史版本列表:
git checkout v2.18.1切完分支后安装依赖:
pip install -r requirements/build.txt pip install -e .pip install -e .是开发者模式安装,它不光安装包本体,还会把当前目录作为包源码路径,这样你修改mmdetection里任何代码都是立即生效的,不用重新安装。做深度学习实验基本都推荐这种模式,方便调试框架代码。
安装完建议执行一次python -c "import mmdet; print(mmdet.__version__)"验证导入是否正常。如果输出版本号说明安装成功了。
3.2 版本兼容性自查清单
Ubuntu 16.04 Python 3.7 PyTorch 1.8.0 CUDA 11.1 mmcv-full 1.4.0 PyTorch 1.8.0与mmcv-full 1.4.0配对 mmdetection 2.18.1这套组合在我实测中跑Faster R-CNN、Mask R-CNN、RetinaNet都很稳定,没有什么兼容性报错。你要是想用其他版本组合,记一个核心原则:mmcv、PyTorch、CUDA三者必须严格对应,mmdetection版本与mmcv主版本必须匹配(mmdet 2.x配mmcv 1.x,mmdet 3.x配mmcv 2.x),不匹配直接gem所有操作,不要试图用其他方式绕过。
另外提醒一句:Ubuntu 16.04的GCC版本是5.4,如果编译过程中报错,通常和编译标准太新有关。我之前在源码编译mmcv时遇到过xxx未声明这类错误,解决方法是升级GCC:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install gcc-7 g++-7 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 70但这个只在源码编译时才需要,用预编译wheel包不受GCC版本影响。所以如果你的系统比较老,我又要重复那句:优先用预编译包。
3.3 用官方Demo验证环境是否可用
环境装完别急着训练自己的数据,先用官方自带demo跑通一遍推理流程,确认整条链路没问题。
下载官方预训练权重:
wget https://download.openmmlab.com/mmdetection/v2.0/faster_rcnn/faster_rcnn_r50_fpn_1x_coco/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth然后用demo脚本跑一张测试图:
python demo/image_demo.py demo/demo.jpg configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth如果终端打印出了检测结果并弹出图像窗口,说明环境OK。这里dialog或xmapping菜单可能弹不出来,因为运行在远程服务器上没有图形界面,这时候加一句:
export QT_QPA_PLATFORM=offscreen或者直接用--out参数保存结果图片路径,不弹窗:
python demo/image_demo.py demo/demo.jpg ... --out result.jpg第一次跑demo,你会发现加载模型花了十几秒,然后推理很快,这是正常的。模型权重文件比较大,加载时需要反序列化到内存,之后GPU推理只是前向传播,速度很快。
4. 准备自己的数据集:从标注到COCO格式转换
4.1 标注工具与格式选择
mmdetection支持的数据格式最常见的有两种:COCO格式和VOC格式。如果从零开始做项目,我建议直接用COCO格式,因为mmdetection对COCO的支持最完善,mAP评估、实例分割标注等都用原生COCO API,省去很多二次开发的麻烦。
标注工具我推荐Labelme,它标注出来的是一堆JSON文件,和COCO的JSON格式很接近,但需要写脚本转换,而且单个图片一个JSON文件的组织方式和COCO的全量JSON不同。另一个选择是LabelImg,用起来也和Labelme类似,需要转换。
其实还有一个更直接的方案:安装一个支持COCO格式标注的工具,比如CVAT或者X-AnyLabeling这类开源工具,标注完直接导出COCO格式。不过说实话,工具之间的转换脚本网上很多,自己写也不复杂。核心是把标注数据整理成COCO JSON,包含五个关键字段:images、annotations、categories、licenses、info。
images字段记录每张图片的id、文件名、宽高;annotations字段记录每个目标的类别id、bbox坐标(x, y, w, h格式)、面积、掩码等;categories字段记录类别序号和类别名。我自己写了个转换脚本,从Labelme的JSON里读出多边形坐标,然后用polygon转mask、mask转bbox的方式生成COCO格式。
4.2 COCO数据集目录结构
mmdetection官方代码里,数据加载是从一个指定的数据根目录读的。以COCO数据集的目录结构为模板:
data/ └── coco/ ├── annotations/ │ ├── instances_train2017.json │ └── instances_val2017.json ├── train2017/ │ ├── 000000000001.jpg │ └── ... └── val2017/ ├── 000000000001.jpg └── ...除非你另外写自定义的dataset类,否则最好保持这个结构,因为mmdetection的CocoDataset类里默认就是去找annotations这个文件夹下的instances_train2017.json和instances_val2017.json。
如果你的数据集不是2017年份命名的,需要改配置文件里的data_root、ann_file、img_prefix这几个参数。比如我的数据集叫custom_dataset,就改成:
data_root = 'data/custom/' train_ann_file = 'custom_annotations/train.json' train_img_prefix = 'custom_annotations/train_images/'4.3 图片和标注检查
数据是训练模型的根基,这块我再多说两句。我第一次训练完模型,发现模型什么也检测不出来,检查了一圈才发现标注里有一张图片的bbox坐标越界了——x坐标比图片宽度还大。这种脏数据在训练过程中不会直接报错,但会给模型引入错误信号。
清洗数据的建议:
- 写个脚本遍历所有图片,检查文件能否用PIL打开、图片尺寸是否异常(宽度或高度小于100像素的图容易导致增强后失真)。
- 检查所有bbox是否在[0, width]、[0, height]范围内,超界的要么裁剪要么删除。
- 统计各类别的目标数量,如果某个类别只有几十个样本而其他类别有几千个,训练出来的模型对这个类别的召回率会惨不忍睹。这种情况需要补数据,或者在配置里调整采样策略。
检查bbox的代码其实很简洁,用pycocotools就能读取并过滤:
from pycocotools.coco import COCO coco = COCO('instances_train2017.json') img_ids = coco.getImgIds() for img_id in img_ids: ann_ids = coco.getAnnIds(imgIds=img_id) anns = coco.loadAnns(ann_ids) img_info = coco.loadImgs(img_id)[0] w, h = img_info['width'], img_info['height'] for ann in anns: x, y, bw, bh = ann['bbox'] if x < 0 or y < 0 or x + bw > w or y + bh > h: print('Bad bbox:', img_info['file_name'], ann['id'])跑一遍这个脚本,把输出的坏标注清除掉再训练,能省掉后面排查问题的很多麻烦。
5. 配置文件详解:关键参数调整逻辑
5.1 配置文件的结构
mmdetection用Python文件作为配置(2.x版本),不像很多框架用yaml或json。一个完整的训练配置由以下几部分组成:模型设置(model)、数据集设置(data)、训练策略(optimizer、lr_config)、运行时设置(total_epochs、work_dir等)。
以Faster R-CNN的配置文件为例,核心部分大概长这样:
model = dict( type='FasterRCNN', backbone=dict( type='ResNet', depth=50, num_stages=4, out_indices=(0, 1, 2, 3), frozen_stages=1, norm_cfg=dict(type='BN', requires_grad=True), norm_eval=True, style='pytorch'), neck=dict( type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=5), rpn_head=dict( type='RPNHead', in_channels=256, feat_channels=256, anchor_generator=dict( type='AnchorGenerator', scales=[8], ratios=[0.5, 1.0, 2.0], strides=[4, 8, 16, 32, 64]), bbox_coder=dict( type='DeltaXYWHBBoxCoder', target_means=[.0, .0, .0, .0], target_stds=[1.0, 1.0, 1.0, 1.0]), loss_cls=dict( type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), loss_bbox=dict(type='L1Loss', loss_weight=1.0)), roi_head=dict(...) )第一次看到这种嵌套dict结构可能觉得复杂,但实际上你不需要全部理解,重点是知道每个模块都在哪改。90%的场合你只需要改这几处:
- model.roi_head.bbox_head.num_classes:改成自己数据集的类别数。
- model.train_cfg、model.test_cfg:训练和推理阶段的参数,比如NMS的iou_threshold。
- data.train、data.val、data.test:数据集路径和类别。
- optimizer、lr_config:学习率和优化器参数。
- total_epochs:训练轮数。
5.2 类别数设置
最容易错的地方是类别数。COCO默认是80类,加上背景一共81类。如果你有自己的数据集,比如只有人、车、自行车3类目标,那num_classes就设成3。千万别在这个基础上加1,很多新手在这里出错,因为有些框架的类别数本来就包含背景,但mmdetection的在算loss时已经处理了背景类别,你只需要填实际类别数。
改配置的时候同时还要改data里的数据集类别元信息:
data = dict( train=dict( type='CocoDataset', ann_file='data/custom/annotations/train.json', img_prefix='data/custom/train_images/', classes=('person', 'car', 'bicycle'), pipeline=[...]), val=dict(...), test=dict(...))5.3 学习率、batch size与epochs的配合
之前用默认参数训练自定义小数据集,模型效果很差。分析后发现:小数据集(几百张图)用默认的1x schedule(12 epoch)太大,模型在后期已经过拟合。小数据集更适合用小学习率和早期停止。
这里给一个经验值参考:
- 1张GPU、2张GPU、batch_size为2时,初始学习率建议0.0025到0.005。
- batch_size翻倍,学习率也翻倍,这是线性缩放规则。
- 小数据集(少于1000张图)建议先设4000到8000次迭代为总迭代数,而不是用epoch来算。
- 如果你的显存不大,batch_size只能设1或2,学习率就要相应调低,我实测0.001左右开始比较稳。
总迭代数和epoch的换算:总迭代数 = epoch数 × 每epoch迭代次数 = epoch数 × 训练图片数 / batch_size。假设你有800张图,batch_size=2,想要4000次迭代,epoch数 = 4000 / (800 / 2) = 10个epoch。
学习率调整策略方面,mmdetection支持step、cosine、poly等。小数据集我用step衰减效果就很好:前70%的迭代用初始学习率,30%时降到0.1倍,也就是用lr_config里的step参数控制:
optimizer = dict(type='SGD', lr=0.0025, momentum=0.9, weight_decay=0.0001) lr_config = dict( policy='step', step=[2800, 3600], # 在总迭代4000下的衰减节点 warmup='linear', warmup_iters=200, warmup_ratio=0.001)warmup的设置很重要。训练开始时模型权重是随机初始化的(或者预训练权重),梯度的方差很大,如果直接用大学习率,很容易在刚开始的几百步就把权重震飞,导致loss变成nan。加一个warmup阶段,用很小的学习率先跑200步,让模型进入一个相对平缓的loss区域,然后再逐步增大到目标学习率,这一步能避免很多“训练一开始就loss爆炸”的问题。
5.4 预训练权重与finetune策略
训练自己的数据集时,强烈建议加载在COCO上预训练过的backbone权重,哪怕你的数据和COCO差异很大,底层特征(边缘、纹理、颜色)仍然是通用的。mmdetection的配置里的load_from参数就是干这个的:
load_from = 'checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth'还有另一个参数resume_from,它和load_from不同。load_from是加载预训练权重然后从头开始训练;resume_from是加载之前训练到一半的模型,完全恢复当时的epoch、optimizer状态继续训练。如果你训练中断了想继续,用resume_from,不是load_from。
还有一个细节:如果只训练RPN而冻结backbone,配置里的frozen_stages参数控制backbone的前几层是否冻结。默认是1,也就是第一个stage不参与梯度更新。微调时如果想加快训练速度,可以设frozen_stages=2或更高。但注意冻结的层太多会影响模型对新数据集的适应能力,这个要根据数据量和数据分布来权衡。我的建议:数据量小于1万张时保留默认(frozen_stages=1),不要冻结更多层,确保backbone能充分适应你的数据分布。
6. 开始训练:日志解读与显存管理
6.1 训练命令与多GPU支持
环境验证完、配置改好了,就可以启动训练了。单GPU训练命令很简单:
python tools/train.py configs/my_configs/faster_rcnn_r50_fpn_custom.py如果是多卡机器:
bash tools/dist_train.sh configs/my_configs/faster_rcnn_r50_fpn_custom.py 4最后一个参数4表示用4张GPU。多卡训练要注意batch_size的设置,因为总batch_size = 每张卡的batch_size × 卡数。比如4卡时每卡2张图,总batch_size=8,那学习率对应应该设为单卡2张图时的4倍左右(线性缩放)。
训练启动后终端会刷出日志,格式大概是:
2019/08/01 12:00:01 - mmdet - INFO - Epoch [1][100/800] lr: 0.002500, eta: 4:00:00, time: 0.45, data_time: 0.12, memory: 3250, loss_rpn_cls: 0.5610, loss_rpn_bbox: 0.3221, loss_cls: 0.8912, loss_bbox: 0.4211, loss: 2.1954, grad_norm: 0.8910注意看几个关键量:
- loss_rpn_cls:RPN前背景分类损失,初期应在0.5到1.0之间。
- loss_cls:最终检测头的分类损失,初期较高,随训练下降。
- loss_bbox:边界框回归损失。
- grad_norm:梯度范数,如果突然变成几百几千甚至nan,说明梯度爆炸了。
- memory:显卡显存占用,单位MB。
- eta:预计剩余时间,可以判断训练进度。
6.2 loss为nan的排查思路
loss出现nan,这是训练检测模型最常见的问题,我列一下排查顺序:
第一步,检查输入数据里有没有异常图片。比如全黑或全白的图片,或者标注为空的图片。解决办法:训练脚本里加一个数据过滤逻辑,或者清洗数据集时把这些样本排除。
第二步,检查学习率是否过大。把lr_config里的warmup_ratio从0.001改成0.0001,warmup_iters从200提到500,通常都能缓解。
第三步,检查loss_cls和loss_bbox是否有inf或nan。如果只有loss_bbox是nan,大概率是目标框回归的目标值有异常,比如标注里出现了width或height为0的bbox。
第四步,检查混合精度训练配置。mmdetection 2.x里可以用fp16训练,但fp16在数据和梯度幅度差异大时容易溢出。如果开了fp16导致nan,改成fp32训练看看是否恢复正常。我的建议是:在没有彻底跑通流程之前,先不开fp16,用fp32把流程跑通再说。
经验:在我训练多个数据集的过程中,排第一的nan来源是数据问题,第二是学习率问题,第三才是框架Bug。所以遇到nan先查数据,这是最快的路径。
6.3 显存不够怎么办
显存溢出(CUDA out of memory)也是高频问题。常规解决方法:
调小batch_size,每卡从2变成1,或从1变成梯度累积。mmdetection支持设置
optimizer_config=dict(type='GradAccumulateOptimizerHook', cumulative_iters=2)来实现梯度累积,相当于用时间换显存。这个功能在mmdetection 2.x中已经内置,等于你batch_size=1但累积4个step再更新一次梯度,效果接近batch_size=4。降低输入图片的尺寸。配置里的img_scale参数控制模型输入分辨率,比如(1333, 800)改成(1000, 600),显存占用能降很多,但mAP也会有一定程度的下降,小目标检测效果尤其明显。这个要权衡。
换轻量backbone。比如把ResNet50换成ResNet18甚至MobileNetV2,模型层的参数少了,显存占用会显著降低,适合做快速验证。
测试时关掉梯度计算,训练时释放不需要的缓存:
torch.cuda.empty_cache()这类旁门左道意义不大,真要省显存还是从模型和输入尺寸下手。
6.4 训练日志的持久化与可视化
建议在启动命令时重定向日志文件,方便训练完复盘:
python tools/train.py ... 2>&1 | tee train_$(date +%Y%m%d_%H%M%S).log训练过程中每隔一段时间看一次loss变化趋势,不要等训练完再去看。标准节奏是:loss在前几百步快速下降,之后缓慢下降,最后在某个值附近小幅度震荡,说明训练基本收敛。如果loss震荡幅度过大,考虑降低学习率。
mmdetection还集成了TensorBoard可视化,启动时加一个--work-dir参数并开启--validate就能边训练边在验证集上评估mAP。训练中mAP曲线和loss曲线一起看,能更早发现过拟合:loss还在降但mAP不升反降,基本就是过拟合了,可以提前停止训练或者加大数据增强。
7. 常见问题与排查技巧实录
7.1 运行时报ImportError
ImportError: libcudart.so.10.2: cannot open shared object file,这类报错说明运行时的CUDA库找不到。排查思路:检查LD_LIBRARY_PATH里有没有包含cuda的lib目录:
echo $LD_LIBRARY_PATH ls /usr/local/cuda/lib64/如果没有,在~/.bashrc里加一行:
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH还有就是conda虚拟环境下PyTorch装的cudatoolkit,路径会指向conda环境内的lib目录,这种通常不会报错。如果报错是libcublas相关的,检查cudatoolkit版本是否匹配。
7.2 编译安装时的contact类型错误
源码编译时如果遇到‘xxx’ was not declared in this scope,基本都是因为系统GCC版本太新或太旧,C++标准不满足。Ubuntu 16.04默认GCC 5.4,对C++14的部分特性支持不好,而新的CUDA Toolkit需要C++17支持。用前面说的update-alternatives切到GCC 7能解决大部分问题。
另外一个常见问题是fatal error: Python.h: No such file or directory,这是因为系统缺少Python开发头文件。conda环境里一般自带,如果是系统Python,需要:
sudo apt-get install python3-dev如果你用conda环境还报这个错,看看conda环境里的include路径是否正确:
conda install python=3.7-dev但通常conda的python包已经带了头文件,不会出现这个情况。出现的话检查一下conda环境是否装的是真正的anaconda python,而不是系统python软链。
7.3 训练开始就报CUDA error
训练启动不到几分钟就崩掉,报错是CUDA error: device-side assert triggered。这个错误很坑,它不会告诉你具体是哪一行代码触发的,需要加环境变量才能看到详细堆栈:
export CUDA_LAUNCH_BLOCKING=1设了这个环境变量后,CUDA错误会精确到具体的kernel调用行,然后你就知道是哪个张量操作出问题了。通常这类错误和类别数不对有关——模型输出的类别数比GT标注的类别ID小,索引越界。比如你的模型num_classes设了3,但GT里有个标注的category_id是4,导致在计算loss时索引越界。设置CUDA_LAUNCH_BLOCKING后排查就非常快。
解决方式是:检查数据标注中的category_id是否从0开始连续编号,COCO格式要求类别id必须从1开始且连续,mmdetection内部会做减1操作映射到数组索引。如果你的数据集category_id有跳号,比如0、2、5,需要在转换脚本里重映射成0、1、2。
7.4 训练很慢、GPU利用率低
如果发现训练速度远不如预期,查看nvidia-smi里GPU-Util那一列。如果利用率低于50%,说明GPU在等待数据,瓶颈在数据加载。
数据加载慢的原因和解决办法:
num_workers设置太小。mmdetection配置里data.train.samples_per_gpu和workers_per_gpu是一对参数,workers_per_gpu默认2,显存充足时可以调到4或8。
图片解码慢,尤其是高清大图。在数据pipeline里把decode的backend从cv2改成turbojpeg或者pillow,有些场景下能提升不少。
磁盘IO瓶颈。如果数据集放在机械硬盘上,读取速度会比SSD慢好几倍。把数据放到SSD或内存盘上,训练速度能立竿见影地提升。
数据增强太复杂。mmdetection支持各种在线增强如随机裁剪、马赛克、MixUp等,增强越强计算越慢。在验证模型正确性时建议关掉多余增强,只保留Resize和Normalize,先把流程跑通。
7.5 模型不收敛的排查顺序
训练跑完了,loss也降了,但mAP就是低甚至接近0。这是一个让人很崩溃的场景。我的排查顺序:
一看数据:类别数量是否均衡、标注是否正确、图片和标注是否对齐(有没有标注错位的情况)。最简单的验证方法:用mmdetection自带的工具把GT画出来,保存成图片肉眼检查一遍:
python tools/misc/browse_dataset.py configs/my_configs/faster_rcnn_r50_fpn_custom.py --show运行这个命令会逐张显示图片和GT框,你翻一翻就能发现数据有没有问题。
二看配置:类别数是否正确、anchor大小和图片尺寸是否匹配。如果你的目标都是小物体,而锚框scale默认是[8],可能就根本覆盖不到。目标都是大物体时默认配置也不太合适。锚框参数要根据物体的实际尺寸来设定,判断方法:统计你数据集中所有GT框的宽高分布,看中位数和范围都落在什么区间。如果标注的目标都很小(比如几十个像素),考虑减小anchor的strides或增加小的anchor scale。
三看训练策略:学习率是否合适、epochs是否足够。小数据集上如果学习率太大,loss曲线会像锯齿一样震荡,模型无法稳定收敛。此时把学习率砍半再试,或者换cosine学习率策略。
四看预训练:有没有加载预训练权重。随机初始化的backbone在小数据集上极其容易不收敛,因为CNN的前几层要学会最基本的边缘检测就需要大量数据,而目标检测的数据量往往不足以支持从头训练。如果没加载预训练权重,赶紧加上load_from再跑。
8. 训练完成后的模型使用:测试、评估与推理部署
8.1 测试模型精度
训练完的模型权重保存在work_dir下的latest.pth里,同时保存的还有训练日志和评估结果。想看模型在验证集上的精度:
python tools/test.py configs/my_configs/faster_rcnn_r50_fpn_custom.py work_dirs/faster_rcnn_r50_fpn_custom/latest.pth --eval bbox输出的结果会包括各类别的AP(平均精度)和整体的mAP。需要注意:mAP是一个加权或平均的指标,如果某个类别的AP特别低,不要只看mAP,要单独看每个类别的AP值才能定位问题。bbox_mAP_50、bbox_mAP_75这些指标分别对应不同IoU阈值下的精度,通常看mAP_50为主,它更接近实际场景中你肉眼判断“检测准不准”的感觉。
8.2 单张图片推理
推理单张图片用demo脚本:
python demo/image_demo.py test.jpg configs/my_configs/faster_rcnn_r50_fpn_custom.py work_dirs/.../latest.pth --out result.jpg在输出图里可以看到检测框、类别和置信度分数。置信度分数太低(比如0.1),说明模型对目标的存在与否没有把握,需要继续训练或者检查数据。置信度分数高但检错了类别,往往是类别间特征相似度过高,建议增加区分度数据。
8.3 模型导出与部署思路
训练完的模型如果要做实际部署,比如用OpenCV或者TensorRT跑推理,一般需要导出成更轻量的格式。PyTorch模型加载慢、依赖重,不适合直接部署到生产环境。
mmdetection支持导出为ONNX格式:
python tools/deployment/pytorch2onnx.py configs/.../latest.pth --output-file model.onnx --input-img test.jpgONNX可以转换到TensorRT或者ONNX Runtime。TensorRT在推理环节有比较大的速度优化,特别适合需要实时性的场景。这个属于部署层面的内容,这里先提一下思路,等你有需要时再深入。现阶段重点是把训练流程跑通、把模型训出来,部署是后续优化的事情,不是当前的重点。
9. 最后分享一些实操习惯
从环境搭建到模型训练,整个流程走下来,总结几个我每次开新项目都会做的习惯性动作。
第一个习惯:每次改动配置后,先跑一个极短的训练(比如把总迭代设成50步),确认训练能启动、loss能正常回传、显卡确实在计算,然后再改成正式参数。这个叫冒烟测试,能帮你快速过滤掉90%的配置错误,而不是等正式训练跑了一两个小时才发现有问题。
第二个习惯:训练开始后不要立刻离开,先在日志输出里等前100步输出,确认loss在正常掉。如果前100步loss就在上升或者震荡得厉害,立即Ctrl+C停掉,检查数据和学习率设置,及时止损。
第三个习惯:所有环境版本号记录到一个requirements.txt或环境说明文档里。后面需要复现实验、换机器、或者回滚版本时,一份准确的版本记录可以帮你快速重建环境,省去一遍遍试版本的时间。
第四个习惯:定期用nvidia-smi看显存和温度,如果显存占用异常偏高(比预期高几GB),多半是代码里有张量没有释放或者数据加载异常,需要检查。
这些习惯让我在后续多个数据集、多个模型的训练过程中少踩了很多坑。环境搭建是门槛,训练调试是真正的技术活,把这些实操经验积累下来,你对目标检测工程化的理解也会越来越深。