
簡介面向遙感衛(wèi)星土地利用分類任務(wù)的ResNet系列改進實戰(zhàn)代碼包覆蓋resnet18/34/50/101/152五種主干。訓(xùn)練時可根據(jù)需要選擇遷移學(xué)習(xí)或僅訓(xùn)練分類層優(yōu)化器集成Adam與SGD損失函數(shù)采用多類別交叉熵學(xué)習(xí)率使用余弦退火策略方便進行對比消融實驗。驗證集在訓(xùn)練過程中同步評估輸出loss、準(zhǔn)確率、混淆矩陣、recall、precision、F1 score與特異度等指標(biāo)并自動生成對應(yīng)曲線圖像訓(xùn)練日志以JSON格式保存便于按需繪制其他曲線。核心改進在ResNet每個layer后加入CBAM注意力模塊可只保留某一層后的CBAM也可整體替換為其他注意力或模塊代碼結(jié)構(gòu)清晰易擴展。包內(nèi)共2000個文件以1994張JPEG土地分類圖像為主另有3個Python腳本、readme說明、JSON日志及txt配置壓縮包大小27.79MB數(shù)據(jù)覆蓋21種土地目標(biāo)。目前已有71人學(xué)習(xí)下載適合需要快速上手注意力機制增強圖像分類模型的研究者與開發(fā)者。1. 遙感土地利用分類為何要給 ResNet 每個 layer 后加 CBAM一張 512×512 的遙感圖里農(nóng)田邊界和裸地經(jīng)常只有色調(diào)深淺的差別水體在大尺度下是一整片、切成小 patch 后又是零碎的溝渠ResNet 這種靠卷積堆深度的骨干網(wǎng)絡(luò)做土地利用分類時總顯得“看不清重點”。給 ResNet 的每個 layer 后串一個 CBAM 注意力模塊是在不改主干、不換 Transformer 的前提下用很少的參數(shù)量把“該看哪些通道、該聚焦哪個位置”直接教給網(wǎng)絡(luò)。這個方案適合手里有幾千到幾萬張標(biāo)注圖、希望快速在遙感衛(wèi)星土地利用分類上提點的一線工程師和研究生。2. 讀懂 CBAM 的兩個子模塊通道先行的注意力為什么適合遙感地物2.1 通道注意力與空間注意力CBAM 在計算什么CBAM 全稱是 Convolutional Block Attention Module設(shè)計上很樸素先做通道注意力再做空間注意力兩個子模塊串行輸出一個和輸入形狀完全相同的重標(biāo)定特征。通道注意力部分對輸入特征圖分別做全局平均池化和全局最大池化得到兩個 1×1×C 的描述子送進一個共享的兩層 MLP加和后再過 Sigmoid得到 1×1×C 的通道權(quán)重。空間注意力部分則是在通道維上對特征圖做平均和最大壓縮拼成一個 2×H×W 的“雙通道”描述用一個 7×7 卷積降成 1×H×W再過 Sigmoid 得到空間權(quán)重。整個過程可以寫成F1 Mc(F) ? FF2 Ms(F1) ? F1。注意 CBAM 訓(xùn)練初期權(quán)重都接近 1所以它不會像 BN 那樣劇烈改變特征分布這也是它能直接插進預(yù)訓(xùn)練模型的原因之一?;氐竭b感土地利用場景農(nóng)田、草地、林地之間紋理差異大但顏色相近的類別容易混淆通道注意力會告訴網(wǎng)絡(luò)“這個地塊更依賴紅光波段還是近紅外波段的響應(yīng)”建筑和裸地的邊界模糊空間注意力則負責(zé)把注意力集中在“地塊內(nèi)部”而不是路網(wǎng)和陰影邊緣。相比只有通道注意力的 SE 模塊CBAM 多出來的這一路空間注意力恰好補上了遙感地物對“位置感”的需求。參數(shù)開銷上以 ResNet18 為例完整 CBAM 插在四個 stage 后新增參數(shù)約 0.4M對比模型本身的 11.7M 幾乎可以忽略。2.2 “每個 layer 后加”到底是加在哪一級標(biāo)題里“每個 layer 后加入 CBAM”這個說法在實操中其實有歧義。ResNet 的 layer 通常指 torchvision 實現(xiàn)里的 layer1 到 layer4也就是模型結(jié)構(gòu)上的 4 個 stage每個 stage 里包含若干個 BasicBlock 或 Bottleneck。把 CBAM 加在 stage 尾部是參數(shù)效率最高的做法而如果理解為“每個 BasicBlock 的第二個卷積后再加”參數(shù)量和顯存都會明顯上漲訓(xùn)練時間也拉長。我一般默認(rèn)“每個 layer 后”就是 stage 后。下面是三種常見加法的對比。加插位置新增參數(shù)ResNet18顯存影響實測效果適用場景每個 stage 后layer1~4 尾部約 0.4M小穩(wěn)定提升 0.5%~2%圖像分類推薦首選每個 BasicBlock 后約 1.2M明顯容易過擬合收斂不穩(wěn)小數(shù)據(jù)集不建議只在 layer3、layer4 后約 0.2M很小提升幅度接近全加計算資源緊張時stage 后加還有一個好處預(yù)訓(xùn)練權(quán)重完全不受影響。因為 CBAM 是額外 add_module 進去的原始卷積和 BN 層的權(quán)重路徑?jīng)]有被改寫加載 ImageNet 權(quán)重時不會出現(xiàn) missing key 或 shape 不匹配。這一點對遙感這種“預(yù)訓(xùn)練權(quán)重決定上限”的任務(wù)尤其重要。2.3 為什么不是自注意力或 FPNCBAM 的取舍近兩年圖像分類模型的熱點已經(jīng)偏向 Transformer自注意力機制確實擅長捕捉粗粒度到細粒度的長程依賴但它在遙感土地利用任務(wù)上有一個現(xiàn)實門檻數(shù)據(jù)量。ViT 類模型在 ImageNet 上至少需要幾千萬張圖才能訓(xùn)出好權(quán)重遙感切塊數(shù)據(jù)往往只有幾千到幾萬張直接微調(diào)很容易過擬合而且 patch 化會把地物的邊界打碎位置編碼也要重新適應(yīng)遙感圖的分布。FPN 是另一條思路但它是檢測框架里的多尺度特征融合結(jié)構(gòu)用于分類任務(wù)需要額外加分類頭改動遠大于一個 CBAM。CBAM 的好處是即插即用不改變數(shù)據(jù)流形狀訓(xùn)練策略和原來幾乎一樣本質(zhì)上是用局部注意力去替代全局自注意力的“大部分收益”。在樣本有限、算力有限的前提下先把 CBAM 加進 ResNet 是性價比最高的第一步。3. 手寫 ResNetCBAM三個代碼塊把注意力插進每個 layer 后3.1 定義 CBAM 模塊通道注意力與空間注意力的最小實現(xiàn)下面是基于 PyTorch 的標(biāo)準(zhǔn) CBAM 實現(xiàn)我習(xí)慣把通道注意力和空間注意力拆成兩個子類調(diào)試時可以直接單獨看某一層輸出。import torch import torch.nn as nn class ChannelAttention(nn.Module): def __init__(self, in_planes, ratio16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.shared_mlp nn.Sequential( nn.Conv2d(in_planes, in_planes // ratio, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(in_planes // ratio, in_planes, 1, biasFalse), ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.shared_mlp(self.avg_pool(x)) max_out self.shared_mlp(self.max_pool(x)) return self.sigmoid(avg_out max_out)in_planes 是輸入特征圖的通道數(shù)ratio 決定中間瓶頸維度。ResNet18 的 layer1 輸出 64 通道64 // 16 4不會出現(xiàn) 0 維但如果你把 ratio 改成 6464 // 64 1也還成立再小就會出問題。這里共享同一個 MLP 對 avg 和 max 兩個分支做映射是論文里的標(biāo)準(zhǔn)寫法我自己實踐時發(fā)現(xiàn) max_pool 分支在遙感圖里更重要因為農(nóng)田邊界和裸地往往是局部極值特征??臻g注意力模塊代碼如下class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() padding kernel_size // 2 self.conv nn.Conv2d(2, 1, kernel_size, paddingpadding, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) x_cat torch.cat([avg_out, max_out], dim1) return self.sigmoid(self.conv(x_cat))kernel_size7 是論文默認(rèn)值7×7 卷積的感受野約等于 7×7 像素對 224×224 輸入來說足夠覆蓋常見地物。如果遙感切塊比較小比如 64×64 的 patch7×7 就顯得過大我會改成 3見第 6 章。組合起來就是class CBAM(nn.Module): def __init__(self, in_planes, ratio16, kernel_size7): super().__init__() self.channel_attn ChannelAttention(in_planes, ratio) self.spatial_attn SpatialAttention(kernel_size) def forward(self, x): x self.channel_attn(x) * x x self.spatial_attn(x) * x return xforward 里的乘法和原特征做的是逐元素相乘CBAM 的輸出形狀和輸入完全一樣所以它能插入網(wǎng)絡(luò)任何位置而不影響后續(xù)張量尺寸。3.2 把 CBAM 插進 ResNet先加載預(yù)訓(xùn)練權(quán)重再 add_module這里有一個容易翻車的順序問題如果你先把 CBAM 寫進 ResNet 結(jié)構(gòu)里再去加載torchvision預(yù)訓(xùn)練權(quán)重load_state_dict會因為新增模塊報 missing key正確做法是先用官方 API 拿到完整權(quán)重再往模型上掛模塊這樣 ResNet 本體的權(quán)重一條都不需要改動。import torchvision.models as models # 這一步拿到的是標(biāo)準(zhǔn) ResNet18 ImageNet 預(yù)訓(xùn)練權(quán)重 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) def add_cbam_to_resnet(model, add_after_layer(True, True, True, True)): cbam_channels [64, 128, 256, 512] # resnet18/34 的四個 stage 輸出通道 layers [model.layer1, model.layer2, model.layer3, model.layer4] for i, (layer, ch) in enumerate(zip(layers, cbam_channels)): if add_after_layer[i]: # 在 stage 的尾部追加 CBAM不改變?nèi)魏我延袑拥臋?quán)重 layer.add_module(fcbam_{i1}, CBAM(in_planesch, ratio16, kernel_size7)) return model model add_cbam_to_resnet(model)add_module的作用是往layer1這個Sequential容器里追加一個子模塊。以 ResNet18 為例原來layer1包含兩個BasicBlock追加后變成三個子模塊數(shù)據(jù)流會按順序執(zhí)行 block1 - block2 - cbam_1。cbam_1 的輸入輸出都是 64 通道不改變 residual 結(jié)構(gòu)。這樣實現(xiàn)比修改torchvision源碼里的BasicBlock干凈得多也方便隨時通過add_after_layer開關(guān)只加后幾個 stage。對于 ResNet50四個 stage 輸出通道是 256、512、1024、2048代碼里只需把cbam_channels換掉其他邏輯不變。參數(shù)初始化的部分不需要額外處理add_module新增的模塊會默認(rèn)使用 PyTorch 的默認(rèn)初始化CBAM 初始輸出接近 1不會在第一個 epoch 就沖亂主干特征。3.3 驗證前向與參數(shù)量跑通最小測試再進訓(xùn)練模型改完先別急著訓(xùn)用隨機張量走一遍前向同時對比參數(shù)量變化這一步能攔截絕大多數(shù)“結(jié)構(gòu)改錯但沒報錯”的玄學(xué)問題。# 前向驗證 model.eval() with torch.no_grad(): out model(torch.randn(1, 3, 224, 224)) print(out.shape) # 期望輸出 torch.Size([1, 1000]) # 參數(shù)量對比 total_params sum(p.numel() for p in model.parameters()) trainable_params sum(p.numel() for p in model.parameters() if p.requires_grad) print(ftotal: {total_params / 1e6:.2f}M, trainable: {trainable_params / 1e6:.2f}M)如果 output 的最后一維不是 1000說明 FC 頭被改動了如果參數(shù)量和原始 ResNet18 的 11.7M 差出好幾倍說明 CBAM 被加到循環(huán)里重復(fù)堆疊了。正常加在四個 stage 后ResNet18 總參數(shù)量約 12.1M新增 0.4M 左右。這個測試數(shù)據(jù)也可以直接寫進實驗記錄后面對照“加 CBAM 到底貴了多少”就不用現(xiàn)算。4. 遙感土地利用分類的訓(xùn)練配置數(shù)據(jù)目錄、增強與超參數(shù)一次調(diào)到位4.1 數(shù)據(jù)目錄與樣本劃分按圖幅切塊是底線土地利用分類常見類別包括農(nóng)田、森林、草地、水體、建筑、裸地六類數(shù)據(jù)按 ImageFolder 組織最省事。有一點要從一開始就注意要按原始遙感圖幅來劃分訓(xùn)練集和驗證集而不是把一張大圖切出的所有 patch 隨機打散。否則同一個地塊的紋理會被模型記進權(quán)重里驗證集虛高兩三個點都不奇怪。data/ train/ farmland/ forest/ grassland/ water/ building/ bareland/ val/ farmland/ forest/ ...每個類別下放經(jīng)過篩選的切塊切塊尺寸常用 224×224 或 256×256。多光譜數(shù)據(jù)輸入通道不是 3 時預(yù)訓(xùn)練權(quán)重不能直接用要么訓(xùn)練時只取 RGB 三波段要么把第一個卷積層單獨處理這部分細節(jié)放在第 5 章避坑清單里。數(shù)據(jù)集來源建議直接選用公開遙感分類數(shù)據(jù)集網(wǎng)上圖像分類數(shù)據(jù)集下載渠道很多但自己抓圖會引入標(biāo)注不一致和傳感器差異這兩個問題比模型結(jié)構(gòu)更難處理。4.2 數(shù)據(jù)增強強一點的幾何增強對注意力更友好遙感圖沒有“上下顛倒”的概念所以翻轉(zhuǎn)可以放開用。增強策略表如下。增強操作參數(shù)建議說明RandomResizedCropscale(0.5, 1.0), size224強制模型從不同尺度學(xué)地物RandomHorizontalFlipp0.5通用增強穩(wěn)定收斂RandomVerticalFlipp0.5遙感圖特有不破壞語義ColorJitterbrightness0.2, contrast0.2數(shù)值不宜過大破壞光譜特征NormalizeImageNet 均值/方差搭配預(yù)訓(xùn)練權(quán)重的標(biāo)配ColorJitter 是雙刃劍。遙感地物的光譜反射率是有物理意義的調(diào)太多會讓水體發(fā)綠、植被發(fā)黃反而引入噪聲。我一般把 brightness 和 contrast 都壓在 0.2 以內(nèi)saturation 不動。加 CBAM 之后模型對空間位置更敏感RandomResizedCrop 的尺度擾動可以適當(dāng)調(diào)強一點防止注意力被“地塊邊緣”這種固定特征騙走。4.3 訓(xùn)練腳本與超參數(shù)小學(xué)習(xí)率配 cosine 足夠ResNet CBAM 的訓(xùn)練策略和純 ResNet 幾乎一致唯一要注意的是 CBAM 是隨機初始化的初始學(xué)習(xí)率不宜過大。用預(yù)訓(xùn)練權(quán)重時我一般用 0.005batch size 64能在前 5 個 epoch 里讓 CBAM 平穩(wěn)進入工作狀態(tài)。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms transform_train transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.5, 1.0)), transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_set datasets.ImageFolder(data/train, transformtransform_train) val_set datasets.ImageFolder(data/val, transformtransform_val) train_loader DataLoader(train_set, batch_size64, shuffleTrue, num_workers8, pin_memoryTrue) val_loader DataLoader(val_set, batch_size64, shuffleFalse, num_workers8, pin_memoryTrue) criterion nn.CrossEntropyLoss(label_smoothing0.1) optimizer torch.optim.SGD(model.parameters(), lr0.005, momentum0.9, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) model model.cuda() for epoch in range(30): model.train() for x, y in train_loader: x, y x.cuda(), y.cuda() out model(x) loss criterion(out, y) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 驗證代碼省略記錄 val acc 和各類別 F1label_smoothing0.1對遙感粗標(biāo)注特別有用因為真實標(biāo)注里混著很多過渡地類硬標(biāo)簽很容易讓模型變得過度自信。CosineAnnealingLR 的T_max30對應(yīng)訓(xùn)練輪數(shù)如果你把調(diào)度改成按 iteration 更新T_max需要改成 30×len(train_loader)兩種寫法不要混用否則學(xué)習(xí)率會掉得太快。4.4 驗證指標(biāo)總體精度之外一定要看類別 F1土地利用分類只報 overall accuracy 是不夠的水體、建筑這類類別樣本往往偏少大類準(zhǔn)確率高會把小類的惡化掩蓋掉。我在訓(xùn)練時每個 epoch 都記錄宏平均 F1 和每類 F1尤其關(guān)注 building 和 bareland 這對“易混淆對”。加了 CBAM 之后常見的變化是總體 acc 提升 1% 左右但 building 和 water 的 F1 拉開差距這說明空間注意力開始把邊界類地物和內(nèi)部均質(zhì)地物分開處理了。如果只是 acc 小幅漲而 F1 沒動那多半是數(shù)據(jù)劃分泄露帶來的虛漲不是模型的功勞。5. 加 CBAM 后訓(xùn)練與評估的避坑清單從掉點到 OOM 的五個現(xiàn)象5.1 加了 CBAM 準(zhǔn)確率反而下跌現(xiàn)象完整加在四個 stage 后val acc 比純 ResNet 還低 1~2 個點loss 曲線也不怎么降。原因最常見是 CBAM 加得太“滿”。layer1 和 layer2 的淺層特征主要表達邊緣、紋理這些低層特征本身空間分辨率高強注意力會把淺層特征過度調(diào)制反而干擾后續(xù)層。另一個原因是學(xué)習(xí)率偏大隨機初始化的 CBAM 在初期被 SGD 大步長推偏主干也被帶歪。解決先只在 layer3、layer4 后加 CBAM用第 3 章代碼里的add_after_layer(False, False, True, True)學(xué)習(xí)率從 0.005 降到 0.003并加 3 個 epoch 的線性 warmup。絕大多數(shù)數(shù)據(jù)集上只加后兩個 stage 的表現(xiàn)不比四個 stage 差訓(xùn)練還更快。5.2 遙感數(shù)據(jù)“地點泄露”導(dǎo)致驗證集虛高現(xiàn)象隨機劃分時 val acc 92%看起來效果很好一旦按圖幅劃劃分同一張原始大圖的 patch 全進同一個集合準(zhǔn)確率掉到 78%。原因隨機劃分把同一個地塊的相鄰切塊同時放進了訓(xùn)練集和驗證集模型記住的是“這張圖的色調(diào)紋理”而不是“這類的通用特征”。這在遙感數(shù)據(jù)集里是普遍存在的黑匣子問題很多公開數(shù)字好看落地到新區(qū)域就翻車。解決劃分?jǐn)?shù)據(jù)時以圖幅 ID 為最小單位。先把一張原始衛(wèi)星圖按滑動窗口切塊記錄每個 patch 的父圖編號然后按“圖幅”分組做 train/val split保證同一個圖幅的 patch 不會同時出現(xiàn)在兩側(cè)。這條和 CBAM 無關(guān)但如果不先堵住所有“CBAM 提升”的結(jié)論都是假的。5.3 多光譜四通道輸入怎么處理現(xiàn)象數(shù)據(jù)集是 RGBNIR 四波段把第一個卷積層改成輸入 4 通道后loss 卡住不降訓(xùn)練幾個 epoch 后 val acc 仍然接近隨機。原因直接改model.conv1的in_channels4后這一層權(quán)重隨機初始化ImageNet 預(yù)訓(xùn)練權(quán)重的信息全部失效等于讓模型從零學(xué)第一個卷積層。CBAM 本身沒問題問題出在主干入口。解決常見的做法是用 3 通道預(yù)訓(xùn)練權(quán)重初始化 RGB 部分新增加的第 4 個通道用 RGB 權(quán)重的均值去填。代碼上可以這樣處理old_conv1 model.conv1.weight.data # shape [64, 3, 7, 7] new_conv1 torch.zeros(64, 4, 7, 7) new_conv1[:, :3, :, :] old_conv1 new_conv1[:, 3, :, :] old_conv1.mean(dim1) model.conv1 nn.Conv2d(4, 64, 7, stride2, padding3, biasFalse) model.conv1.weight.data new_conv1也可以選擇只訓(xùn)第一個 conv 層而凍結(jié)其余層跑幾個 epoch再全部解凍。這種“半凍結(jié)”策略能明顯減少多光譜入口帶來的訓(xùn)練震蕩。既然用到了多光譜數(shù)據(jù)增強里的 ColorJitter 更不建議開大近紅外通道的數(shù)值擾動會讓植被類地物特征失效。5.4 顯存溢出batch 64 訓(xùn)不動現(xiàn)象ResNet18 原本 batch 64 跑得好好的加上 CBAM 后同一個 batch 直接 OOM。原因CBAM 里的 7×7 空間注意力卷積雖然參數(shù)量小但它在每個 stage 結(jié)束后都保留了一份完整的 H×W 中間激活用于反向傳播。四個 stage 的特征圖分辨率逐級減半淺層的 64 通道 56×56 特征圖被多保留了一份顯存峰值就上去了。解決把kernel_size從 7 降到 3能省一點計算但不會太多更有效的是減少插入位置只加(False, False, True, True)淺層不保留額外激活。還有一招是把 batch size 降到 32并用梯度累積模擬 64 的等效 batch。如果顯存還是很緊可以考慮對 CBAM 的 forward 使用torch.utils.checkpoint用計算換顯存但這個操作會拖慢訓(xùn)練。5.5 水體精度特別差森林卻很高現(xiàn)象森林類別 F1 0.94水體只有 0.61訓(xùn)練曲線顯示 water 的 loss 一直很高。原因水體在遙感圖里往往是大面積均質(zhì)區(qū)域經(jīng)過 4 次下采樣后邊界信息基本丟失CBAM 空間注意力在深層拿到的已經(jīng)是低分辨率特征很難恢復(fù)完整的水體輪廓。森林紋理豐富即使分辨率低也能靠紋理特征區(qū)分。解決對這類“大目標(biāo)類別”可以在 layer4 后、全局池化前再補一個 CBAM讓空間注意力在最高的語義特征層上再做一次區(qū)域加權(quán)。另一個更直接的辦法是把訓(xùn)練切塊改成多尺度除了 224×224 的 patch再抽一部分 448×448 的大 patch 下采樣到 224 輸入讓模型在訓(xùn)練時見過“整片水體”的樣子。我的實踐經(jīng)驗里最后這種尺度策略對水體類別的 F1 提升比調(diào) CBAM 參數(shù)更明顯。6. 驗證 CBAM 是否真的有效三組消融與熱力圖定位6.1 先跑三組對照別只盯最后一輪 acc建議至少做三組實驗純 ResNet18 基線、ResNet18 CBAM(后兩個 stage)、ResNet18 CBAM(四個 stage)。下面是一個典型的記錄表。模型參數(shù)量val accmacro F1單 epoch 耗時ResNet1811.7M86.2%79.5%42sResNet18 CBAM(后兩 stage)11.9M87.4%81.0%47sResNet18 CBAM(四 stage)12.1M87.1%80.6%53s后兩個 stage 的版本往往比四個 stage 的更好這個現(xiàn)象在很多遙感數(shù)據(jù)集上都能復(fù)現(xiàn)。判斷 CBAM 有沒有用還要看看訓(xùn)練前 10 個 epoch 的 loss 曲線CBAM 版本通常下降更快這個“前期收斂加速”比最后一輪的 acc 更能說明模塊真的在學(xué)習(xí)注意力。6.2 用 Grad-CAM 看注意力落點消融實驗數(shù)值上去了還想確認(rèn)空間注意力到底看重哪里可以用 Grad-CAM 做可視化。下面是被簡化后的核心片段。def grad_cam(model, x, target_class): model.eval() feature None gradient None def hook_f(module, input, output): nonlocal feature feature output.detach() def hook_b(module, grad_input, grad_output): nonlocal gradient gradient grad_output[0].detach() target_layer model.layer4[-1] # 注意此時 layer4 的最后一個子模塊是 cbam_4 handle_f target_layer.register_forward_hook(hook_f) handle_b target_layer.register_full_backward_hook(hook_b) out model(x) model.zero_grad() out[0, target_class].backward() handle_f.remove() handle_b.remove() weight gradient.mean(dim(2, 3), keepdimTrue) cam (weight * feature).sum(dim1, keepdimTrue).relu() return cam在 torchvision 的 ResNet 里layer4[-1]不再是一個 BasicBlock 的 conv而是我們掛上去的 CBAM這會讓熱力圖直接反映 CBAM 輸出前的梯度分布反而很適合觀察注意力給自己留下了哪些區(qū)域。對比純 ResNet 的熱力圖CBAM 版本的熱力中心通常更集中在地塊內(nèi)部而不是全圖彌散。6.3 一個有用的調(diào)參習(xí)慣先大后小最后分享一個我自己的習(xí)慣任何新數(shù)據(jù)集上做 CBAM 實驗都先把ratio固定 16、kernel_size固定 7、只放在層3和層4后跑通一輪再根據(jù)可視化結(jié)果決定要不要加到淺層。理論上 CBAM 參數(shù)不多但它們和數(shù)據(jù)集尺度、切塊策略耦合在一起網(wǎng)格搜索“四個 stage 是否都加”的成本遠高于它的收益。希望這些經(jīng)驗?zāi)軒湍闵僮咭欢螐澛纷U{(diào)參順利。本文還有配套的精品資源點擊獲取