☰
小波变换+平行注意力:多源遥感分类提效实战
2026/10/5 2:57:57 网站建设 项目流程

简介:基于小波变换与平行注意力的多源遥感图像分类完整实现,源自北京航空航天大学学报2023年同名论文,面向遥感图像处理研究者与机器学习开发者,用于解决多源遥感数据在复杂场景下的精准识别与分类难题。压缩包共56个文件,体积仅2.97MB,包括19个Python源码、31个编译后的pyc文件、2个YAML环境配置、论文PDF与说明文档,源码按数据、模型、训练、测试、可视化等模块组织,结构清晰。其中不仅集成小波变换工具,还提供FusatNet、Fusion-HCT、AsyFFNet、DFINet等多套平行注意力网络,以及基准脚本与依赖清单,便于直接运行或在此基础上改进。已有160人学习下载,适用于土地利用分类、环境监测、灾害预警等遥感应用场景,也是理解小波特征提取与注意力机制结合的优质参考实现。

1. 多源遥感分类卡在特征融合上时,我把注意力换成了平行结构

接手过一个土地利用分类的小型工程,光学影像加SAR数据堆成8通道直接喂进ResNet,OA一直卡在84%,调学习率、换数据增强都救不回来。后来把特征融合段换成小波变换加平行注意力,同一套数据,OA涨了近8个点,参数量还没怎么涨。这个标题拆开是三件事:小波变换在频域把多源特征拆开,平行注意力在通道与空间两个维度同时筛选关键信息,最后拼成一个能端到端训练的多源遥感图像分类模型。适合两类人:手上有光学或SAR数据、想通过多源融合把精度顶上来的工程师,以及正在找毕业设计或论文方案、需要一份能跑能改源码的人。接下来按原理、代码、训练配置、踩坑和验证逐层讲。

2. 多源遥感为什么难:四个“对不齐”与小波的频域补位逻辑

2.1 多源遥感做分类,最难的从来不是网络结构

多源遥感图像分类要解决的实际问题是:把来自不同传感器的影像统一映射到一组语义类别上,例如水体、耕地、建筑、林地和裸地。工程里最常见的组合是光学加SAR,有时再叠DEM和夜光数据。大家愿意引入多源,是因为单一传感器有天然短板:光学影像受云和光照影响大,阴天或夜间直接失效;SAR虽然不受云层制约,但几何畸变明显、相干斑噪声重。多源不是为了多个输入通道,而是为了信息互补。

但多源数据一起用的时候,难点就落在四个“对不齐”上:

不对齐项典型表现对分类的影响
空间分辨率光学0.5米,SAR 10米地物边界错位,类别边缘噪声大
波段物理量光学反射率 vs SAR后向散射系数数值量纲差异大,直接堆叠难收敛
成像时间同一区域影像日期相差数周农田、水体等时相敏感类别标签冲突
几何投影地形起伏在SAR上出现叠掩、阴影阴影区域的像元与光学标签错位

这四个不对齐叠加起来的效果是,同一个地物类别在不同源上表现为完全不同的像素分布。水体在光学影像里是暗色低反射目标,在SAR上因为镜面反射通常也偏暗;城市建筑在光学里是高亮高纹理,在SAR上却因为多重散射出现角反射亮斑。也就是说,“同类别不同表象”才是多源分类的根本难点,单靠把波段叠成多通道让网络去隐式记忆,需要大量的训练样本和更深的网络去兜底,效果还不一定稳。

所以我在做这类工程时,一般不会先去堆网络深度,而是先回答一个问题:能不能在把数据送进分类器之前,把“共性结构”和“差异纹理”分开?小波变换正好提供了这个视角。

2.2 小波变换在分类里不是“去噪前处理”,是给网络一个分频视角

现在网上搜小波变换图像增强python或小波变换图像去噪,能搜到的大多数例程逻辑是一样的:对图像做离散小波变换,把高频系数做收缩或阈值处理,再做逆变换重建图像。这是图像复原的思路,在遥感预处理里确实有人用,但把它原样搬进分类模型通常不划算,因为它会连带把有用的边缘纹理弱化。

分类模型里更合理的做法,是把小波变换当成一个“分频器”而不是“滤器”。一层离散小波变换把图像分解成四个子带:LL是低频近似,对应原图下采样后的主体结构;LH、HL、HH分别捕捉水平、垂直和对角方向的细节响应。LL保留了整体轮廓但丢掉部分高频噪声,LH/HL/HH则把纹理和噪声按方向分开。对遥感分类来说,这意味着SAR的相干斑噪声主要落在高频子带里,而光学云阴影这类大尺度亮度异常主要污染LL区域,两者不再混在同一张图上。

