【深度学习|学习笔记】目标检测里 使用预训练模型的优劣 + 从零训练(train from scratch)(含可运行 PyTorch 代码片段与实战建议)
【深度学习|学习笔记】目标检测里 使用预训练模型的优劣 + 从零训练(train from scratch)(含可运行 PyTorch 代码片段与实战建议)
欢迎铁子们点赞、关注、收藏!
祝大家逢考必过!逢投必中!上岸上岸上岸!upupup
大多数高校硕博生毕业要求需要参加学术会议,发表EI或者SCI检索的学术论文会议论文。详细信息可扫描博文下方二维码 “
学术会议小灵通”或参考学术信息专栏:https://ais.cn/u/mmmiUz
前言
- 为什么/何时用预训练模型(优点与缺点详解);
- 什么时候/如何从零训练(风险、技巧与工程要点);
- 两段可运行 PyTorch 示例代码:A)基于 torchvision 的使用预训练权重微调(Faster R-CNN),B)从零开始训练同一架构(随机初始化 backbone),并给出训练调度与超参建议。
写得尽量实用——你可以把代码直接放到你的训练脚本里改造使用。
1)使用预训练模型的优点(为什么常用)
- 收敛更快:ImageNet 等大规模分类预训练提供了很好的低/中层特征(边缘、纹理、语义),检测头只需较少更新就能快速收敛。
- 数据效率高:在标注昂贵的检测任务上(尤其小/中数据集)能显著提升精度,减少过拟合风险。
- 稳定性增强:预训练参数作为良好初始点,训练过程更稳定、对学习率更不敏感。
- 工程便利:立即可用的基线,便于快速原型验证与调参。
- 适配下游任务:通过微调(只训练 head 或分阶段解冻)可以在不同检测场景快速迁移。
2)使用预训练模型的缺点与限制
- 域差异(domain gap):当目标域与预训练域(ImageNet、自然图像)差别很大(医学影像、卫星影像、红外、显微图像等),预训练特征可能并不理想,甚至成为限制因素。
- 潜在偏置/上游任务限制:预训练可能携带上游数据偏差(类别、风格),影响目标检测的特定细节。
- 限制架构选择:有些现代改进(特殊 normalization、不同通道数)与现成预训练权重不兼容,迫使你重新训练或复杂迁移。
- 可解释性 / 可控性:如果需要严格控制特征学习过程(例如特定表征需求),从零训练可能更可控。
结论/经验法则:
- 如果你的检测数据量有限或与自然图像相似 → 强烈建议使用预训练(ImageNet / COCO 等)。
- 如果你的数据与ImageNet差异极大、并且你有大量标注数据或可用大量无标签域数据做自监督/域自适应预训练 → 可以考虑从零训练或先做 domain-adaptive pretraining。
3)什么时候要考虑“从零训练”(Train from scratch)
合适情形包括:
- 大规模标注集(上十万图像),能支撑网络从零学习。
- 上游任务域差距大(例如红外、医学、卫星),ImageNet 特征不适配。
- 你想研究/改进骨干结构或归一化方式(例如想用不同通道数、不同 BN 策略或新型 layer),无法直接套用现成预训练权重。
- 做学术研究:证明某方法在无预训练条件下是否有效(公平比较)。
从零训练的主要挑战:收敛慢、需要更长训练时间、更严格的超参(LR)调节、更强正则与大量数据增强、可能需要更复杂 warmup/优化器。
4)从零训练的关键技巧(详尽清单)
- 更长训练时间:通常比微调多数倍的 epochs 或 steps(例如 3–10×)。
- 初始化策略:Backbone & RPN & Head 使用合适初始化(Kaiming/He、Xavier);对层归一化/权重初值敏感。
- 学习率策略:小的初始学习率 + warmup(线性 warmup 数千 steps),或使用 OneCycle。对大 batch 使用线性缩放规则再配合 warmup。
- 优化器:SGD + momentum(0.9)常被用于从零训练,或 AdamW(配合学习率 schedule)。
- 更强的数据增强:随机裁剪、多尺度训练(multi-scale)、颜色抖动、随机翻转、mixup/cutmix(当适用)。
- 正则与正交手段:weight decay、标签平滑(如果是分类 head)、dropout(按需)、更严格的 anchor/matching 设计。
- 归一化策略:若 batch size 很小,BatchNorm 不稳定 → 使用 GroupNorm/SyncBN/LayerNorm 或训练时增大 batch via gradient accumulation。
- 更多负样本策略:RPN/anchor 的正负样本比、IoU thresholds 的调整可以帮助稳定训练早期。
- 监控与早停:频繁保存 checkpoint、可视化 loss/cls/box loss、mAP,在早期检测问题(如预测异常)。
- 有条件地用自监督或 domain-adaptive pretraining:当有大量无标签数据时,先做自监督(比如 MoCo、SimCLR、MAE 等)再微调,往往比纯从零训练更稳定且效果好。
5)实用对比表(预训练 vs 从零训练)
| 维度 | 预训练微调 | 从零训练 |
|---|---|---|
| 收敛速度 | 快 | 慢(需要更多 steps) |
| 数据需求 | 低 / 中 | 高(大量标注) |
| 推荐场景 | 小/中样本,自然图像 | 大样本或域差距大 |
| 工程成本 | 低 | 高(计算、调参) |
| 性能上限 | 通常高于小样本从零 | 在大量数据 + 精细调参下可比肩或更好 |
6)示例代码(PyTorch + torchvision):A)微调(使用预训练 backbone);B)从零训练(随机初始化 backbone)
说明:代码使用 torchvision.models.detection.fasterrcnn_resnet50_fpn。
pretrained=True:使用在 COCO 上预训练的 weights(或 torchvision 早期是 ImageNet resume for backbone)。pretrained_backbone=False:从随机初始化 backbone(注意一些 torchvision 版本有pretrained_backbone参数;如果没有,需要手动替换或重新初始化)。
下面示例给出训练 loop 的核心(可扩展到 COCO/Dataset),并标注关键超参与注意点。
通用依赖与 Dataset 说明(先看整体)
# pip install torch torchvision
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision.models.detection import fasterrcnn_resnet50_fpn
from torchvision.transforms import functional as F
from torch.utils.data import DataLoader, Dataset
import torchvision.transforms as T
import random
import numpy as np
- 你需要准备一个符合 torchvision detection 接口的数据集(每个 item 返回 image (C,H,W) tensor 与 target dict:
target = {
"boxes": Tensor[N,4],
"labels": Tensor[N],
"image_id": Tensor[1],
"area": Tensor[N],
"iscrowd": Tensor[N] # optional
}
- 建议先用 COCO / VOC 或自定义 DataSet(示例省略数据加载实现细节)。
A)示例:微调(预训练)(推荐用于多数场景)
def get_model_pretrained(num_classes):
# num_classes includes background (so for COCO 91 or for custom tasks classes+1)
model = fasterrcnn_resnet50_fpn(pretrained=True) # backbone + heads pre-trained (torchvision)
# 替换 box predictor(分类 head)以适配新的 num_classes
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = \
torchvision.models.detection.faster_rcnn.FastRCNNPredictor(in_features, num_classes)
return model
# 超参(微调)
num_classes = 1 + 20 # example: 20 classes + background
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = get_model_pretrained(num_classes).to(device)
# 优化器:只训练可训练参数(如果你冻结了一些层)
params = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
# 学习率 schedule:例如 step lr 或 ReduceLROnPlateau
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=8, gamma=0.1)
# 训练 loop(简要)
num_epochs = 12
for epoch in range(num_epochs):
model.train()
for images, targets in dataloader_train:
images = list(img.to(device) for img in images)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
lr_scheduler.step()
# 验证 & 保存 checkpoint
微调提示:
- 可先冻结 backbone 的大部分层(
for name,param in model.backbone.body.named_parameters(): param.requires_grad=False),只训练 RPN + ROI heads +最后几个 backbone 层。 - head lr 可以稍大(0.005),解冻 backbone 后对 backbone 设置较小 lr(1e-4–5e-5)。
- 对小 batch(如单卡 batch 2)考虑用 GroupNorm 或 SyncBN(需要改模型并使用分布式训练)。
B)示例:从零训练(随机初始化 backbone)(更挑剔的配置)
import torchvision
def get_model_scratch(num_classes):
# 检查 torchvision 版本是否允许 pretrained_backbone flag
# 在某些版本:fasterrcnn_resnet50_fpn(pretrained=False, pretrained_backbone=False)
model = fasterrcnn_resnet50_fpn(pretrained=False, pretrained_backbone=False)
# 需要对 backbone/neck/heads 做合理初始化(torchvision 默认会初始化 head)
# 替换 box predictor:
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = torchvision.models.detection.faster_rcnn.FastRCNNPredictor(in_features, num_classes)
return model
model = get_model_scratch(num_classes).to(device)
# 对模型参数做合理初始化(示例性 He init for convs)
def init_weights_he(m):
if isinstance(m, torch.nn.Conv2d):
torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
torch.nn.init.constant_(m.bias, 0)
elif isinstance(m, torch.nn.Linear):
torch.nn.init.normal_(m.weight, 0, 0.01)
if m.bias is not None:
torch.nn.init.constant_(m.bias, 0)
model.apply(init_weights_he)
# 优化器:更保守 lr + warmup
# 建议:使用 SGD+momentum,初始 lr 较小,配合 warmup
params = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.SGD(params, lr=0.01, momentum=0.9, weight_decay=0.0005)
# 实现简单 linear warmup scheduler(示例)
def warmup_lr_scheduler(optimizer, warmup_iters, warmup_factor):
def f(x):
if x >= warmup_iters:
return 1
alpha = float(x) / float(warmup_iters)
return warmup_factor * (1 - alpha) + alpha
return torch.optim.lr_scheduler.LambdaLR(optimizer, f)
warmup_iters = 1000
warmup_factor = 0.1
warmup_scheduler = warmup_lr_scheduler(optimizer, warmup_iters, warmup_factor)
# 后续再使用 StepLR/Cosine decay 等
# 训练 loop(更长)
num_epochs = 24 # 比微调时可能要多几倍
global_step = 0
for epoch in range(num_epochs):
model.train()
for images, targets in dataloader_train:
images = list(img.to(device) for img in images)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
# warmup 步进(按 step)
if global_step < warmup_iters:
warmup_scheduler.step()
global_step += 1
# epoch-level scheduler step(如 StepLR)
# 验证 & checkpoint
从零训练特别注意:
- 训练更久:把 epoch 数设长并定期评估(mAP)。
- 数据增强:强烈建议使用多尺度训练(在训练中随机改变输入短边/长边)、随机裁剪、颜色扰动等。
- 归一化:如果 batch 很小,避免使用 BatchNorm,改用 GroupNorm(需要修改 backbone)或用 SyncBN(多卡训练)。
- RPN/anchor tuning:不同数据集物体尺度/纵横比不同,可能需要调整 anchor 尺寸与比例、NMS thresholds、positive IoU threshold 等。
- 监控多个 loss 项(RPN cls/box, ROI cls/box),尤其训练早期可能 RPN 很弱需要手动检查和调整。
7)目标检测从零训练与预训练比较的实战建议(最终总结)
- 优先策略:能用预训练就用——特别是数据少或资源有限。
- 若决定从零训练:
(1)准备好大量标注或先做自监督/域自适应预训练;
(2)使用合理初始化、warmup、SGD+momentum、小初始 lr、强 augment、更多训练 epoch;
(3)使用 GroupNorm 或 SyncBN 来应对小批量问题;
(4)调整 anchor、NMS、IoU thresholds 以匹配目标尺度分布;
(5)保存大量 checkpoint 并频繁评估(避免白白训练很久在错误设置上)。 - 混合策略:当域差距中等时,常见做法是先用 ImageNet/COCO 预训练的 backbone,再在目标域大量无标签数据上做自监督微调(MAE / MoCo / SimCLR),最后再做目标检测微调——通常优于直接从零训练。
1602

被折叠的 条评论
为什么被折叠?



