ResNet每一层后接入CBAM:遥感土地利用分类的注意力机制实践

发布时间:2026/9/14 14:22:10
ResNet每一层后接入CBAM:遥感土地利用分类的注意力机制实践 简介这份资源是一套面向遥感卫星土地利用场景的 ResNet 系列图像分类改进实战代码包适合有一定深度学习基础、希望将注意力机制融入主干网络做对比实验的研究者或开发者。工程完整覆盖 resnet18/34/50/101/152 五个系列支持迁移学习或仅训练分类层并内置 Adam 与 SGD 优化器、交叉熵损失、余弦退火学习率策略训练阶段同步输出 loss、准确率、混淆矩阵、召回率、精确率、F1 与特异度等指标及曲线图。改进亮点是在每个 layer 后加入 CBAM 模块且可灵活选择仅在某层后启用便于替换其他注意力模块做消融研究。资源共 2000 个文件以 1994 张 jpg 图像数据集为主另有 3 个 Python 脚本、1 个 readme、1 个 json 日志说明及 1 个 txt 标注/说明文件压缩包大小约 27.79MB。数据集包含 21 种土地目标类别按 readme 要求整理即可更换数据。目前已有 71 人学习适合用于分类任务迁移、注意力机制对比及遥感影像落地实践。1. 为什么遥感土地利用分类需要在每个 ResNet layer 后加 CBAM当你要把遥感卫星影像切成农田、建设用地、水体、林地和裸地第一版基线往往就是 ResNet。ResNet 的残差连接解决了深度网络退化ImageNet 预训练权重也足以应付很多自然图像任务但遥感图像不是普通照片。遥感土地利用图像分类里最典型的失败不是“认不出物体”而是区域边界模糊水体被阴影带偏道路和裸地混在一起建设用地与农田在纹理统计上高度重叠。CBAM 注意力机制通过通道权重和空间权重对特征图做显式重标定把它放到每个 ResNet layer 后相当于让网络在下采样后反复问一次“当前哪些通道重要、哪个位置值得看”。分类头最终拿到的特征会更贴近土地利用的决策边界。下面这条链路按“原理 → 代码 → 训练 → 验证”展开最后你能得到一份可以直接在 PyTorch 上跑的 ResNet50CBAM 实现。2. 动手前先读懂 CBAM通道权重和空间权重如何与 ResNet layer 产出对齐2.1 CBAM 注意力机制的构成通道注意力与空间注意力CBAM 的完整结构由两个串行子模块组成。通道注意力首先对输入特征图 F∈R^{C×H×W} 同时做全局平均池化和全局最大池化得到两个 C×1×1 的向量。平均池化保留整体响应水平最大池化保留最强激活这两个视角对遥感数据尤其重要某一类地物可能整体色调平均但会在某个波段出现尖峰响应。两个向量进入同一个共享 MLP先把维度压到 C/r再恢复到 C相加后过 sigmoid 得到通道权重输出为 C×1×1。通道注意力之后是空间注意力输入特征图经过通道维度上的平均和最大操作拼接成 2×H×W 的映射再用一个 7×7 卷积压缩到 1×H×Wsigmoid 后得到空间权重。两个子模块串行连接理由很直接通道加权改变了各位置上的特征幅度空间注意力再在这个更干净的特征上选择位置两个动作解耦后反向传播路径更稳定。通道注意力在 ResNet 中的参数体量主要由当前层输出通道数 C 决定。以 ResNet50 为例layer1 到 layer4 的输出通道数分别是 256、512、1024、2048。每个 CBAM 模块增加参数量大约是 2×(C×C/r×2)reduction 取 16 时layer4 后增加的参数量约 50 万四个模块合计约为原始 ResNet50 的 2% 到 3%可以接受。真正昂贵的不是参数量而是特征图分辨率。layer1 输出的是 56×56 的特征空间注意力在那里的乘法和卷积会带来额外算力但因为每一次操作都是逐元素乘法实际训练时间增加一般在 10% 到 20%。如果遥感影像分辨率很高可以先把 CBAM 的 kernel_size 从 7 降到 5减少空间卷积开销。2.2 ResNet 的 layer 边界如何定位四个 stage 与下采样PyTorch torchvision 里的 ResNet 对“layer”有明确命名。先实例化一个不加载权重的模型打印子模块名会得到import torchvision.models as models model models.resnet50(weightsNone) for name, module in model.named_children(): print(name)输出依次是 conv1、bn1、relu、maxpool、layer1、layer2、layer3、layer4、avgpool、fc。这里的 layer1 到 layer4 实际对应论文里的四个 stage每个 stage 内部由若干 BasicBlock 或 Bottleneck 组成。题目中说的“每个 layer 后”合理读法就是在 layer1、layer2、layer3、layer4 的输出之后分别接入 CBAM。如果你把粒度理解成每个 block 后也完全可行四个 stage 分别有 3、4、6、3 个瓶颈块那就要插入 16 个 CBAM参数量和训练时长都会明显上升。在做遥感土地利用图像分类时我的经验是先做 stage-level 的插入这已经能覆盖不同尺度特征。CBAM 放在 layer 之后意味着输入层已经从上一个下采样压缩过空间分辨率减半但通道数翻倍通道注意力的收益比放在 block 内部更明显。2.3 插入位置与计算代价的不同选择把 CBAM 放在哪里本质上是在权衡“注意力应该作用在多大尺度的特征上”。下面这张表概括了三种常见策略策略插入模块数作用特征图尺寸适合场景仅放在 layer4 后12048×7×7轻量快速验证长尾类别感受野过大每个 layer 后4256×56×56 到 2048×7×7本文采用的默认配置平衡精度与开销每个 Bottleneck 后16全部尺度高精度需求且算力充足适合细碎地块只放 layer4 后的缺点很典型此时特征图只有 7×7空间注意力几乎没有像素级定位能力只能提供全局位置提示。对于遥感影像里大量存在的狭长地块比如道路、河流7×7 的掩膜根本不足以表达线性结构。放在每个 layer 后能让 layer1 和 layer2 的高分辨率特征也参与空间注意力线性地物在 56×56 甚至 28×28 尺寸上仍保留较强的空间连续性。这也是遥感土地利用分类比一般 ImageNet 分类更需要中间层注意力的原因。3. 用 PyTorch 在 ResNet 的每个 layer 后接入 CBAM 模块代码与参数3.1 最小可复现的 CBAM 模块实现下面的代码是可直接使用的 CBAM 实现包含通道注意力和空间注意力两部分没有依赖额外库。注意通道注意力里用 1×1 卷积代替线性层这样既能处理任意输入尺寸也方便直接替换 ResNet 的中间层。import torch from torch import nn class ChannelAttention(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(channels // reduction, channels, 1, biasFalse), ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.mlp(self.avg_pool(x)) max_out self.mlp(self.max_pool(x)) weight self.sigmoid(avg_out max_out) return x * weight class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) concat torch.cat([avg_out, max_out], dim1) weight self.sigmoid(self.conv(concat)) return x * weight class CBAM(nn.Module): def __init__(self, channels, reduction16, kernel_size7): super().__init__() self.channel_attention ChannelAttention(channels, reduction) self.spatial_attention SpatialAttention(kernel_size) def forward(self, x): x self.channel_attention(x) x self.spatial_attention(x) return x参数说明channels必须等于上一层输出的通道数reduction16是压缩比例通道数越多压缩收益越大kernel_size控制空间注意力感受野7 是对自然图像常用的默认值遥感图像里如果要捕获河流或公路的细长结构7 仍然够用。AdaptiveMaxPool2d会让梯度流经最大值位置这与平均池化提供的梯度形成互补是 CBAM 在通道权重上优于 SE 模块的关键点。3.2 修改 ResNet 前向传播在 Layer1 到 Layer4 后插入调用我一般不对 torchvision 的源码做硬改而是用一个包装类重新组装 ResNet50。这样做的好处是预训练权重可以直接从backbone继承CBAM 模块作为新增参数独立存在训练时也能单独设置学习率。import torchvision.models as models class ResNet50CBAM(nn.Module): def __init__(self, num_classes6): super().__init__() backbone models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) self.conv1 backbone.conv1 self.bn1 backbone.bn1 self.relu backbone.relu self.maxpool backbone.maxpool self.layer1 backbone.layer1 self.cbam1 CBAM(256, reduction16) self.layer2 backbone.layer2 self.cbam2 CBAM(512, reduction16) self.layer3 backbone.layer3 self.cbam3 CBAM(1024, reduction16) self.layer4 backbone.layer4 self.cbam4 CBAM(2048, reduction16) self.avgpool nn.AdaptiveAvgPool2d((1, 1)) self.fc nn.Linear(2048, num_classes) def forward(self, x): x self.maxpool(self.relu(self.bn1(self.conv1(x)))) x self.cbam1(self.layer1(x)) x self.cbam2(self.layer2(x)) x self.cbam3(self.layer3(x)) x self.cbam4(self.layer4(x)) x self.avgpool(x) x torch.flatten(x, 1) return self.fc(x)前向逻辑里的关键点是顺序先跑完一个 stage再进入 CBAM。self.layer1(x)的输出会交给cbam1此时特征仍是高分辨率空间注意力可以对 56×56 的每个位置分配权重。cbam4则作用在 2048×7×7 上主要调整全局语义信息。如果把 CBAM 放在layer内部与残差分支并联还要考虑恒等映射是否也被加权这会让训练初期梯度更容易震荡。放在外部则不需要改动原始残差连接是最容易稳定复现的接入方式。3.3 预训练模型加载与新增层参数裁剪上面的实现直接通过backbone获取预训练卷积层load_state_dict不存在键冲突。如果用了strictTrue加载CBAM 和 fc 的缺失键会报错正确的姿势是去掉与cbam或fc相关的参数名。另一种常见做法是先构建 torchvision 的原始模型加载权重后把layer1到layer4赋值给自定义模型同时保留conv1、bn1等结构。这样 Imagenet 上训练出的低级边缘纹理特征不会丢失。CBAM 的初始化也值得注意新模块没有预训练启动时输出的空间权重会被 sigmoid 压到接近 0.5通道权重接近 1因此初始阶段 CBAM 几乎不改变原 ResNet 输出这是安全的行为。若希望模块更快进入训练状态可在SpatialAttention卷积层初始化里给 conv 参数乘一个小系数但一般不需要。4. 遥感土地利用图像分类的训练配置与评估指标4.1 数据集组织与类别平衡策略遥感土地利用图像分类的项目目录一般按train和val拆分每个类别一个子文件夹。影像来源可能是高分二号、Sentinel-2 或者无人机航片统一裁剪到 224×224 或 256×256 作为输入。没有预先切好的数据集时最先要检查的是类别面积占比。建设用地、水体常常占比较大而裸地、道路占比较小直接用 CrossEntropyLoss 会导致小类别被忽略。CBAM 模块能缓解这个问题但没有办法完全替代损失函数层面的处理。我一般会先统计训练集每类样本数计算中位数与最小类别之间的比例超过 5:1 就采用加权采样或给损失函数添加类别权重。遥感数据往往带有地理位置相关性切割训练集时一定要按地块或图幅划分不能把同一块地的 patch 同时放进训练和验证否则验证指标虚高。4.2 训练超参数冻结策略、优化器和学习率使用预训练 ResNet 后backbone 参数不需要从零训练。载入预训练模型后可冻结 backbone 前几层固定 low-level 特征只训练 CBAM 和分类头。对于 6 类土地利用分类这种中小型任务常见配置如下超参数建议值影响optimizerSGD momentum0.9遥感特征噪声较大SGD 比 Adam 更易收敛到平坦极值lr0.001 骨干0.001 CBAM从 0.01 起步容易让 CBAM 梯度突变weight_decay5e-4在空间注意力卷积上倾向小权重batch_size32 或 64受影像通道数与裁切大小影响epochs30 到 50CBAM 前 10 轮基本完成通道筛选为了处理新模块与预训练骨干的行为不一致可以对参数做分组backbone_params [] cbam_params [] baseline_params [] for name, param in model.named_parameters(): if cbam in name: cbam_params.append(param) else: backbone_params.append(param) optimizer torch.optim.SGD([ {params: backbone_params, lr: 1e-3}, {params: cbam_params, lr: 1e-3}, ], momentum0.9, weight_decay5e-4)如果希望骨干更稳定可以将第一个 layer 的学习率设为其他层的 0.1 倍我一般只在数据量为几千张这种规模下才启用这一设置。训练中关注第一个 epoch 的 lossCBAM 初始权重接近恒等loss 应和原始 ResNet 近似如果明显高出说明输入归一化或学习率设置异常。4.3 用混淆矩阵和 Kappa 系数验证改进效果分类准确率在遥感土地利用上不足以说明问题特别是样本类别不均衡时。我会额外计算混淆矩阵和 Kappa 系数。Kappa 衡量的是分类结果与随机分类之间的差异数值在 0 到 1 之间遥感领域一般要求达到 0.75 以上才算模型可用。用测试集跑一轮预测再统计import numpy as np from sklearn.metrics import cohen_kappa_score, confusion_matrix preds [] labels [] model.eval() with torch.no_grad(): for inputs, targets in val_loader: inputs inputs.cuda() outputs model(inputs) _, predicted torch.max(outputs, 1) preds.extend(predicted.cpu().numpy()) labels.extend(targets.numpy()) kappa cohen_kappa_score(labels, preds) cm confusion_matrix(labels, preds)在对比实验中base_result 是只使用 ResNet50 的混淆矩阵cbam_result 是加入四个 CBAM 后的矩阵。加入 CBAM 后最明显的改观通常体现在水体和阴影的混分、道路与裸地的错贴上。肉眼逐类检查矩阵中的 off-diagonal 元素如果这些长期混淆的对角邻域值明显下降说明空间注意力确实在偏分位置起了作用而不只是整体准确率微涨。训练日志建议保留每个 epoch 的 Kappa因为准确率可能小幅波动而 Kappa 对偶然一致性更敏感更能反映空间注意力的稳定性。5. 验证改进效果与常见坑用 Grad-CAM 判断 CBAM 是否落在正确图层加入 CBAM 后最怕的并不是模型变差而是模块根本没有被训练起来只是挂在网络结构里形成了一个恒等分支。验证它是否真正发挥作用的直接方式是观察梯度、激活和注意力掩膜。一个前端技巧是注册 forward hook把 CBAM 中空间注意力输出的权重保存下来看看不同土地类别上权重分布有没有差异。遥感图像里的水体通常呈线性空间注意力应该沿水体轮廓产生长条高亮建设用地是连续面状区域注意力掩膜应该更平滑且集中在中心地块。如果所有类别的掩膜都几乎均匀说明空间注意力退化为全 1 映射。还可以用 Grad-CAM 对类别输出做可视化确认 CBAM 是否把分类头最后的最近邻调整到正确的纹理边界上。以下代码放在验证阶段对 layer4 后最后一层特征图求梯度def grad_cam(model, input_tensor, target_class): model.eval() input_tensor input_tensor.unsqueeze(0).cuda() output model(input_tensor) model.zero_grad() output[0, target_class].backward() conv_layer model.layer4[-1].conv3 feature conv_layer.output gradient conv_layer.weight.grad weights gradient.mean(dim(2, 3), keepdimTrue) cam (weights * feature).sum(dim1, keepdimTrue) cam torch.relu(cam) return cam实际运行时需要先给目标卷积层注册 forward hook 保存正向特征。将 cam 放大到输入尺寸后叠加到原始遥感图上如果高亮区域和目标地物重合度过低而你确认分类标签正确那问题往往出在 backbone 下采样太早CBAM 没有机会在早期特征图上形成空间分辨率的注意力。解决方法是把 layer1 或 layer2 后面的 CBAM kernel_size 加大到 7并且检查通道注意力的 max_path 是否有梯度消失。小批量训练时torch.max的梯度流在空间维度上会退化为单点建议用mean max融合空间注意力输入或者把批量大小提高到 16 以上避免单张样本主导梯度。使用一幅验证好的热力图作为快速测试检查 water 类别激活集中在河道而非岸边林地这就是 ResNetCBAM 相对普通 ResNet 可观察到的真实改进。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询