DAY 50 预训练模型+CBAM模块
知识点回顾:
- resnet结构解析
- CBAM放置位置的思考
- 针对预训练模型的训练策略
- 差异化学习率
- 三阶段微调
ps:今日的代码训练时长较长,3080ti大概需要40min的训练时长
作业:
- 好好理解下resnet18的模型结构
- 尝试对vgg16+cbam进行微调策略
ResNet18 模型结构理解ResNet18 模型结构理解
ResNet(残差网络)通过引入残差块解决了深层网络的梯度消失问题,使网络可以训练更深。ResNet18 包含 18 层(1 个卷积层 + 17 个卷积层或全连接层),具体结构如下:
输入层:接收 224×224×3 的图像
初始卷积层:7×7 卷积,64 通道,步长 2,随后 BatchNorm 和 ReLU
4 个残差模块组:每个模块组包含多个残差块(BasicBlock)
残差块结构为:3×3 卷积 → BatchNorm → ReLU → 3×3 卷积 → BatchNorm → 残差连接 → ReLU
全局平均池化:将特征图压缩为 1×1 特征向量全连接层:输出分类结果
VGG16+CBAM 微调策略
CBAM(卷积注意力模块)可以增强特征表达能力,将其应用于 VGG16 时,可以放置在每个卷积组之后。以下是针对 VGG16+CBAM 的三阶段微调策略:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import models
from torch.optim.lr_scheduler import StepLR
# 定义 CBAM 模块
class ChannelAttention(nn.Module):
def __init__(self, in_channels, reduction_ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc = nn.Sequential(
nn.Conv2d(in_channels, in_channels // reduction_ratio, 1, bias=False),
nn.ReLU(),
nn.Conv2d(in_channels // reduction_ratio, in_channels, 1, bias=False)
)
def forward(self, x):
avg_out = self.fc(self.avg_pool(x))
max_out = self.fc(self.max_pool(x))
out = avg_out + max_out
return torch.sigmoid(out)
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
out = torch.cat([avg_out, max_out], dim=1)
out = self.conv(out)
return torch.sigmoid(out)
class CBAM(nn.Module):
def __init__(self, in_channels, reduction_ratio=16, kernel_size=7):
super(CBAM, self).__init__()
self.channel_att = ChannelAttention(in_channels, reduction_ratio)
self.spatial_att = SpatialAttention(kernel_size)
def forward(self, x):
x = x * self.channel_att(x)
x = x * self.spatial_att(x)
return x
# 修改 VGG16 模型,添加 CBAM
def vgg16_cbam(pretrained=True):
model = models.vgg16(pretrained=pretrained)
# 在每个 block 后添加 CBAM
features = list(model.features)
new_features = []
# VGG16 的 block 划分点
block_end_indices = [4, 9, 16, 23, 30]
current_block = 0
for i, layer in enumerate(features):
new_features.append(layer)
if i == block_end_indices[current_block] and current_block < len(block_end_indices) - 1:
# 除了最后一个 block,其他都添加 CBAM
in_channels = list(filter(lambda x: isinstance(x, nn.Conv2d), features[:i+1]))[-1].out_channels
new_features.append(CBAM(in_channels))
current_block += 1
model.features = nn.Sequential(*new_features)
return model
# 三阶段微调策略
def train_vgg16_cbam():
# 阶段 1: 冻结除最后几层外的所有参数
model = vgg16_cbam(pretrained=True)
# 冻结大部分参数
for param in list(model.parameters())[:-20]:
param.requires_grad = False
optimizer = optim.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001, momentum=0.9)
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
# 训练前 10 个 epoch
for epoch in range(10):
# 训练代码
scheduler.step()
# 阶段 2: 解冻更多层,使用差异化学习率
for param in list(model.parameters())[-40:]:
param.requires_grad = True
# 为不同层设置不同学习率
params = [
{'params': list(model.parameters())[:-40], 'lr': 1e-5},
{'params': list(model.parameters())[-40:-20], 'lr': 1e-4},
{'params': list(model.parameters())[-20:], 'lr': 1e-3}
]
optimizer = optim.SGD(params, momentum=0.9)
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
# 训练 10-20 个 epoch
for epoch in range(10, 20):
# 训练代码
scheduler.step()
# 阶段 3: 解冻所有层,使用更低学习率
for param in model.parameters():
param.requires_grad = True
optimizer = optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
# 训练 20-30 个 epoch
for epoch in range(20, 30):
# 训练代码
scheduler.step()
return model
策略解释
模型修改:在 VGG16 的每个卷积组后添加 CBAM 模块,增强特征表达能力。
三阶段微调:
阶段 1:冻结大部分预训练参数,仅训练最后几层和 CBAM 模块,防止灾难性遗忘。
阶段 2:解冻更多层,使用差异化学习率(底层小学习率,高层大学习率),微调中间层特征。
阶段 3:解冻所有层,使用低学习率全局微调,进一步优化模型。
学习率调整:使用 StepLR 调度器,每 5 个 epoch 降低学习率,有助于模型收敛。
@浙大疏锦行