Day 51:复习日

在 Day 44 到 Day 50 的学习中,我们掌握了预训练模型、TensorBoard 监控以及 CBAM 注意力机制。今天,我们将这些“零散”的知识点熔于一炉,对“狗品种识别”这一实战项目进行深度优化。

本文将从设计思路核心技术原理代码实现剖析进行详细讲解。


一、 优化思路:为什么这么改?

1. 从 ResNet 到 ResNet + CBAM

  • 痛点:原始的 ResNet50 虽然强大,但它在提取特征时是“平铺直叙”的——它对图片中的每一个像素点(空间)和每一个特征通道(语义)的关注度是相对均匀的。但在狗品种识别中,背景(如草地、沙发)往往是干扰项,我们需要模型更聚焦于狗的眼睛、耳朵、毛色纹理等关键部位。
  • 解法:引入 CBAM (Convolutional Block Attention Module)
    • Channel Attention (通道注意力):告诉模型“看什么”。比如在识别哈士奇时,模型应该更关注“黑白毛色”、“蓝色眼睛”这些特征通道,而抑制背景噪音通道。
    • Spatial Attention (空间注意力):告诉模型“看哪里”。直接在特征图上加权,让模型聚焦于狗的躯干部位,忽略周围的草地。

2. 从统一学习率到差异化学习率

  • 痛点:我们在做迁移学习。ResNet50 的主干部分(Backbone)已经在 ImageNet 的 1400 万张图片上训练得非常好了,是“资深专家”;而我们新加入的 CBAM 模块和全连接分类层(Head)是随机初始化的“实习生”。如果用同样大的步子(学习率)去训练,容易把“专家”带偏;如果用同样小的步子,"实习生"学得太慢。
  • 解法差异化学习率 (Differential Learning Rate)
    • 专家 (Backbone):用极小的学习率 (1e-4 或更低),以此微调,保持其强大的特征提取能力。
    • 实习生 (Head + CBAM):用较大的学习率 (1e-3),让他们快速收敛,跟上专家的节奏。

二、 代码剖析

1. CBAM 模块实现

CBAM 由两个子模块串联而成:通道注意力 -> 空间注意力。

class CBAM(nn.Module):
    def __init__(self, in_channels, ratio=16, kernel_size=7):
        super().__init__()
        # 通道注意力:关注“是什么特征”
        self.channel_attn = ChannelAttention(in_channels, ratio)
        # 空间注意力:关注“在哪个位置”
        self.spatial_attn = SpatialAttention(kernel_size)

    def forward(self, x):
        # 串联结构:先通道,后空间
        x = self.channel_attn(x)
        x = self.spatial_attn(x)
        return x
  • 细节:为什么是 x = self.channel_attn(x) 而不是 out = ...; return x + out
    • CBAM 是一种加权机制,它输出的是一个 0~1 之间的权重图(Mask),通过乘法直接作用于原始特征 x,从而起到增强或抑制的作用。这与 ResNet 的残差相加(Addition)不同。

2. ResNet50 集成 CBAM

这是本次优化的核心架构。我们没有破坏 ResNet 的内部结构(Block),而是在每个 Stage 的输出端“外挂”了一个 CBAM。

class ResNet50_CBAM(nn.Module):
    def __init__(self, num_classes=120, pretrained=True):
        super().__init__()
        # 1. 加载预训练的“专家”
        self.backbone = models.resnet50(pretrained=pretrained)
        
        # ResNet50 四个 Stage 的输出通道数
        layer_channels = [256, 512, 1024, 2048]
        
        # 2. 在每个 Stage 后定义 CBAM 模块
        self.cbam_layer1 = CBAM(in_channels=layer_channels[0])
        self.cbam_layer2 = CBAM(in_channels=layer_channels[1])
        # ... (layer3, layer4 同理)
        
        # 3. 重写分类头,适应 120 种狗的分类
        self.backbone.fc = nn.Linear(in_features=2048, out_features=num_classes)

    def forward(self, x):
        # 手动重写前向传播,插入 CBAM
        
        # Stem (输入层)
        x = self.backbone.conv1(x)
        x = self.backbone.bn1(x)
        x = self.backbone.relu(x)
        x = self.backbone.maxpool(x)
        
        # Layer 1
        x = self.backbone.layer1(x) # 原始 ResNet 提取特征
        x = self.cbam_layer1(x)     # CBAM 进行精炼和加权
        
        # Layer 2 ... (同理)
        
        # Head (分类)
        x = self.backbone.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.backbone.fc(x)
        return x

3. 差异化学习率配置

在 PyTorch 中,optimizer 可以接收一个参数列表,每个元素是一个字典,可以单独指定 lr

# 1. 筛选出 Backbone 的参数 ID
backbone_params = list(map(id, model.backbone.parameters()))

# 2. 筛选出新添加层(CBAM + FC)的参数
# 逻辑:只要参数 ID 不在 backbone_params 里,就是新参数
new_params = filter(lambda p: id(p) not in backbone_params, model.parameters())

# 3. 定义优化器
optimizer = optim.Adam([
    # 组1:专家层,学习率低 (1e-4)
    {'params': model.backbone.parameters(), 'lr': 1e-4},
    
    # 组2:新层,学习率高 (1e-3)
    {'params': new_params, 'lr': 1e-3}
])

三、 训练与监控

我们引入了 TensorBoard,这是工业界最常用的可视化工具。

from torch.utils.tensorboard import SummaryWriter

# 初始化记录器
writer = SummaryWriter('runs/dog_breed_resnet50_cbam')

# 在训练循环中记录
writer.add_scalar('Training Loss', loss.item(), global_step)
writer.add_scalar('Training Accuracy', epoch_acc, epoch)

这样,我们就不需要盯着控制台滚动的数字,而是可以打开浏览器,看到 Loss 曲线是否收敛、是否存在震荡,从而科学地调整超参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值