欢迎光临
我们一直在努力

PyTorch模型微调实战指南

微调 torchvision 模型完整指南

一、概述

本指南涵盖两种迁移学习场景:

  • 目标检测与实例分割:使用 Mask R-CNN 在自定义数据集(Penn-Fudan 行人数据集)上微调,同时预测边界框和分割掩码。

  • 图像分类:使用 ResNet、AlexNet、VGG、SqueezeNet、DenseNet、Inception v3 等模型在蜜蜂/蚂蚁二分类数据集上微调或特征提取。

  • 两种方法的共同步骤:

    • 加载预训练模型

    • 根据自定义任务的类别数修改最后一层(或必要层)

    • 选择更新全部参数(微调)或仅更新新层(特征提取)

    • 定义损失函数、优化器、数据加载器

    • 训练并评估


    二、目标检测与实例分割微调(Mask R-CNN)

    2.1 数据集准备

    官方源(推荐):Penn-Fudan Database for Pedestrian Detection and Segmentation。在页面中找到 "Download zipped file here" 的链接即可下载

    Penn-Fudan 数据集结构:

    PennFudanPed/
    PNGImages/ # 原始图像
    PedMasks/ # 分割掩码(不同实例用不同颜色编码)

    每张图像对应一个掩码图,掩码中像素值 0 表示背景,非零值表示不同实例。

    2.2 自定义数据集类 PennFudanDataset

    关键要求:继承 torch.utils.data.Dataset,实现 __len__ 和 __getitem__。 返回字典 target 必须包含:

    • boxes (FloatTensor[N,4]):[x0, y0, x1, y1] 格式

    • labels (Int64Tensor[N])

    • image_id (Int64Tensor[1])

    • area (Tensor[N])

    • iscrowd (UInt8Tensor[N])

    • masks (UInt8Tensor[N,H,W]) – 可选,但 Mask R-CNN 需要

    代码解释:

    class PennFudanDataset(object):
    def __getitem__(self, idx):
    # 加载图像和 mask
    img = Image.open(img_path).convert("RGB")
    mask = Image.open(mask_path)
    mask = np.array(mask)

    # 获取所有实例 ID(非零值)
    obj_ids = np.unique(mask)[1:]
    # 将 mask 拆分为多个二值 mask
    masks = mask == obj_ids[:, None, None]

    # 计算每个实例的边界框
    boxes = []
    for i in range(len(obj_ids)):
    pos = np.where(masks[i])
    xmin, xmax = np.min(pos[1]), np.max(pos[1])
    ymin, ymax = np.min(pos[0]), np.max(pos[0])
    boxes.append([xmin, ymin, xmax, ymax])

    # 转换为 tensor
    boxes = torch.as_tensor(boxes, dtype=torch.float32)
    labels = torch.ones((num_objs,), dtype=torch.int64) # 只有一类:人
    masks = torch.as_tensor(masks, dtype=torch.uint8)
    image_id = torch.tensor([idx])
    area = (boxes[:, 3] – boxes[:, 1]) * (boxes[:, 2] – boxes[:, 0])
    iscrowd = torch.zeros((num_objs,), dtype=torch.int64)

    target = {"boxes": boxes, "labels": labels, "masks": masks,
    "image_id": image_id, "area": area, "iscrowd": iscrowd}
    return img, target

    2.3 模型构建函数 get_model_instance_segmentation

    关键点:替换分类头和 mask 预测头,适配自定义类别数(此处为 2:背景 + 人)。

    def get_model_instance_segmentation(num_classes):
    # 加载 COCO 预训练的 Mask R-CNN
    model = torchvision.models.detection.maskrcnn_resnet50_fpn(pretrained=True)

    # 替换 box 分类器
    in_features = model.roi_heads.box_predictor.cls_score.in_features
    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)

    # 替换 mask 预测器
    in_features_mask = model.roi_heads.mask_predictor.conv5_mask.in_channels
    hidden_layer = 256
    model.roi_heads.mask_predictor = MaskRCNNPredictor(in_features_mask,
    hidden_layer,
    num_classes)
    return model

    2.4 训练流程

    辅助模块:需要从 torchvision/references/detection/ 复制 engine.py, utils.py, transforms.py。

    • engine.py: pytorch/vision/blob/main/references/detection/engine.py

    • utils.py: pytorch/vision/blob/main/references/detection/utils.py

    • transforms.py: pytorch/vision/blob/main/references/detection/transforms.py

    数据增强:

    def get_transform(train):
    transforms = [T.ToTensor()]
    if train:
    transforms.append(T.RandomHorizontalFlip(0.5))
    return T.Compose(transforms)

    主训练代码结构:

    def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    num_classes = 2

    # 创建数据集(训练集+验证集,按 170:50 划分)
    dataset = PennFudanDataset('PennFudanPed', get_transform(train=True))
    dataset_test = PennFudanDataset('PennFudanPed', get_transform(train=False))
    indices = torch.randperm(len(dataset)).tolist()
    dataset = Subset(dataset, indices[:-50])
    dataset_test = Subset(dataset_test, indices[-50:])

    # DataLoader 中需使用 utils.collate_fn
    data_loader = DataLoader(dataset, batch_size=2, shuffle=True,
    collate_fn=utils.collate_fn)

    model = get_model_instance_segmentation(num_classes).to(device)
    optimizer = torch.optim.SGD([p for p in model.parameters() if p.requires_grad],
    lr=0.005, momentum=0.9, weight_decay=0.0005)
    lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)

    for epoch in range(10):
    train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10)
    lr_scheduler.step()
    evaluate(model, data_loader_test, device=device)

    2.5 结果

    • 训练 1 个 epoch 后:bbox AP 60.6%,mask AP 70.4%

    • 训练 10 个 epoch 后:bbox AP 79.9%,mask AP 76.1%,模型能准确检测图像中的行人并给出分割掩码。


    三、分类模型微调(ResNet, AlexNet, VGG, SqueezeNet, DenseNet, Inception v3)

    3.1 两种策略

    策略更新参数适用场景
    微调 (Fine-tuning) 全部模型参数 新数据集较大或与预训练数据集差异明显
    特征提取 (Feature Extraction) 仅新初始化的最后一层 新数据集较小且与预训练数据集相似

    3.2 辅助函数

    设置参数冻结:

    def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
    for param in model.parameters():
    param.requires_grad = False

    训练与验证函数 train_model:

    • 支持 Inception v3 的双输出(主输出 + 辅助输出)损失计算

    • 每个 epoch 后计算验证准确率,保存最佳模型

    • 返回最佳模型和验证准确率历史

    3.3 各模型最后一层修改方法

    模型原最后一层修改方式
    ResNet model.fc model.fc = nn.Linear(in_features, num_classes)
    AlexNet / VGG model.classifier[6] model.classifier[6] = nn.Linear(4096, num_classes)
    SqueezeNet model.classifier[1] (Conv2d) model.classifier[1] = nn.Conv2d(512, num_classes, 1)
    DenseNet model.classifier model.classifier = nn.Linear(1024, num_classes)
    Inception v3 model.fc 和 model.AuxLogits.fc 两者均替换为 nn.Linear(2048/768, num_classes)

    统一初始化函数 initialize_model 根据 model_name 返回修改后的模型和输入尺寸(Inception v3 需 299×299,其余 224×224)。

    3.4 数据准备与优化器

    数据增强(以 ResNet 为例):

    from torchvision import transforms

    data_transforms = {
    'train': transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 补全参数
    ]),
    }

    优化器:只更新 requires_grad=True 的参数(特征提取时只有新层可训练)。

    params_to_update = model.parameters()
    if feature_extract:
    params_to_update = [p for p in model.parameters() if p.requires_grad]
    optimizer = optim.SGD(params_to_update, lr=0.001, momentum=0.9)

    3.5 训练与对比

    • 预训练模型微调(SqueezeNet, 15 epochs):验证准确率 92.8%

    • 从头训练:验证准确率仅 45.8%(几乎无效),充分说明迁移学习的巨大优势。

    可视化对比:绘制验证准确率随 epoch 变化曲线,预训练模型收敛快且精度高。


    四、关键注意事项与实操建议

  • 版本兼容性 教程基于 torchvision 0.3,但核心 API(如 maskrcnn_resnet50_fpn, FastRCNNPredictor 等)在后续版本中依然有效。建议使用 PyTorch 1.5+。

  • 数据格式

    • 目标检测:target 字典中的 boxes 必须是 [x0, y0, x1, y1] 格式,且归一化到图像尺寸内。

    • 分类:使用 datasets.ImageFolder 时,子文件夹名即为类别名。

  • 性能优化

    • 使用 collate_fn 处理可变大小的目标(检测任务)。

    • 训练时启用 num_workers 加速数据加载。

    • 若显存不足,减小 batch_size 或使用梯度累积。

  • 常见错误

    • 忘记将模型切换到 model.train() / model.eval() 模式。

    • Inception v3 训练时未正确处理辅助输出损失。

    • 特征提取时忘记冻结主干网络参数(应调用 set_parameter_requires_grad)。

  • 扩展到其他任务

    • 关键点检测:需在 target 中添加 keypoints 字段,形状 [N, K, 3]。

    • 多类别检测:修改 num_classes 并将 labels 设置为对应类别 ID。


  • 五、总结

    通过这两个完整教程,您应该掌握了:

    • 如何为自定义目标检测/分割数据集编写 Dataset 类,并利用预训练 Mask R-CNN 微调。

    • 如何对不同架构的分类模型进行迁移学习(微调或特征提取),包括修改最后一层、冻结参数、训练评估。

    • 迁移学习能够在小数据集上显著提升性能,避免从头训练的失败。

    所有代码均可直接运行(需下载对应数据集),是实践计算机视觉迁移学习的极佳起点。您可以根据自己的任务调整类别数、骨干网络和超参数。

    赞(0)
    未经允许不得转载:171主机测评 » PyTorch模型微调实战指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址