和傅里叶变换相比,小波变换的关键优势是保留了空间位置信息。傅里叶把整幅图变成频域系数后,每个系数对应的是全局频率分量,空间位置丢失,分类模型用起来还得再拼回来。小波变换不同,分解后的每个子带仍是空间域的图,只是分辨率减半,后续网络可以直接在这个结构上继续卷积。

这里还有一个细节值得注意:小波分解后每个子带的统计分布差异很大。LL的值范围通常接近原图,HH的均值和动态范围明显偏小,并且以零为中心。如果不分别做标准化,高频分支在尺度上天然弱势,卷积层输出被低频主导,等于白加了分支。我第一次跑实验时没在意这个点,见loss降得还可以,直到看注意力图发现高频分支权重几乎为零,才想起来子带级别的归一化漏了。这一条在不少开源代码里只是一句注释,实际做出来的差异很大。

2.3 平行注意力:不是CBAM那种串行,是两个分支各管一摊再汇合

注意力模块的选择,是标题里第二个关键决策。常见选择是CBAM那种串行结构:先做通道注意力,再把增强后的特征图送到空间注意力里。很多开源代码里直接抄这个结构,放在遥感分类里效果往往一般。

串行注意力的隐患在于两级串联后,前一级的权重误差会被后一级放大。遥感影像尺度跨度大,一条两米宽的道路和一片几百米的水域在同一张图里,通道注意力按全局统计算出来的权重天然偏向水域这类大面积目标,再把结果喂给空间注意力,小目标对应的空间位置可能已经被压没了。

平行注意力换了个思路:两个分支并行,分别独立计算通道权重和空间权重,再回到输入特征图上做融合。通道分支管通道,把每个波段或特征通道的重要性算出来;空间分支管位置,把“该看图像哪个位置”的权重图算出来。两者互不依赖、互不放大误差,对遥感里大目标和小目标共存的情况更友好。

这里要注意一个容易混淆的表述:有人把Transformer里的多头注意力也叫平行注意力,那其实是同一个注意力机制分多个头做线性投影,再拼接结果。我们说的平行注意力,是通道注意力和空间注意力这两种机制的并行,两者结构、参数、作用对象都不同,只在最后一步汇合。

另外,并行之后用加法融合还是乘法融合也值得定一下。有的实现把通道注意力和空间注意力结果相乘再乘回输入,我试下来加法(x * ca + x * sa)比乘法更稳,因为两个sigmoid输出都小于1,乘法会让整体响应幅值快速衰减,深层网络梯度信号变小。这个区别在小数据集上尤其明显。

3. 把“设计”落成源码:数据入口、小波分解层与平行注意力模块

3.1 数据入口设计:光学与SAR先逐像素对齐,再谈分类

写代码前有一件事必须先在数据层面做完:多源影像的空间对齐。我一般会在QGIS或者GDAL里把所有影像重投影到同一坐标系、重采样到同一分辨率、裁到同一范围,再做成一组shape一致的tif或npy文件。这个步骤不要写进dataloader里动态做,否则每次训练都要重新采样,慢而且结果不稳定。

对齐完成后,数据读取本身很简单,我用rasterio直接读tif,在Dataset类里做逐波段标准化:

import numpy as np import rasterio from torch.utils.data import Dataset class RemoteSensingDataset(Dataset): def __init__(self, img_path, label_path, band_mean, band_std): self.img = rasterio.open(img_path).read().astype(np.float32) # (C,H,W) self.label = rasterio.open(label_path).read(1).astype(np.int64) # (H,W) self.mean = np.array(band_mean, dtype=np.float32).reshape(-1, 1, 1) self.std = np.array(band_std, dtype=np.float32).reshape(-1, 1, 1) def __len__(self): return 1 def __getitem__(self, idx): img = (self.img - self.mean) / (self.std + 1e-6) return img.astype(np.float32), self.label.astype(np.int64)

这段代码有两个容易踩的细节。一是rasterio.open().read()读出来的shape是(C,H,W),和PyTorch默认的(B,C,H,W)只差batch维,别在Dataset里顺手转成(H,W,C);二是标准化必须每个波段单独做,尤其SAR数据的后向散射系数和光学反射率量纲差好几个数量级,统一拉成均值0、方差1是后面能不能收敛的前提。这里band_mean是长度等于波段数的列表,SAR波段建议先取对数再做统计,直接对原始强度数值做标准化容易受极端亮目标干扰。

