多模态遥感目标检测这件事,我前后在三个项目里踩过坑,从最早的“一个模态训一个模型”堆到七八套权重,到后来尝试把可见光、红外、SAR 硬塞进同一个网络,中间返工的时间比写代码的时间长得多。最近看到某高校视觉团队公开的一项工作,主题就是一种用于多模态遥感目标检测的统一模型,思路上和我后期收敛出来的方案高度重合,但它在模态对齐和缺失鲁棒性上做得更彻底。这篇东西我不打算复述论文,而是把这套“统一模型”的思路拆成能直接落地的东西:它到底解决了什么问题、架构为什么这么设计、参数怎么调、训练怎么跑、坑在哪。适合正在做遥感检测工程化、被多套模型折磨过的同学,也适合刚接触多模态融合、想搞清楚“统一”两个字到底统一了什么的新手。
1. 多模态遥感目标检测的真实难点
1.1 异构模态之间的三个“不同”,比想象中麻烦
很多人第一次做多模态遥感,脑子里想的是“把通道拼一拼不就完了”,RGB 三通道加个红外通道变成四通道输入,听起来毫无难度。真跑起来就会发现完全不是一回事。第一个不同是成像机理不同:可见光记录的是反射率,红外记录的是热辐射,SAR 记录的是微波后向散射的幅度和相位,多光谱是多个窄波段,高光谱动辄上百个波段。它们的像素值分布、动态范围、噪声模型全都不一样。可见光是 8bit 归一化到 0-1,热红外常见的 14bit 或 16bit 原始值,直接归一化到 0-1 会让绝大部分像素挤在极小的区间里,网络根本学不动。
第二个不同是空间分辨率不同。同一片区域,全色影像可能是 0.5 米,多光谱 2 米,红外 4 米,SAR 3 米,还带着不同倾角造成的几何畸变。你想让它们在特征图上逐像素对齐,先得做重采样和配准,而重采样本身就会引入模糊和伪影。我做过一次统计,配准误差在 2 个像素以内时,融合还能带来正收益;超过 5 个像素,融合后的精度反而不如单模态,因为跨模态注意力学到的是错位的对应关系。
第三个不同是标注粒度不同。遥感目标检测的标注框通常只在一种模态上标(多数是可见光),其他模态靠共享标注。问题在于,红外图像上热目标可能只有几个亮斑,SAR 图像上目标轮廓完全变形,同一批框在不同模态上的“贴合度”差异巨大。如果你的损失函数对所有模态一视同仁,模型会不自觉地偏向标注最准的那个模态,其他模态退化成噪声。
1.2 一套模态一套模型的工程代价
早期最直觉的做法是每个模态训一个检测器,推理时按可用模态路由。这个方案在小规模验证时效果不错,一旦上生产就原形毕露。权重体积是第一道坎,假设每个模型 100MB,六种模态组合就是 600MB 起步,边缘设备根本放不下。第二道坎是维护成本,主干网络升级一次要重新训六遍,数据增强改一个参数要同步六个配置文件,任何一次实验对比都得跑六组。第三道坎是模态组合爆炸:实际业务里经常出现“今天有可见光没红外、明天红外来了 SAR 断了”的情况,你不可能为每一种组合都训一个专用模型,2 的 N 次方这个数量级很快就失控。
统一模型要解决的核心就是这件事:用一套参数覆盖多模态输入,并且允许推理时模态缺失。这不是一个纯粹的学术问题,是工程上被逼出来的需求。
1.3 统一模型必须回答的四个问题
我在设计自己的方案时列过一张清单,后来看这项工作,基本也是围绕这四个问题展开的。第一,异构输入怎么进同一个网络:通道数不一致、数值分布不一致,入口必须做适配。第二,模态间信息怎么交互:是早期拼接、中期融合还是后期决策融合,各自的代价差别很大。第三,模态缺失怎么办:训练时就得模拟缺失,否则推理时掉点会非常难看。第四,统一之后精度会不会掉:这是最容易被质疑的点,统一往往意味着某种程度的参数共享,共享过头就变成“平均化”,每个模态都学不好。
下面这张表是我整理的常见遥感模态特性对照,做方案选型时可以直接拿来参考,也解释了为什么“统一”这件事天然别扭。
| 模态类型 | 通道数 | 典型动态范围 | 相对分辨率 | 主要优势 | 主要短板 |
|---|---|---|---|---|---|
| 可见光 RGB | 3 | 8bit | 高 | 纹理细节丰富,标注最准 | 受光照、云雾影响大 |
| 近红外 | 1 | 8-16bit | 高 | 夜间可用,植被区分好 | 纹理弱,目标边界模糊 |
| 热红外 | 1 | 14-16bit | 中 | 不依赖光照,人车目标突出 | 分辨率低,热交叉干扰 |
| SAR | 1-4(极化) | 幅度浮点 | 中 | 全天时全天候 | 相干斑噪声,几何畸变 |
| 多光谱 | 4-12 | 12-16bit | 中低 | 材质区分能力强 | 单波段空间细节差 |
| 高光谱 | 100+ | 12-16bit | 低 | 精细物质识别 | 数据量大,冗余严重 |
2. 统一模型的整体架构拆解
2.1 “统一”其实分了三层,别混为一谈
很多讨论把“统一模型”当成一个笼统的概念,实际上它至少包含三层统一,每一层的实现难度和收益完全不同。输入层的统一指所有模态都能进同一个接口,通常做法是模态特定的轻量 stem(比如 3×3 卷积堆叠)把任意通道数映射到固定的 C 维,这一层参数量很小,几乎不增加负担。特征层的统一指不同模态的特征在同一个语义空间里可比,这是最难的部分,需要共享主干加显式的对齐约束。任务层的统一指检测头共享,所有模态输出同一套框和类别。
我见过一些方案只在输入层做统一,主干和头部共享,看起来是“一个模型”,实际上模态特征在主干里各走各的,融合只发生在最末端的拼接上。这种做法在小数据量下能work,但跨模态的互补信息基本没被利用起来。真正有价值的统一发生在特征层,也就是让网络在中间某几层就开始交换信息。
2.2 为什么是“共享主干 + 轻量模态适配”,而不是多专家集成
多专家(MoE)路线的诱惑力很大:每个模态配一个专家网络,用门控选择,理论上各模态都能保持自己的专长。但放到遥感检测这个场景,我不推荐,原因有三。其一,专家路线的参数量随模态数线性增长,六模态就是六份主干,端侧部署基本没戏。其二,门控网络需要足够的样本来学,而多模态遥感数据集普遍偏小,几千到几万张的量级,门控训不收敛会导致模态路由错误,比不融合还差。其三,标注共享的前提下,各模态的监督信号本来就是同一套框,让它们各自发展出完全独立的表示,反而丢失了天然的对应关系。
“共享主干 + 轻量模态适配”的逻辑正好相反:主体参数学习模态无关的通用表示(边缘、角点、目标形状),适配器只负责把模态特有的分布差异校正过来。举一个生活化的类比,主干像是一个识图能力很强的老师傅,适配器像是给不同来源的照片配的滤镜,照片本身的光照色温不一样,但老师傅看的还是形状和结构。这个设计的参数增量通常在主干参数量的 5% 到 10%,完全可控。
还有一个隐性好处:共享主干天然带来了跨模态的正则化效果。某个模态样本少的时候,它的梯度会通过共享参数被其他模态“带一把”,相当于一种隐式的多任务学习。我在一个小样本红外数据集上验证过,共享主干的方案比独立训练的模型高出 4 个点左右的 AP50,样本越少,优势越明显。
2.3 模态缺失与模态不齐整,必须写进设计前提
这里我要强调一个在实际项目里被严重低估的点:训练时就必须假设模态会缺失。做遥感数据采集的人都清楚,同一区域的多模态影像很难完全同时获取,云层遮挡导致可见光失效、卫星过境时间不同导致红外和 SAR 差好几天,这些都是常态。如果你的模型只在“全模态”数据上训练,推理时缺一路就崩,那这套系统在真实场景里毫无价值。
常见的处理手段是模态随机丢弃(modality dropout):每个训练样本以一定概率把某一路模态置零或者替换成可学习的空模态 token,让网络学会在信息不全的情况下做判断。这里有个细节很容易做错——置零不能简单地把输入张量填 0,因为填 0 在归一化之后对应的是某个具体的像素值,网络会把它当成真实信号。正确做法是维护一个“模态可用性掩码”,在融合模块里把不可用模态的注意力权重直接屏蔽掉,同时用可学习的空模态嵌入占位。
3. 核心模块的实现细节与参数选择
3.1 模态统一编码:把异构通道变成可对齐的特征
入口模块的设计我建议遵循“先独立、再共享”的顺序。具体来说,每个模态先过一个独立的轻量 stem,把通道数统一到 C0(常用 32 或 64),这个 stem 只做 2 到 3 层卷积,不做下采样,目的是吸收模态间的数值分布差异;然后所有模态汇入共享主干。stem 太深会削弱共享主干的作用,太浅又压不住分布差异,我试下来的甜点区是两层 3×3 卷积加一层 1×1 调整通道。
归一化策略是这一步的关键。遥感数据的动态范围差异极大,我通常不用固定的均值方差,而是用逐模态的分位数归一化:统计该模态训练集 2% 和 98% 分位点,线性拉伸到 0-1,再减均值除标准差。这比直接用 ImageNet 统计量靠谱得多,尤其是红外和 SAR,用 ImageNet 的均值方差会导致大量像素饱和。
下面这段是我实际项目里用的入口模块结构,PyTorch 写法,通道数、归一化都做了模态区分,可以直接改成你要的形式。
import torch import torch.nn as nn class ModalityStem(nn.Module): """每个模态一个轻量入口,只负责压分布差异,不负责提特征""" def __init__(self, in_ch, out_ch=64): super().__init__() self.net = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, stride=1, padding=1, bias=False), nn.BatchNorm2d(out_ch), nn.SiLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, stride=1, padding=1, bias=False), nn.BatchNorm2d(out_ch), nn.SiLU(inplace=True), ) def forward(self, x): return self.net(x) class UnifiedInputAdapter(nn.Module): """异构模态统一入口 + 可用性掩码""" def __init__(self, modality_channels, out_ch=64): super().__init__() self.stems = nn.ModuleDict({ name: ModalityStem(ch, out_ch) for name, ch in modality_channels.items() }) # 可学习的空模态嵌入,模态缺失时用它占位 self.null_token = nn.Parameter(torch.zeros(1, out_ch, 1, 1)) def forward(self, batch): feats, masks = [], [] for name, stem in self.stems.items(): if name in batch and batch[name] is not None: f = stem(batch[name]) m = torch.ones(f.shape[0], 1, 1, 1, device=f.device) else: ref = next(iter(batch.values())) f = self.null_token.expand(ref.shape[0], -1, ref.shape[2] // 4, ref.shape[3] // 4) m = torch.zeros(ref.shape[0], 1, 1, 1, device=ref.device) feats.append(f) masks.append(m) return torch.stack(feats, dim=1), torch.stack(masks, dim=1) # [B, M, C, H, W]注意:空模态嵌入不要初始化为全零后直接冻结,冻结的话它永远学不到有效表示,梯度也传不进去。我的做法是初始化为零但保持可训练,配合模态丢弃概率从 0.1 逐步升到 0.3,让网络有个适应过程。
3.2 跨模态交互:门控、注意力还是稀疏化,怎么选
融合模块是整个模型的心脏,选型上我走过两条弯路。第一条是直接做全连接的跨模态注意力,M 个模态两两交互,复杂度 O(M²),六模态就是 15 对,显存直接爆掉,而且小目标特征图分辨率高,注意力的计算量完全不可接受。第二条是简单加权求和,每个模态学一个标量权重,这个方案极其轻量,但表达力太弱,遇到“某区域可见光失效但 SAR 有效”这种空间不均匀的情况就无能为力。
比较靠谱的折中是通道分组 + 空间门控的混合方案:先在通道维度上分成若干组,组内做跨模态的轻量交互(1×1 卷积实现,成本很低),再在空间维度上用门控图决定每个位置该信哪个模态。门控图的生成可以用各模态特征图上的全局平均池化加一层卷积,输出 M 个通道的权重图,softmax 归一化后加权融合。这样既能处理空间不均匀,又把复杂度压到了 O(M)。
下面是我用的门控融合核心代码,逻辑很直白:算权重、屏蔽不可用模态、加权求和。
class GatedFusion(nn.Module): def __init__(self, channels, num_modalities, reduction=4): super().__init__() hidden = max(channels // reduction, 16) self.gate = nn.Sequential( nn.Conv2d(channels * num_modalities, hidden, 1, bias=False), nn.BatchNorm2d(hidden), nn.SiLU(inplace=True), nn.Conv2d(hidden, num_modalities, 1, bias=True), ) def forward(self, feats, masks): # feats: [B, M, C, H, W] masks: [B, M, 1, 1, 1] B, M, C, H, W = feats.shape x = feats.reshape(B, M * C, H, W) logits = self.gate(x) # [B, M, H, W] logits = logits.masked_fill(masks.squeeze(-1).squeeze(-1) < 0.5, -1e4) weight = torch.softmax(logits, dim=1).unsqueeze(2) # [B, M, 1, H, W] return (feats * weight).sum(dim=1)实际调参时有几个经验值可以抄:门控的 reduction 用 4,hidden 最小 16;softmax 之前一定做掩码填充,填充值用 -1e4 而不是 -inf,避免全模态缺失时出现 NaN;融合层数不要太深,我在主干的两个 stage 后各插一次融合,再多就没有正收益了。
3.3 检测头与损失:小目标密集排布怎么处理
遥感检测的老大难是小目标。1024×1024 的输入,一辆车可能只有 10 到 15 个像素,主干下采样 32 倍之后特征图上就剩半个像素,信息早就没了。我的处理是把融合后的特征接到一个 stride 为 4 的浅层分支上,专门负责小目标,检测头在 P2、P3、P4 三个层级上做预测,而不是常规的 P3、P4、P5。代价是计算量增加约 30%,显存占用增加 40%,但小目标的召回能提升十几个点,这个交换我认为非常值。
损失这块,统一模型有个特殊要求:单模态监督。也就是说,当某个样本只有可见光时,损失只在可见光分支上计算,不能把缺失模态的空输出也算进去。实现上就是把每张图的可用掩码乘到损失项上,做加权平均而不是简单求平均。另外我建议加一个模态一致性损失,约束同一目标在不同模态下的分类 logits 不要太离谱,用 KL 散度或者余弦相似度都行,系数取 0.05 到 0.1,太大会压制各模态的特异性。
还有一个细节:正样本分配策略。遥感目标密集排布,用固定 IoU 阈值分配正样本会导致某些目标分配不到,我倾向用动态分配(比如按 top-k 代价选正样本)。这类细节在论文里往往一笔带过,但实测对最终 mAP 的影响在 1 到 2 个点,值得花时间调。
4. 完整训练流程与关键参数跑通记录
4.1 数据准备:配对采样比数据增强更重要
多模态数据的第一道工序是配对。同一区域的可见光和红外最好按地理坐标和时间窗口做关联,时间窗口我一般设成同一天,跨度超过 24 小时的话地表温度和光照变化会影响融合效果。配不上的样本不要直接丢,可以保留成单模态样本参与训练,正好给模态缺失提供真实数据。
采样器的设计上,我建议用模态平衡采样:每个 batch 里保证各模态至少出现一定比例,同时按概率注入模态缺失样本。比如 batch size 16,其中 10 个全模态、3 个缺一到两路、3 个单模态,这样训练出的模型对缺失最鲁棒。这个配比我试过 8/4/4 和 12/2/2,最后选了 10/3/3,缺失比例再高会拖累全模态下的精度。
数据增强要注意模态一致性:水平翻转、旋转这类几何变换,所有模态必须用完全相同的参数;而色彩抖动、亮度调整这类光度变换,只能作用在可见光上,对红外和 SAR 用会导致分布失真。这一点非常容易出错,我在早期版本里就因为对 SAR 做了色彩抖动,导致模型收敛异常缓慢,排查了两天才发现问题。
4.2 训练脚本与核心配置
下面是我跑通的一套基础配置,输入 1024×1024,主干用 ViT 类的轻量变体,检测头用 DETR 风格。学习率按 batch size 线性缩放,warmup 用 500 步,优化器 AdamW,权重衰减 0.05。
python train.py \ --config configs/unified_remote_sensing.yaml \ --modalities rgb ir sar ms \ --img-size 1024 \ --batch-size 8 \ --epochs 36 \ --lr 1e-4 \ --lr-backbone 2e-5 \ --warmup-iters 500 \ --weight-decay 0.05 \ --modality-dropout 0.25 \ --gap 0.1 \ --num-gpus 4 \ --amp \ --output work_dirs/unified_v1几个参数要单独说明。--lr-backbone比主学习率小 5 倍,这是微调预训练主干的常规操作,能防止预训练权重被早期大梯度破坏。--modality-dropout 0.25是模态丢弃的整体概率,具体到底层是每个模态独立以 0.15 概率丢弃,再以 0.1 概率丢弃两路。--gap是梯度累积间隔,多模态模型显存占用大,单卡 batch 8 已经是极限,用梯度累积把等效 batch 拉到 32,BN 层的统计才比较稳定。
显存占用我实测过一组数:1024×1024 输入、ViT-S 级别主干、四模态,单卡 24GB 显存,batch 8 加 AMP 大概占 21GB,几乎顶满。如果显存不够,优先降输入尺寸到 800,或者把浅层的小目标分支关掉,比降 batch 更稳妥。
训练过程中必须盯住三个曲线:各模态的损失是否同步下降、门控权重的熵是否稳定、验证集上单模态和全模态的指标差距。如果某模态损失长期不降,大概率是归一化参数配错了;如果门控熵一路趋近于 0,说明网络退化成只用某一个模态,需要调大模态丢弃概率或者加一致性损失。
4.3 推理与部署侧的落地考量
统一模型在部署上的优势很明显:一套权重、一个引擎文件、一次推理,不需要为模态组合写路由逻辑。但有几个工程细节必须提前规划。动态输入是第一个,不同模态的分辨率不一样,推理时要统一到同一尺寸,我一般在预处理阶段做双线性上采样到主干输入尺寸,不追求严格对齐,因为配准误差本来就存在,过度对齐反而引入伪影。
切片推理是第二个。遥感图像动辄上万像素,整图缩放到 1024 会让小目标彻底消失,必须切成重叠瓦片分别推理。瓦片尺寸建议 1024,重叠 200 像素,重叠区只保留中心区域的结果,避免边界截断导致的漏检。切片数量和重叠率会直接影响推理耗时,实测 10000×10000 的图像,切 1024 瓦片、重叠 200,大概产生 144 个瓦片,单卡 T4 上耗时约 40 秒,这个量级在离线批处理场景完全可接受。
量化是第三个。我做过 FP16 和 INT8 的对比,FP16 基本无损,速度提升约 1.8 倍;INT8 在可见光上掉点不明显,但在红外和 SAR 上掉点明显,尤其 SAR 的相干斑噪声对量化误差很敏感。如果你的部署场景里 SAR 权重较大,建议对 SAR 分支保留 FP16,其他分支走 INT8,做混合精度。
5. 消融实验怎么设计才有说服力
5.1 四组必做的消融,缺一组就会被质疑
统一模型的论文里,消融实验是最容易被审稿人挑刺的地方,因为“统一”带来的收益很容易被其他因素混淆。我总结下来有四组是必须做的。第一组,共享主干 vs 独立主干,参数量对齐的情况下比较,证明共享本身有效而不只是参数多了。第二组,融合模块的位置和数量,在主干不同 stage 后插入融合,给出 1 次、2 次、3 次的对比。第三组,模态丢弃的有效性,训练时开与不开,在推理阶段分别测全模态和缺模态两种设置下的指标。第四组,入口 stem 的必要性,去掉模态独立 stem 直接拼接通道进主干,看掉多少点。
第三组是我认为最有价值的一组,因为它直接对应真实场景。表格里应该同时报告“全模态推理”和“随机缺模态推理”两列,很多工作只报前者,实际上后者才决定这套系统能不能用。我在自己项目里的经验是,开了模态丢弃之后,全模态精度可能掉 0.5 个点,但缺模态精度能涨 8 到 12 个点,这个交换非常划算。
| 消融项 | 全模态 AP50 | 缺一路 AP50 | 参数量增量 | 结论 |
|---|---|---|---|---|
| 独立主干(基线) | 基准值 | 不可用 | 100% | 无缺失能力 |
| 共享主干,无融合 | -2.1 | -6.5 | +2% | 共享有正则效果 |
| 单次融合(后段) | +0.8 | +2.3 | +5% | 融合位置偏后收益有限 |
| 两次融合(中后段) | +2.4 | +5.1 | +8% | 甜点配置 |
| 三次融合 | +2.5 | +5.3 | +13% | 收益递减 |
| 加模态丢弃 | -0.5 | +10.8 | 0 | 鲁棒性关键 |
5.2 评测指标的陷阱:别只看 mAP
遥感检测领域大家习惯报 AP50,但统一模型场景下,只看 mAP 会漏掉关键问题。我建议至少再补三个指标。跨模态一致性:同一张图用不同模态输入,检测结果的重合度,这个指标能暴露融合模块是否真的在融合,还是一直在依赖某一路。模态敏感度:把所有模态逐一屏蔽,看指标下降曲线,曲线越平说明鲁棒性越好。小目标分档指标:按目标像素面积分档统计 AP,因为统一模型最容易在小目标上出问题,平均指标会掩盖这一点。
还有一个容易忽略的评测设置:训练未见过的模态组合。比如训练时只用过 RGB+IR 和 RGB+SAR,测试时给 RGB+IR+SAR 三模态,看模型能不能泛化。这个设置在真实场景里很常见(新卫星上线多了一路数据),很多模型在这里会直接崩掉。
6. 常见问题与排查实录
6.1 模态跷跷板:为什么总有一个模态学不动
这是统一模型里最高频的问题,表现是训练日志里某个模态的损失长期高于其他模态,收敛慢,最终该模态单独推理时精度很差。根因通常有三个。样本量失衡:某个模态数据量只有其他模态的十分之一,梯度被淹没。解决办法是模态加权采样,给稀有模态更高的采样概率,或者用重采样把各模态的样本量拉平。归一化参数错误:我在一个项目里因为 SAR 的幅度值没做对数变换,直接线性归一化导致极值主导,整个 SAR 分支几乎是死的,改成 10log10 变换后立刻恢复。梯度尺度差异:不同模态的损失量级不一样,共享主干时大的压小的,可以给每个模态的损失乘一个自适应权重,用不确定性加权或者 GradNorm 都行。
排查顺序我一般是这样的:先单独打印每个模态的输入统计量(均值、方差、分位数),再单独跑一次只含该模态的前向,看特征图有没有大面积死区,最后看该模态的梯度范数。这三步能定位 90% 的问题。
6.2 常见问题速查表
| 现象 | 最可能的原因 | 处理方式 |
|---|---|---|
| 某模态损失不降 | 归一化参数错或用错增强 | 检查分位数归一化,确认光度增强未作用于该模态 |
| 门控熵趋近 0 | 网络退化为单模态 | 提高模态丢弃概率,加跨模态一致性损失 |
| 全模态精度正常,缺模态崩 | 未做模态缺失训练 | 开启模态丢弃,加入空模态嵌入 |
| 小目标召回低 | 特征图分辨率不足 | 加 stride=4 分支,切片推理,重叠裁剪 |
| 训练 loss 正常,验证掉点 | 过拟合或数据泄漏 | 检查配对是否跨区域,加几何增强 |
| 融合后精度还不如单模态 | 配准误差过大 | 统计配准误差,超过 5 像素改用决策级融合 |
| SAR 分支输出全黑 | 幅度值未做对数变换 | 用 10log10 压缩动态范围后再归一化 |
6.3 踩过的坑和几句实在话
说几个文档里基本不会写、但实际会浪费你很多时间的点。第一,多模态数据的时间戳一定要核对。我遇到过一次,红外和可见光的文件命名规则一样,但采集日期差了一个月,模型在训练集上表现很好,一到测试就崩,查了三天才发现是数据配对错了。后来我强制在数据加载器里加了一条断言,检查同一组数据的采集时间差是否超过 24 小时,超过就报警。
第二,融合模块的初始化很关键。门控的最后那个 1×1 卷积如果随机初始化,训练初期各模态权重几乎是均分的,相当于给噪声较大的模态也分了一半权重,容易导致早期收敛困难。我后来把它初始化为偏置项接近 0、权重很小的状态,softmax 之后接近均匀但梯度温和,收敛稳定性明显提升。
第三,别迷信更复杂的融合。我试过用 Transformer 的 cross-attention 做模态融合,在小数据集上的表现反而不如简单的门控加权,原因是注意力需要大量数据才能学好,遥感数据集普遍不到一万张图,过拟合严重。如果你手上数据量在五千张以下,老老实实用轻量门控,把精力放在数据增强和损失设计上,收益更高。
第四,推理时的确定性。多模态模型在模态缺失时会走空模态嵌入分支,如果你的部署环境里某些样本的缺失是偶发的(比如某天云层遮挡导致可见光不可用),要保证这种“偶发缺失”和训练时的“人工缺失”走完全相同的路径,否则特征分布对不上,精度会莫名下降。我的做法是把缺失判断统一放到预处理阶段,输出一个标准化的掩码,训练和推理共用同一份数据管道代码。
最后分享一个小技巧。统一模型最难调的是模态间的平衡,与其一个个手动调损失权重,不如先跑一次“模态单独训练”的对照组,拿到每个模态在独立模型下的收敛损失量级,再按这个量级反推共享模型里的权重系数。这个方法不优雅,但非常有效,我在两个项目里都是靠它把模态跷跷板压下去的。