深度学习篇---MNIST手写数字分类中的CBAM模块
CBAM模块在MNIST手写数字分类中的应用:
下面我将以 MNIST 手写数据集分类为例,详细介绍在 PyTorch 中手工设计 CBAM 模块的流程,并提供高度集成可复用的代码。
CBAM 模块设计流程
- 设计通道注意力模块 (Channel Attention)
- 设计空间注意力模块 (Spatial Attention)
- 组合两个模块形成完整 CBAM
- 构建包含 CBAM 的分类网络
- 训练和测试网络
代码实现与详细介绍
基于PyTorch的CBAM模块实现与MNIST分类
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
# 1. 通道注意力模块
class ChannelAttention(nn.Module):
def __init__(self, in_channels, reduction_ratio=4):
super(ChannelAttention, self).__init__()
# 缩减通道数,减少计算量
self.avg_pool = nn.AdaptiveAvgPool2d(1) # 全局平均池化
self.max_pool = nn.AdaptiveMaxPool2d(1) # 全局最大池化
# 瓶颈结构:先降维再升维
self.fc = nn.Sequential(
nn.Linear(in_channels, in_channels // reduction_ratio, bias=False),
nn.ReLU(inplace=True),
nn.Linear(in_channels // reduction_ratio, in_channels, bias=False)
)
self.sigmoid = nn.Sigmoid() # 将注意力权重限制在0-1之间
def forward(self, x):
# x的形状: [batch_size, channels, height, width]
b, c, _, _ = x.size()
# 平均池化路径
avg_out = self.avg_pool(x).view(b, c) # [b, c, 1, 1] -> [b, c]
avg_out = self.fc(avg_out).view(b, c, 1, 1) # [b, c] -> [b, c, 1, 1]
# 最大池化路径
max_out = self.max_pool(x).view(b, c) # [b, c, 1, 1] -> [b, c]
max_out = self.fc(max_out).view(b, c, 1, 1) # [b, c] -> [b, c, 1, 1]
# 两个路径的结果相加,经过sigmoid得到通道注意力权重
out = avg_out + max_out
attention_weights = self.sigmoid(out)
# 将注意力权重与输入特征相乘
return x * attention_weights
# 2. 空间注意力模块
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=3):
super(SpatialAttention, self).__init__()
# 确保卷积后特征图尺寸不变
self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size//2, bias=False)
self.sigmoid = nn.Sigmoid() # 将注意力权重限制在0-1之间
def forward(self, x):
# x的形状: [batch_size, channels, height, width]
# 在通道维度上取平均和最大值
avg_out = torch.mean(x, dim=1, keepdim=True) # [b, 1, h, w]
max_out, _ = torch.max(x, dim=1, keepdim=True) # [b, 1, h, w]
# 拼接两个结果
x_cat = torch.cat([avg_out, max_out], dim=1) # [b, 2, h, w]
# 卷积得到空间注意力权重
attention_weights = self.conv(x_cat) # [b, 1, h, w]
attention_weights = self.sigmoid(attention_weights)
# 将注意力权重与输入特征相乘
return x * attention_weights
# 3. CBAM模块:通道注意力 + 空间注意力
class CBAM(nn.Module):
def __init__(self, in_channels, reduction_ratio=4, kernel_size=3):
super(CBAM, self).__init__()
self.channel_attention = ChannelAttention(in_channels, reduction_ratio)
self.spatial_attention = SpatialAttention(kernel_size)
def forward(self, x):
x = self.channel_attention(x) # 先应用通道注意力
x = self.spatial_attention(x) # 再应用空间注意力
return x
# 4. 带有CBAM模块的MNIST分类网络
class MNISTNetWithCBAM(nn.Module):
def __init__(self):
super(MNISTNetWithCBAM, self).__init__()
# 第一个卷积块 + CBAM
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.cbam1 = CBAM(32) # 对32个通道应用CBAM
# 第二个卷积块 + CBAM
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.cbam2 = CBAM(64) # 对64个通道应用CBAM
# 池化层
self.pool = nn.MaxPool2d(2, 2)
# 全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
# Dropout防止过拟合
self.dropout = nn.Dropout(0.5)
def forward(self, x):
# 第一个卷积块: 卷积 -> 批归一化 -> ReLU -> CBAM -> 池化
x = self.pool(F.relu(self.cbam1(self.bn1(self.conv1(x)))))
# 第二个卷积块: 卷积 -> 批归一化 -> ReLU -> CBAM -> 池化
x = self.pool(F.relu(self.cbam2(self.bn2(self.conv2(x)))))
# 展平特征图
x = x.view(-1, 64 * 7 * 7)
# 全连接层
x = F.relu(self.fc1(x))
x = self.dropout(x) # 应用dropout
x = self.fc2(x)
return x
# 5. 数据加载与预处理
def load_data(batch_size=64):
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # MNIST数据集的均值和标准差
])
# 加载MNIST数据集
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
# 创建数据加载器
train_loader = DataLoader(
train_dataset, batch_size=batch_size, shuffle=True, num_workers=2
)
test_loader = DataLoader(
test_dataset, batch_size=batch_size, shuffle=False, num_workers=2
)
return train_loader, test_loader
# 6. 训练函数
def train(model, train_loader, criterion, optimizer, device, epoch):
model.train() # 设置为训练模式
running_loss = 0.0
correct = 0
total = 0
for batch_idx, (inputs, targets) in enumerate(train_loader):
inputs, targets = inputs.to(device), targets.to(device)
# 清零梯度
optimizer.zero_grad()
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播和优化
loss.backward()
optimizer.step()
# 统计训练信息
running_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
# 打印训练进度
if batch_idx % 100 == 99:
print(f'[{epoch + 1}, {batch_idx + 1}] loss: {running_loss / 100:.3f} '
f'accuracy: {100. * correct / total:.2f}%')
running_loss = 0.0
return 100. * correct / total
# 7. 测试函数
def test(model, test_loader, criterion, device):
model.eval() # 设置为评估模式
test_loss = 0
correct = 0
total = 0
# 不计算梯度,节省内存
with torch.no_grad():
for inputs, targets in test_loader:
inputs, targets = inputs.to(device), targets.to(device)
outputs = model(inputs)
loss = criterion(outputs, targets)
test_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
test_loss /= len(test_loader)
accuracy = 100. * correct / total
print(f'Test set: Average loss: {test_loss:.4f}, '
f'Accuracy: {correct}/{total} ({accuracy:.2f}%)\n')
return accuracy
# 8. 主函数
def main():
# 设置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Using device: {device}')
# 加载数据
train_loader, test_loader = load_data(batch_size=64)
# 初始化模型、损失函数和优化器
model = MNISTNetWithCBAM().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
epochs = 10
train_accuracies = []
test_accuracies = []
print("开始训练带有CBAM模块的模型...")
for epoch in range(epochs):
train_acc = train(model, train_loader, criterion, optimizer, device, epoch)
test_acc = test(model, test_loader, criterion, device)
train_accuracies.append(train_acc)
test_accuracies.append(test_acc)
# 绘制训练曲线
plt.plot(range(1, epochs+1), train_accuracies, label='Train Accuracy')
plt.plot(range(1, epochs+1), test_accuracies, label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('MNIST Classification with CBAM')
plt.legend()
plt.savefig('cbam_mnist_accuracy.png')
plt.show()
# 保存模型
torch.save(model.state_dict(), 'mnist_cam_model.pth')
print("模型已保存为 'mnist_cam_model.pth'")
if __name__ == '__main__':
main()
代码详细解析
1. 通道注意力模块 (ChannelAttention)
- 功能:判断哪些特征通道更重要,给它们更高的权重
- 实现步骤:
- 使用全局平均池化和全局最大池化压缩空间信息
- 通过瓶颈结构 (先降维再升维) 学习通道权重
- 使用 Sigmoid 将权重限制在 0-1 之间
- 将学习到的权重与输入特征相乘,增强重要通道
2. 空间注意力模块 (SpatialAttention)
- 功能:判断图像中哪些位置更重要,给它们更高的权重
- 实现步骤:
- 在通道维度上取平均值和最大值,压缩通道信息
- 拼接两个结果,通过卷积学习空间权重
- 使用 Sigmoid 将权重限制在 0-1 之间
- 将学习到的权重与输入特征相乘,增强重要位置
3. CBAM 模块
- 简单地将通道注意力和空间注意力串联起来
- 先应用通道注意力,再应用空间注意力
4. 分类网络 (MNISTNetWithCBAM)
- 为 MNIST 手写数字分类设计的网络
- 在两个卷积块后都加入了 CBAM 模块
- 使用批归一化和 Dropout 提高模型泛化能力
5. 训练与测试
- 数据加载:使用 PyTorch 内置的 MNIST 数据集
- 训练函数:实现模型训练过程,计算损失和准确率
- 测试函数:评估模型在测试集上的表现
- 主函数:整合所有组件,执行完整的训练流程
如何复用 CBAM 模块
要在其他网络中使用 CBAM 模块非常简单,只需:
- 导入 CBAM 类
- 在网络的卷积层后添加 CBAM 模块,如:
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
self.cbam = CBAM(out_channels) # 使用与卷积输出相同的通道数
- 在 forward 方法中应用:
x = self.conv(x)
x = self.cbam(x) # 应用CBAM注意力机制
这个实现保持了代码的简洁性和可复用性,同时在 MNIST 数据集上能够取得很好的分类效果,通常可以达到 99% 以上的准确率。
CBAM模块的应用场景:
CBAM 模块作为一种轻量级、通用的注意力插件,核心价值是 “增强关键特征、抑制冗余信息”,这种能力不局限于图像分类(如 MNIST 任务),而是可迁移到几乎所有计算机视觉任务中。只要任务依赖 “卷积神经网络提取视觉特征”,CBAM 都能通过 “聚焦有用信息” 提升模型性能,且无需大幅修改原有网络结构。
以下是 CBAM 模块的典型应用场景,结合任务特点、应用逻辑和实际效果展开说明,让你清楚 “为什么能用”“怎么用”:
一、图像分类(通用场景,不止 MNIST)
MNIST 是 “单目标、低分辨率、简单背景” 的分类任务,而 CBAM 在复杂场景的分类任务中优势更明显 —— 比如自然图像分类、细分类(区分相似物体)、小样本分类等。
应用逻辑:
复杂分类任务中,图像往往包含 “目标主体 + 复杂背景”(如 “在草地里的猫”“货架上的饮料”),普通卷积网络容易被背景干扰(如把草地纹理误判为猫的毛发)。CBAM 通过 “通道注意力筛选关键特征(如猫的轮廓、毛发通道)+ 空间注意力聚焦目标位置(如猫的身体区域)”,帮助网络忽略背景噪声。
典型任务与效果:
- 自然图像分类(如 ImageNet 数据集):将 CBAM 插入 ResNet、MobileNet 等网络的卷积块后,能在参数增加 < 1% 的前提下,将分类准确率提升 1-3 个百分点(论文原实验:ResNet50+CBAM 在 ImageNet 上 top-1 准确率从 75.3% 提升到 77.2%)。
- 细分类任务(如区分 “不同品种的狗”“不同型号的汽车”):这类任务中目标差异小(如金毛和拉布拉多的毛色、耳形差异),CBAM 能增强 “差异特征通道”(如耳形边缘通道),提升区分度。
- 小样本分类(如仅用 10 张样本训练一个类别):小样本数据下模型容易过拟合,CBAM 通过聚焦核心特征(而非噪声),减少过拟合风险,提升泛化能力。
二、目标检测(定位 + 分类的复合任务)
目标检测的核心是 “找到图像中的目标(定位)并判断类别(分类)”,难点在于 “多目标重叠”“小目标难识别”“背景复杂”——CBAM 能针对性解决这些问题。
应用逻辑:
- 对 “特征图全局” 用 CBAM:增强整个特征图中的 “目标特征”(如人、车的特征),抑制背景(如马路、树木),让后续检测头(如 YOLO 的检测层、Faster R-CNN 的 RPN 层)更容易捕捉目标。
- 对 “候选区域(RoI)” 用 CBAM:在 Faster R-CNN 等需要生成候选区域的算法中,对每个 RoI 的特征图单独应用 CBAM,聚焦 RoI 内的目标细节(如小目标的轮廓),避免候选区域内的背景干扰。
典型任务与效果:
- 通用目标检测(如 COCO 数据集):在 YOLOv3、Faster R-CNN 中插入 CBAM,小目标检测准确率可提升 5%-10%(例如 COCO 数据集中 “小于 32x32 像素” 的小目标,AP 值从 20% 左右提升到 25% 以上)。
- 特定场景检测(如自动驾驶中的 “行人 / 车辆检测”、工业质检中的 “零件缺陷检测”):
- 自动驾驶:CBAM 能增强 “雨天 / 夜晚” 场景下的目标特征(如雨天中行人的轮廓、夜晚车灯的特征),减少恶劣环境对检测的影响;
- 工业质检:CBAM 能聚焦 “零件的缺陷区域”(如裂缝、划痕),忽略零件表面的正常纹理,提升缺陷检出率。
三、图像分割(像素级分类任务)
图像分割需要 “给每个像素分配类别”(如语义分割:区分 “人、车、道路”;实例分割:区分 “不同的人”“不同的车”),难点在于 “类别边界模糊”“同类目标内部纹理不一致”。
应用逻辑:
分割任务的特征图需要 “像素级的精准注意力”——CBAM 的空间注意力能对每个像素打分,通道注意力能筛选 “类别专属特征”(如 “道路” 的特征通道、“行人” 的特征通道),两者结合让分割边界更清晰、同类像素分类更准确。
典型任务与效果:
- 语义分割(如 Cityscapes 数据集):在 U-Net、DeepLabv3 等分割网络的 “编码器 - 解码器连接层” 插入 CBAM,能增强 “跨层特征融合” 时的目标特征,分割边界的 IoU(交并比)可提升 3%-5%(例如 Cityscapes 中的 “道路” 类别,IoU 从 85% 提升到 88%)。
- 实例分割(如 COCO 实例分割任务):在 Mask R-CNN 的 “掩码分支” 中应用 CBAM,对每个实例的掩码特征图单独增强,能减少实例间的特征干扰(如两个重叠行人的掩码混淆),实例分割的 AP 值提升 2%-4%。
- 医学图像分割(如 CT/MRI 图像中的 “肿瘤分割”“器官分割”):医学图像中目标(如肿瘤)往往与周围组织(如正常器官)灰度接近,CBAM 能增强 “肿瘤的边缘 / 密度特征通道”,让分割结果更精准,辅助医生诊断。
四、图像修复与超分辨率(图像生成类任务)
这类任务需要 “基于已有信息生成缺失 / 高清图像”(如修复老照片的划痕、将低分辨率图放大为高清图),难点在于 “生成内容与原图语义一致”“细节真实不模糊”。
应用逻辑:
- 图像修复:CBAM 能聚焦 “缺失区域周围的有效特征”(如老照片划痕周围的纹理、颜色),让修复的缺失区域更贴合原图语义(避免修复出与周围无关的内容)。
- 超分辨率:CBAM 能增强低分辨率图像中的 “细节特征通道”(如文字的边缘、人脸的五官纹理),让超分辨率后的图像细节更清晰(避免放大后出现 “模糊马赛克”)。
典型任务与效果:
- 图像修复(如 Places2 数据集的 “任意区域修复”):在 Global&Local 等修复网络中插入 CBAM,修复区域的 “感知质量评分(PSNR)” 可提升 1-2 dB(数值越高,修复效果越接近原图)。
- 单图像超分辨率(如 DIV2K 数据集):在 ESRGAN、RCAN 等超分网络中应用 CBAM,超分后图像的 “结构相似性(SSIM)” 提升 0.02-0.05(数值越接近 1,结构越真实),尤其在文字、纹理等细节处效果明显。
五、人脸相关任务(高关注度、高细节需求任务)
人脸任务(如人脸识别、人脸关键点检测、人脸属性编辑)对 “细节特征” 要求极高(如人脸识别依赖 “眼角、鼻梁的细微特征”,关键点检测依赖 “眉毛、嘴角的位置”),CBAM 能精准聚焦这些关键细节。
应用逻辑:
- 人脸识别:在 ResNet-18/50 等人脸识别网络的 “特征提取层” 插入 CBAM,增强 “人脸的判别性特征”(如虹膜纹理、面部痣的位置),减少 “光照、姿态变化” 对识别的影响。
- 人脸关键点检测:在 MTCNN、HRNet 等关键点网络中,对 “人脸特征图” 应用 CBAM,聚焦 “关键点周围的局部特征”(如眼角周围的纹理),提升关键点定位的精度。
- 人脸属性编辑:在 StyleGAN 等生成网络中,对 “人脸属性特征通道”(如 “眼镜”“微笑” 的特征通道)应用 CBAM,让属性编辑更精准(如只添加眼镜,不改变人脸其他特征)。
典型效果:
- 人脸识别:在 LFW 数据集上,ResNet50+CBAM 的识别准确率从 99.63% 提升到 99.75%,尤其在 “姿态偏转 30 度以上” 的难样本中,准确率提升更明显(从 95% 提升到 98%)。
- 人脸关键点检测:在 300W 数据集上,关键点定位的平均误差从 3.5 像素降低到 2.8 像素,对 “遮挡场景”(如戴口罩)的关键点检测误差降低 15%。
六、视频相关任务(时序 + 空间特征结合任务)
视频任务(如视频分类、视频目标检测、动作识别)需要 “结合空间特征(单帧图像)和时序特征(多帧间的运动)”,CBAM 可与 “时序注意力” 结合,提升时空特征的有效性。
应用逻辑:
- 视频分类 / 动作识别:在 I3D、R (2+1) D 等视频网络中,先对 “单帧图像的空间特征” 应用 CBAM(增强单帧中的动作特征,如 “挥手” 的手臂特征),再结合时序注意力(增强多帧间的运动特征),让模型更准确识别动作。
- 视频目标检测:在 FGFA、Tubelet 等视频检测网络中,对 “多帧融合的特征图” 应用 CBAM,聚焦 “目标的运动轨迹区域”(如奔跑的人),避免帧间背景变化对检测的干扰。
典型效果:
- 动作识别:在 UCF101 数据集上,I3D+CBAM 的动作识别准确率从 94.2% 提升到 95.8%,对 “快速动作”(如打篮球、跳舞)的识别准确率提升更显著。
总结:CBAM 的应用核心规律
从上述任务可以看出,CBAM 的应用不局限于特定场景,只要满足以下两个条件,就能发挥作用:
- 任务依赖卷积神经网络提取视觉特征(几乎所有现代计算机视觉任务都满足);
- 任务存在 “有用特征与冗余信息混杂” 的问题(如复杂背景、小目标、细节需求高的场景)。
而且 CBAM 的 “轻量级” 特性(参数增加 < 1%,计算量增加 < 2%),让它即使在移动端、嵌入式设备(如手机、工业相机)的实时任务中也能使用,这也是它比其他复杂注意力模块(如自注意力)更通用的重要原因。
更多推荐



所有评论(0)