真实工程里不会把整张影像一次性喂进网络,我会按滑窗切成128或256的patch训练。采样逻辑一般写在数据预处理阶段,Dataset只负责按索引取patch;我还会让相邻patch保留重叠,stride取patch边长的一半,对小目标分类帮助明显。

3.2 小波分解的代码:pywt.wavedec2 返回的系数怎么用

小波分解在工程里最常用的是PyWavelets库,核心函数是pywt.wavedec2。它不是张量算子,输入输出都是numpy数组,所以我在网络外面做离线分解,把分解后的子带存成npy,训练时直接读,比在训练循环里每次现算省下很多IO和CPU时间。

import numpy as np import pywt def pad_to_multiple(img, base=2): _, _, h, w = img.shape ph = (base - h % base) % base pw = (base - w % base) % base if ph == 0 and pw == 0: return img return np.pad(img, ((0, 0), (0, 0), (0, ph), (0, pw)), mode='reflect') def wavelet_split(img, wavelet='db2', level=1): """img: (B,C,H,W) float32 归一化影像 返回: ll (B,C,H/2,W/2), detail (B,C,3,H/2,W/2) """ img = pad_to_multiple(img) b, c, h, w = img.shape ll = np.zeros((b, c, h // 2, w // 2), dtype=np.float32) detail = np.zeros((b, c, 3, h // 2, w // 2), dtype=np.float32) for n in range(b): for ch in range(c): coeffs = pywt.wavedec2(img[n, ch], wavelet=wavelet, level=level) cA, (cH, cV, cD) = coeffs ll[n, ch] = cA detail[n, ch, 0] = cH detail[n, ch, 1] = cV detail[n, ch, 2] = cD return ll, detail

代码逻辑不复杂:对每个样本的每个波段单独做一层二维离散小波变换,然后按系数分别存入低频和高频两个数组。wavedec2返回的最低层低频系数就是LL,高频部分是一个三元组(cH, cV, cD),分别对应水平、垂直、对角方向。

这里有两个参数需要仔细选。

第一是wavelet,默认用'db2'(Daubechies-2),它的支撑长度比'haar'长,对边缘和纹理细节的刻画更稳。如果数据里弱纹理对分类很重要,可以试'db4',但不要盲目加深小波家族阶数,阶数越高边界效应越明显。

第二是level,我一般只用level=1。level每加一,各子带尺寸再减半,图上细小的道路、独立房屋占的面积本来就不大,频域再缩两次基本就变成几个像素了,后续卷积根本提不到可用特征。如果影像很大而目标都是大块地物,level=2可以试,三以上基本不建议。

最后注意边界问题:影像尺寸如果不是2的倍数,wavedec2会报错或产生尺寸不一致,所以先用pad_to_multiple把高宽补齐到偶数。补边方式用reflect而不是constant,避免在影像边缘制造出人为的硬跳变。

小波分解得到的LL和高频子带怎么组织进网络呢?常见做法是LL作为主干输入,因为分类需要的主体结构信息都在里面;高频三张子带作为辅助分支,用少量卷积提取方向纹理后再拼回主干。拼接时子带尺寸比原图小一半,后续上采样恢复分辨率时尽量不要用简单双线性插值,后面避坑章会专门讲。

3.3 平行注意力模块:几十行实现一个能直接抄的模块

平行注意力的PyTorch实现不长,核心是两条并行分支在forward里独立算完权重,再做融合。

import torch import torch.nn as nn class ParallelAttention(nn.Module): def __init__(self, channels, reduction=16, spatial_kernel=7): super().__init__() self.ch_avg = nn.AdaptiveAvgPool2d(1) self.ch_max = nn.AdaptiveMaxPool2d(1) self.ch_fc = nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, bias=False), nn.ReLU(inplace=True), nn.Conv2d(channels // reduction, channels, 1, bias=False), ) self.spatial_conv = nn.Conv2d(2, 1, kernel_size=spatial_kernel, padding=spatial_kernel // 2, bias=False) def forward(self, x): avg_out = self.ch_fc(self.ch_avg(x)) max_out = self.ch_fc(self.ch_max(x)) ca_map = torch.sigmoid(avg_out + max_out) avg_pool = torch.mean(x, dim=1, keepdim=True) max_pool, _ = torch.max(x, dim=1, keepdim=True) sa_in = torch.cat([avg_pool, max_pool], dim=1) sa_map = torch.sigmoid(self.spatial_conv(sa_in)) return x * ca_map + x * sa_map

通道分支复用了一个很常见的技巧:平均池化和最大池化各自通过同一个卷积映射,再加和激活。平均池化给出全局统计,最大池化突出响应最强的通道,两者互补。这里的self.ch_fc用的是1x1卷积实现全连接层效果,好处是不要求输入特征图尺寸固定,换patch大小不用重写。

空间分支对每个空间位置沿通道维度做平均池化和最大池化,把C个通道压成两张单通道图,再在通道方向拼成2通道,过一个7x7卷积生成空间权重。7x7核能照顾到周围一片区域的上下文,比3x3更稳,如果担心参数量太大改成5也可以。

两路并行之后,把通道权重和空间权重分别乘回输入,再加在一起。这种x * ca + x * sa的融合方式保留了两种注意力的独立贡献;如果希望更平滑一些,也可以用x * (0.5 + 0.5 * (ca + sa)),但我实测前一种在语义分割任务里更稳,因为显式相加不会把权重压到接近1的抑制区。

这个模块要接在卷积块后面,一般我按下面这个残差块来组织:

class WaveletAttentionBlock(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_ch) self.conv2 = nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_ch) self.attn = ParallelAttention(out_ch) self.shortcut = (nn.Conv2d(in_ch, out_ch, kernel_size=1, bias=False) if in_ch != out_ch else nn.Identity()) self.relu = nn.ReLU(inplace=True) def forward(self, x): out = self.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out = self.attn(out) return self.relu(out + self.shortcut(x))

注意力放在第二个卷积和残差相加之间。这样设计的原因是:经过两层卷积之后特征图已经混合了光学和SAR两条路的信息,此时做通道和空间筛选,比在刚拼接完的原始特征上做更有效。shortcut负责维度对齐,避免输入输出通道数不一致时加不回去。

到这里,设计源码的主体框架已经清楚了:数据读取后先做小波分解,LL走主干网络,高频子带走辅助分支,两者融合后经过几个带平行注意力的残差块,最后接全局池化和分类头输出类别概率。主干用ResNet34或自定义轻量卷积都可以,平行注意力替换掉原有ResNet block里的瓶颈结构即可。

4. 训练与评估配置:跑通多源分类工程前,先把这几个参数定住

4.1 损失函数怎么选:多源遥感分类不要只用CrossEntropy

常见的遥感分类代码里,默认就是CrossEntropyLoss,训练曲线漂亮但分类报告惨淡。原因是遥感影像里类别分布极度不均:一张大范围土地利用图上,林地或未利用地可能占了60%以上,道路、水体、建筑这些目标类别占比很低。交叉熵对多数类过拟合,精度表面上高,实际对稀少类别没有判别力。

我在多源遥感分类里会换用Focal Loss,它是交叉熵在困难样本上的变形,实现不复杂:

class FocalLoss(nn.Module): def __init__(self, alpha=0.25, gamma=2.0): super().__init__() self.alpha = alpha self.gamma = gamma def forward(self, logits, target): ce = nn.functional.cross_entropy(logits, target, reduction='none') pt = torch.exp(-ce) modulate = (1 - pt) ** self.gamma loss = self.alpha * modulate * ce return loss.mean()

alpha控制整体样本权重,gamma控制难易样本的调节力度。gamma越小,调制越弱;gamma=0时退化成带偏置的交叉熵。类别不平衡严重的项目,可以把alpha按各类别样本比例反比设置,比如多数类是少数类三十倍,就给多数类0.1、少数类0.9。gamma用2.0起步,跑两轮看混淆矩阵再决定要不要加大。

如果做的是像素级分割而不是patch级分类,Dice Loss也可以和Focal Loss按0.5:0.5加权。Dice从区域重叠角度给梯度,Focal从像素难易给梯度,两者配合对细碎地物比单一损失稳得多。

4.2 评价指标:OA、AA和Kappa各干什么

这个标题对应的工程里,汇报用的指标通常有三个:OA、AA和Kappa。很多人只报OA,这对多源遥感来说代表性不足。

指标计算思路适用场景
OA全部分类正确的像元占比类别分布均衡、侧重整体时用
AA各类别正确率的算术平均重点关注稀少类别时更可信
Kappa扣除随机一致后的总体一致性类别不均衡时对比OA更有参考价值

OA好看不代表模型好用,一张图90%是林地,模型全猜林地OA就有90%。AA把每个类别单独算正确率再平均,林地是100%,水体是30%,平均下来才是真实水平。Kappa再扣除“瞎猜也能猜对”的部分,对类别不平衡最稳。

实际评估时我一般把混淆矩阵和这三个指标直接打印,代码很短:

from sklearn.metrics import confusion_matrix, accuracy_score, cohen_kappa_score def report_metrics(y_true, y_pred): cm = confusion_matrix(y_true, y_pred) oa = accuracy_score(y_true, y_pred) aa = (cm.diagonal() / cm.sum(axis=1)).mean() kappa = cohen_kappa_score(y_true, y_pred) return {"OA": oa, "AA": aa, "Kappa": kappa}

注意cm.sum(axis=1)按真实类别统计样本数,如果某个类别在测试集里没有样本,除零会产生nan,所以测试集采样要保证每个类别都有一定量级的样本,一般每类不低于几百个像元。

4.3 关键超参与训练策略:小波分解影响输入尺寸,标签也要跟着对齐

多源模型的训练配置里,我一般按下表起步:

配置项建议值说明
patch_size128或256和影像分辨率、GPU显存一起衡量
batch_size8~16BatchNorm对小batch敏感,至少保持8
optimizerAdamWweight_decay设1e-4~3e-4
lr1e-3(从头训练)/ 3e-4(微调)配合warmup前5个epoch
schedulerCosineAnnealing比StepLR更稳
epochs60~100多源数据量小时加大epoch配增强

有一个点要特别提醒:小波分解做在数据读取端,会造成图像尺寸减半。如果标签还是原tif的像素级标签,就不能直接拿去对LL分支算损失,尺寸对不上。常见处理是改成patch级分类:每个patch是小波分解后的子带块,标签按该patch内部多数类来确定,损失用图块交叉熵。如果坚持做语义分割,标签同样要先downsample,并且downsample时用多数投票而不是双线性插值。

如果主干用了ImageNet预训练权重,前几个epoch最好冻结backbone只训练新加的头,等loss降下来再解冻整个网络。多源输入的通道数和ImageNet的3通道不一致,一种做法是只取光学RGB通道初始化,SAR通道用随机初始化并放在网络最后一组输入通道里。预训练通道对SAR图像没有语义映射,随机初始化部分是合理的,不用硬用某个其他通道来补。

最后强调一个常被忽略的点:多源遥感patch动辄大尺寸,纯float32容易OOM,训练时建议开启混合精度(PyTorch的torch.cuda.amp加GradScaler三行代码搞定)。开启后收敛速度会变,学习率按原来打七八折比较稳。

5. 多源遥感分类避坑:亲测踩过的五个坑

5.1 同一份代码换台机器,结果掉两个点

现象:在一台卡上调好的模型,拿到另一张卡上重训或直接加载权重测试,精度掉了一截,甚至验证集曲线震荡发散。

原因:根子通常在BatchNorm。大显存卡batch可以开到16,换小显存卡batch缩到4,BN的running mean和running var统计口径变了。如果测试时又在不同batch大小下跑,分布差异被放大。多卡训练时还容易遇到每张卡单独算BN统计量的问题。

解决:先把随机种子固定住,包括Python、numpy和PyTorch三个层面;多卡训练时把BatchNorm换成SyncBatchNorm;测试时一定要切到model.eval(),让BN走running统计而不是当前batch统计。记录实验结果时把batch size写进配置文件名,这是基本规范。

5.2 加小波分解后精度不升反降

现象:满怀期待把小波分解做出来,换上去后OA掉了3~5个点,还不如直接堆通道。

原因:结构组织出了偏差。比较常见的是把LL、LH、HL、HH四张子带直接按通道拼在一起喂给同一个卷积网络,等于把所有通道混在一起重新学;高频子带里的SAR噪声和非判别纹理被当成了强特征,模型越训越偏。另一个常见问题是子带尺寸减半后直接用插值上采样还原,丢失了高频结构本身。

解决:LL走主干,高频子带走辅助分支,只在深层特征融合;对高频分支单独加dropout,让网络不要把高频子带当唯一判据;分解只做level=1,尺寸减半对多数patch已经足够。改完后重新跑一轮消融,对比加不加小波的差距,别凭感觉调。

5.3 光学和SAR的标签错位几十米

现象:分类结果的整体指标看着正常,但放大看地物边缘全是锯齿,建筑轮廓对不齐,道路断断续续,边界错误集中。

原因:多源影像没有做严格空间对齐。光学的投影坐标和SAR坐标系如果只是“看起来差不多”,在山区或城区变形会非常明显。标签通常是人工在光学影像上画的,相当于标签坐标系和光学对齐,而SAR影像要映射到同一坐标系里,任何一方投影参数有偏差,边界就是错位的结果。

解决:数据进模型前,用GDAL或QGIS把所有影像重投影到同一UTM坐标、重采样到同一分辨率、按矢量边界裁切。做完后在QGIS里把两种源叠加成半透明图层,肉眼检查道路和桥梁边缘是否重合,这一步比任何代码都可靠。位置不对的先手动校准,别急着往下走。

5.4 训练Loss降到很低,mIoU却上不去

现象:训练曲线loss确实在稳步下降,但验证集mIoU或Kappa卡住,甚至不如随机初始化的小模型。

原因:多源遥感场景类别极度不平衡时,Focal Loss调制不够,模型可以把多数类学得很好,但多数类占loss大头,少数类的误差被淹没了。另一个可能是测试集本身采样不均衡,评估指标被大类别主导。

解决:改用FocalLoss并把alpha按类别频率反比设置;训练时用WeightedRandomSampler对多数类降采样、少数类升采样;评估时坚持看AA和Kappa,并打印混淆矩阵,找到被吞掉的是水体还是道路,再回数据采样层针对性处理。

5.5 SAR输入没做对数变换,第一轮训练就爆数值

现象:loss第一轮直接nan,或者模型很快收敛到全0.5的类别输出,怎么调学习率都没用。

原因:SAR图像原始值是后向散射强度或幅度,动态范围从0到几千甚至几万;光学反射率是0~1的归一化值。SAR没做对数变换,batch里几个强散射体就能把梯度拉爆,梯度在饱和区反复摆动。

解决:在标准化之前先对SAR强度做log1p或转dB处理,dB公式是10 * log10(x + 1e-6),然后再做均值方差标准化;可视化确认转换后直方图接近单峰高斯,而不是长尾分布。这一行预处理决定后面所有训练是否有效,血泪经验。

6. 验证模型是否真的“看了该看的地方”:三个检查方法

模型训完先别急着写论文或交付,做三个验证再下结论。

6.1 把空间注意力权重打回原图

训练完第一件事不是看OA,而是把平行注意力模块里的空间权重图sa_map导出来,归一化后叠加到光学影像上。用一个hook把forward里的sa_map截下来,保存成热力图即可。合理的结果是注意力集中在道路、水系、建筑轮廓这些有语义的边界上;如果热力图散成一团噪点,或者集中到影像角落阴影里,那模型学到的是伪特征而不是地物语义。

6.2 用Grad-CAM看分类决策区域

空间注意力只反映模块内部筛选结果,分类头怎么决策还要看Grad-CAM。代码核心十行左右:

def grad_cam(model, x, target_class): feature = model.backbone(x) logit = model.head(feature)[0, target_class] grad = torch.autograd.grad(logit, feature)[0] weights = grad.mean(dim=(2, 3), keepdim=True) return torch.relu((weights * feature).sum(dim=1, keepdim=True))

我会分别对光学输入、SAR输入、双源融合三种输入跑同一目标类别,对比响应区域。如果融合后响应区比单源更集中,说明多源融合确实在帮模型定位;如果两张源明显冲突,那大概率是数据对齐阶段还有问题。

6.3 跑一张完整的消融表

所有验证手段都比不上一张消融表有说服力。把四组配置各跑三遍,固定随机种子:

配置OAAAKappa
单源光学基线.........
单源SAR基线.........
多源+通道堆叠.........
多源+小波+平行注意力.........

这张表同时回答了两件事:多源比单源涨了多少,小波加平行注意力在多源这条增量里拿了多少。如果后一项没有明显提升,说明卡点还在数据质量上,宁可回头修对齐,也别继续堆模块。我的习惯是每次迭代都把空间注意力图和Grad-CAM存进实验记录,和消融表放在一起。指标可以骗人,可视化骗不了人——注意力在哪儿、什么时候学歪了,一眼就能看出来。希望这些方法能在你的多源遥感分类工程里派上用场,少走我走过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询