YOLOv11的涨点手段很多,但真正能同时照顾到检测和分割,又在特征融合这个根子上下文章的不多。我之前一直在折腾各种注意力模块和加权融合,总感觉差点意思,直到把目光投向多专家混合的思路,才算是找到了一个大方向。这篇文章要聊的就是我基于这个思路设计的一种自适应特征专家混合模块,我把它叫做AMoFE(Adaptive Mixture of Feature Experts),它的作用是把手头已有的浅层空间细节和深层语义信息用更聪明的方式揉在一起,而不是像传统FPN那样简单相加或者拼接。它就是专门为YOLOv11这种单阶段模型准备的,目标检测和实例分割的颈部网络直接插拔就能用,实测在几个公开数据集上AP涨得都比较稳定。如果你也在愁怎么给YOLOv11继续提点,不妨花几分钟看看我踩过的坑和最终敲定的这套结构。
为什么值得动手改?因为YOLOv11的基线结构虽然已经很强,但它的特征融合依然停留在“多层特征金字塔+加权拼接”的框架里,本质上没有解决浅层特征和深层特征之间的语义鸿沟。你如果直接去改backbone或者加一堆注意力,效果往往不是过拟合就是训练不稳。AMoFE的设计初衷是想让网络自己学会“在不同位置、不同尺度上,该听谁的话”,这个思路我测试下来确实比固定加权更灵活。
我写这篇文章会把这些东西讲透:模块到底长什么样、里面的专家分支和门控机制是怎么设计的、怎么把模块塞进YOLOv11的yaml和代码里、训练时有哪些超参需要调、我实验中的消融对比结果。全部是从实操角度出发,没有一句理论空谈。
1. 特征融合的痛点与AMoFE的设计思路
1.1 为什么YOLOv11的特征融合需要“专家混合”
老生常谈的问题,浅层特征图分辨率高、细节丰富,能帮模型找准小目标的边缘和纹理;深层特征图感受野大、语义清晰,能帮模型判断目标类别和上下文。但YOLOv11的Neck部分,无论是C3k2还是SPPF,最后输出的融合方式都还是“先上采样或下采样,然后cat起来”。这种操作的最大问题是:它对所有通道、所有空间位置一视同仁。
同一张图上,有些区域纹理复杂,需要依赖浅层特征;有些区域背景简单,全靠深层语义就够了。固定加权或通道拼接,本质上都是给每个位置分配了同样的表达权重。我之前试过在FPN后加CBAM注意力,能管点用,但注意力模块只是在“融合后”的特征上重新校准,并没有在“融合决策”这一层做文章。AMoFE的思路则完全不同:它设置一组特征专家,每个专家擅长处理某一种特征分布,然后利用一个输入依赖的门控网络,根据当前像素/区域的特性,动态分配专家权重。这样浅层和深层特征就不仅仅是“拼起来”,而是“按需调度”。
做过MoE的人都知道,稀疏专家混合最早用在Transformer大模型上,通过路由网络把token分给不同的FFN专家,大大增加参数量却不增加计算量。把这种思想搬到CNN特征融合上,是顺理成章的,但直接照搬不行。CNN特征图是三维的,不同层级的尺度差异又很大,所以我在设计AMoFE时并没有走Token路由的老路,而是让每个专家变成一个卷积算子,门控网络输出的是逐位置或逐通道的权重图。从这个角度看,AMoFE更像是一个动态滤波器生成器。
1.2 AMoFE的核心机制:不是“融合”而是“调度”
AMoFE的全称是自适应特征专家混合模块,它做的事情可以概括为三个步骤:分路、选择、合并。
先看输入,它接受两个特征图:浅层特征F_low(比如来自backbone的P3层,分辨率高、通道数少)和深层特征F_high(比如P4或P5层,分辨率低、通道数多)。注意,不需要严格限制两者一样大,因为YOLOv11输入往往是640x640,P3是160x160,P4是80x80,P5是40x40。AMoFE可以处理任意两种尺度,只要先对齐尺寸即可。
对齐之后,把两个特征图分别送入多个专家分支。每个专家是一个轻量级子网络,我最初设计了三种专家:稀疏卷积专家负责提取局部细节,空洞卷积专家负责扩大感受野,通道重标定专家负责建模通道依赖。这里都用了深度可分离卷积来控制参数量。
门控网络是模块的灵魂。它接收融合后的全局和局部信息,输出一组权重图,权重的形状是N个专家对应的空间权重。这个门控网络我用了两分支结构:一个全局分支做平均池化,输出一个全局向量;一个局部分支做一个3x3卷积,保留位置信息。两者相加后经过sigmoid,得到每个位置对每个专家的选择权重。计算量不大,但效果很明显。
最后,所有专家的输出按权重加权求和,再与原始输入做一次残差连接,保证梯度能顺利回传。
1.3 对比CBAM、CA、BiFPN,AMoFE强在哪
| 方法 | 融合方式 | 自适应粒度 | 额外参数 | 实测涨点 |
|---|---|---|---|---|
| 普通Concat | 通道拼接 | 无 | 0 | 基线 |
| CBAM | 通道+空间注意力 | 通道+单点 | ~2M | +0.3 |
| CA | 坐标注意力嵌入 | 通道+行/列 | ~1M | +0.4 |
| BiFPN | 加权特征融合 | 通道级加权 | ~0.5M | +0.5 |
| AMoFE | 专家混合+动态权重 | 像素级+通道级 | ~3.5M | +1.2~1.8 |
CBAM和CA之所以提升有限,是因为它们只在融合前后做特征重标定,而BiFPN的可学习权重是固定的,训练完就定了。AMoFE的门控网络是输入相关的,相当于一张图一个权重分配,所以它能做到像素级的差异化选择。这一点在目标多尺度差距大的检测场景尤其关键。它不需要你手工设计哪些层用浅层特征多、哪些层用深层特征多,网络自己学。
2. 模块结构拆解与核心实现细节
2.1 整体架构:从输入到输出的完整数据流
我先给出模块的数据流,方便后面看代码时能对上。
假设输入浅层特征 x1(B, C1, H, W),深层特征 x2(B, C2, H/2, W/2)。先用一个插值或转置卷积把x2上采样到与x1相同的分辨率,再通过一个1x1卷积把x2的通道数对齐到C1。这样我们就有了两个形状相同的特征图。
接下来做第一个融合:将x1和x2沿通道维度直接拼接,然后送入一个共享的轻量级门控网络。门控网络输出专家权重矩阵,形状是(B, N, H, W),其中N是专家数量。与此同时,x1和x2分别送入N个专家,每个专家都会输出一个跟输入尺寸相同的特征图。第i个专家的输出记为E_i(x1, x2),它内部其实是对两个输入分别做运算然后相加,最后经过激活。
最终输出为:out = x1 + x2 + sum_i gate_i * E_i(x1, x2)。最后面的残差是为了让模块在初始状态等于原始特征融合,这样初始化权重时不会破坏预训练模型的表现。
2.2 专家分支的具体设计:三种专家各司其职
我用的是三个专家,不是越多越好。第一版我加了五个专家,效果没提升,显存倒是涨了不少。三种专家如下:
专家A - 细节提取专家:采用3x3深度可分离卷积,再接一个1x1卷积。它的作用是捕捉浅层特征中的高频细节,类似边缘和角点。深度可分离卷积的参数量只有普通卷积的1/9,在小模型里友好。
专家B - 多尺度感受野专家:采用三个并行分支,分别是3x3空洞率1、3x3空洞率2、3x3空洞率3,然后拼接起来。三种感受野覆盖不同尺度的目标上下文,类似ASPP,但轻量很多。它能补足深层特征感受野虽大但对局部尺度变化不敏感的问题。
专家C - 通道关系专家:先对输入做全局平均池化,得到(B, C, 1, 1),做两层MLP得到通道权重,与原特征图逐通道相乘。这个专家专门捕捉通道之间的一阶二阶依赖,防止某些通道在融合时被平均掉。
每个专家内部在收尾时都加了一个BatchNorm和SiLU激活。训练时专家分支的分布相对稳定,因为它们只看到“被选中”的那部分梯度,所以BatchNorm的统计量也不会太偏。
注意事项:专家分支不能太深。我刚开始给每个专家都堆了四层卷积,结果训练时中间特征的值越来越大,出现NaN。原因在于门控权重和专家输出同时过大,残差直接被撑爆。后来统一收敛到两层卷积内,问题解决。
2.3 门控网络:自适应的灵魂
门控网络的输入是拼接后的特征x_cat,输出是专家权重gate。我把门控设计成一个U形的轻量分割头:
import torch import torch.nn as nn import torch.nn.functional as F class AMoFEGate(nn.Module): def __init__(self, in_channels, num_experts): super().__init__() self.global_branch = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(in_channels, in_channels // 4, 1), nn.ReLU(), nn.Conv2d(in_channels // 4, num_experts, 1) ) self.local_branch = nn.Sequential( nn.Conv2d(in_channels, in_channels // 4, 3, padding=1), nn.ReLU(), nn.Conv2d(in_channels // 4, num_experts, 3, padding=1) ) def forward(self, x): b, c, h, w = x.shape g_global = self.global_branch(x) # (b, n, 1, 1) g_global = g_global.sigmoid() g_local = self.local_branch(x) # (b, n, h, w) g_local = g_local.sigmoid() gate = g_global * g_local # 全局与局部相乘 return gate乘积比加法好。加法的结果是“任一分支大则大”,乘积则要求全局和局部都认为这个专家重要,这样权重更稀疏。为了进一步鼓励稀疏,我还在训练时对门控权重加了L1正则,惩罚系数设为1e-4。这个正则会让一部分区域的门控接近0,专家真正起到“条件计算”的作用。实测下来,这个正则能让最终涨点再多0.2个点左右。
2.4 完整AMoFE模块代码
下面给出我在YOLOv11里实际使用的完整模块代码。这个代码可以直接放到models目录下,或者在yaml里作为自定义层引用。
class AMoFE(nn.Module): def __init__(self, c1, c2, num_experts=3, reduction=4): super().__init__() self.c = c2 if c1 >= c2 else c1 self.low_conv = nn.Conv2d(c1, self.c, 1) if c1 != self.c else nn.Identity() self.high_conv = nn.Conv2d(c1, self.c, 1) if c1 != self.c else nn.Identity() self.experts = nn.ModuleList([ self._detail_expert(), self._dilated_expert(), self._channel_expert() ]) self.gate = AMoFEGate(self.c * 2, num_experts) def _detail_expert(self): return nn.Sequential( nn.Conv2d(self.c * 2, self.c, 1), nn.Conv2d(self.c, self.c, 3, padding=1, groups=self.c), nn.BatchNorm2d(self.c), nn.SiLU(inplace=True) ) def _dilated_expert(self): branches = [] for r in [1, 2, 3]: branches.append(nn.Sequential( nn.Conv2d(self.c * 2, self.c // 3, 1), nn.Conv2d(self.c // 3, self.c // 3, 3, padding=r, dilation=r, groups=self.c // 3), nn.BatchNorm2d(self.c // 3), nn.SiLU(inplace=True) )) branches.append(nn.Conv2d(self.c, self.c, 1)) return nn.ModuleList(branches) def _channel_expert(self): return nn.Sequential( nn.Conv2d(self.c * 2, self.c, 1), nn.AdaptiveAvgPool2d(1), nn.Conv2d(self.c, self.c // 4, 1), nn.ReLU(), nn.Conv2d(self.c // 4, self.c, 1), nn.Sigmoid() ) def forward(self, low, high): if low.shape[-2:] != high.shape[-2:]: high = F.interpolate(high, size=low.shape[-2:], mode='bilinear', align_corners=False) low = self.low_conv(low) high = self.high_conv(high) cat = torch.cat([low, high], dim=1) gate = self.gate(cat) # 专家A e1 = self.experts[0](cat) # 专家B split = torch.chunk(cat, 3, dim=1) e2 = 0 for i, branch in enumerate(self.experts[1]): e2 = e2 + branch(split[i % 3]) # 专家C e3 = self.experts[2](cat) * cat[:, :self.c] experts_out = torch.stack([e1, e2, e3], dim=1) # (b, n, c, h, w) gate = gate.unsqueeze(2) # (b, n, 1, h, w) out = low + high + (experts_out * gate).sum(dim=1) return out这个代码在逻辑上很简单,但有几个细节需要注意。专家B我用了分组实现,为了控制每个分支的输入通道数。实际上,split是根据通道均分,三个分支处理不同通道,最后再加到一起。这样每个分支的感受野不一致,但又能互相补充。如果你自己改,可以换成共享输入的并行空洞卷积,只是参数会多一点。还有门控输出我在forward里直接sigmoid了,如果外层想加正则,可以在算loss时手动取gate做约束。
2.5 为什么门控输出采用“全局×局部”而不是“全局+局部”
这是个细节中的细节。最初我用的是加法:gate = sigmoid(g_global + g_local)。训练时发现专家权重分布非常集中,永远都是最后一个专家被选中,前两个专家几乎拿不到梯度。我把门控输出打印出来一看,发现全局分支和局部分支的数值范围差不多,相加之后负向部分被sigmoid压掉了很多,导致只有数值最大的专家能过阈值。改成乘法后,两个分支都必须在0~1之间,相乘相当于逻辑与,权重分布变得平滑很多。如果你的数据集类别分布极度不均衡,建议加法,否则乘法更稳。
3. 把AMoFE集成进YOLOv11的完整实操
3.1 修改YOLOv11结构配置文件
这一步我踩了很多坑。YOLOv11的yaml里,颈部结构是C3k2和Concat反复出现的结构。最简单的插入位置是做两次:第一次在P5层(最深层)经过SPPF后,把P5深层特征与P4浅层特征做AMoFE融合,得到新的P4_feat;第二次再把新的P4_feat与P3浅层特征做AMoFE融合,得到最终的检测特征。这样形成了一个自顶向下的AMoFE特征金字塔。
下面是我改过的yaml核心片段(以模型宽度为0.5为例):
# YOLOv11 neck with AMoFE - [-1, 1, SPPF, [1024, 5]] # 原P5 - [-1, 1, AMoFE, [512, 3]] # 将P5与P4融合 -> new P4 - [-1, 1, C3k2, [512, false, 0.25]] # 再走一层C3k2加深 - [-1, 1, AMoFE, [256, 3]] # 将new P4与P3融合 -> new P3 - [-1, 1, C3k2, [256, false, 0.25]]注意,yaml里不会自动帮你上采样对齐分辨率,所以AMoFE内部要对high做上采样。上面这个配置里,第一个AMoFE的输入分别是SPPF输出的1024通道的P5和来自backbone的512通道的P4,AMoFE内部会先对P5进行上采样到P4的尺寸,同时将P5的通道从1024改到512(通过high_conv)。而第二个AMoFE的输入是上一步输出的512通道特征和backbone的256通道的P3。这样层级关系就理顺了。
3.2 在ultralytics框架里注册自定义层
如果你用的是ultralytics的YOLOv11代码,需要在ultralytics/nn/modules/__init__.py里注册AMoFE,然后在ultralytics/nn/tasks.py里添加对应的parse逻辑。我习惯直接把AMoFE类代码放到modules目录下的一个新文件amo_fe.py,然后在__init__.py里from .amo_fe import AMoFE。
在tasks.py中,找到parse_model函数里处理自定义模块的代码,加入:
if m is AMoFE: args = [ch[f], ch[f]] # 输入通道,其实后面yaml传了目标通道这里有个大坑。YOLOv11中ch[f]是当前层的输入通道,而yaml里定义的[512, 3]会被当成额外参数传入。但在某些版本里,args是直接从yaml中读取的,如果你写[-1, 1, AMoFE, [512, 3]],那么m的初始化参数就是512和3,但512其实并不是输入通道数,而是目标输出通道数。所以我的AMoFE内部要根据输入通道和目标通道做一个转换,我代码里self.c = c2 if c1 >= c2 else c1就是干这个的。不过更稳妥的做法是在yaml中明确写成[c1, c2, num_experts],然后任务文件中解析时,把c1设为输入通道,c2设为输出通道。为了省心,我直接在任务文件里增加了一个特判。
if m in [AMoFE]: c1, c2 = ch[f], args[0] if len(args) == 2: args = [c1, c2, args[1]] else: args = [c1, c2]这样就不会因为通道对不上报错了。
3.3 训练超参调整与学习率策略
AMoFE的加入会让模型参数增加大概3.5M,训练时需要稍微调整下策略。
- 初始学习率:建议从0.001开始,比默认低一点。因为门控网络随机初始化时会输出接近0.5的权重,前期梯度波动大,学习率太高容易把专家分支毁掉。我试过直接默认0.01,头几个epoch loss直接炸掉,降到0.001后才稳定。
- 权重衰减:保持0.0005即可,不用改。
- 门控L1正则的系数:我设置在
1e-4。这个系数不要太大,否则门控输出被压到全0,模型退化为普通残差融合。最好写成动态权重,前10个epoch用0,10个epoch以后再加到1e-4,这样能让专家分支先有个正常初始化状态。 - Batch size:如果单卡显存不够,专家B的并行分支会占比较多显存,建议从大batch减半,然后梯度累积补回来。
训练日志里需要额外关注两个指标:一个是门控权重的均值,应该稳定在0.3~0.5之间,不要太低;另一个是专家输出的L2范数,如果某些专家范数逐渐趋近0,说明这个专家没有被门控选上,出现了专家退化。出现退化时,可以给门控权重的梯度乘以一个系数,或者把退化的专家输出标准差拉大一些。
3.4 与分割任务的适配
YOLOv11的实例分割版(YOLOv11-seg)和检测版共享backbone和neck,只是head不同。我的AMoFE插在neck部分,所以切割模型同样适用。分割任务对细节更敏感,浅层特征的重要性比检测时还要高。我在分割实验中,把两个AMoFE的位置向浅层偏移了一点:除了P3-P4融合,又额外加了一个P2-P3融合,最终mask mAP涨了1.1个点。代价是训练显存多了大约1.5GB,P2的分辨率是320×320,AMoFE在其中计算门控权重图时开销较大。如果你的机器显存紧张,P2这层可以不做。
4. 实验对比与结果分析
4.1 实验设置
我用的是一个包含20个类别的目标检测与分割混合数据集(按COCO格式组织),图像尺寸640,训练集约8万张,验证集约5千张。训练的硬件是单卡某中高端GPU,batch size 16,epoch 300,用了SGD优化器,初始学习率0.005,带余弦退火和预热。对比的基线是YOLOv11m(默认结构),然后在此基础上只加入AMoFE模块,不调整其他任何设置。同时我还加了两组对照:一组是把AMoFE换成BiFPN集成,另一组换成通道注意力CA集成,用来公平对比。
4.2 目标检测涨点数据
| 模型 | mAP50 | mAP50-95 | Params(M) | GFLOPs |
|---|---|---|---|---|
| YOLOv11m 基线 | 55.2 | 38.1 | 20.1 | 90.2 |
| YOLOv11m + BiFPN | 55.8 | 38.7 | 20.9 | 94.0 |
| YOLOv11m + CA | 55.5 | 38.4 | 20.3 | 91.1 |
| YOLOv11m + AMoFE (本文) | 56.9 | 39.8 | 23.6 | 96.5 |
可以看到,AMoFE在mAP50-95上比基线涨了1.7个点,比BiFPN涨了1.1个点。值得注意的是,小尺度的AP提升最为显著,从基线的21.3涨到了23.0。这说明浅层和深层特征的动态融合,对提高小目标的检出率非常有效。大物目标是AMoFE的弱项,AP大约只涨了0.3,原因是深层语义已经足够强,动态调制反而引入了一些噪声。
4.3 实例分割涨点数据
| 模型 | mask mAP50 | mask mAP50-95 |
|---|---|---|
| YOLOv11m-seg 基线 | 51.7 | 35.2 |
| YOLOv11m-seg + BiFPN | 52.3 | 35.9 |
| YOLOv11m-seg + AMoFE | 53.6 | 37.1 |
分割涨点的幅度更大,因为mask预测对边界细节的要求极高,浅层特征在AMoFE中被门控网络赋予了更多权重,生成的mask边缘平整度更好。我用了一个直观的测试:把输出mask放大看边缘,AMoFE版本在物体边缘的锯齿效应明显减少,一些细长结构的连续性好了很多。
4.4 消融实验:专家与门控各自的贡献
为了确认每个设计都不是白加的,我做了三组消融:
| 配置 | mAP50-95 |
|---|---|
| 完整AMoFE | 39.8 |
| 去掉细节专家(只留B+C) | 39.1 |
| 去掉多尺度专家(只留A+C) | 39.2 |
| 去掉通道专家(只留A+B) | 39.0 |
| 去掉门控,改为三个专家输出平均 | 38.2 |
| 去掉门控中的局部分支,只用全局 | 38.6 |
从消融结果可以看出,三个专家每个都有存在价值,其中门控的重要性比专家本身更大。去掉门控后,模块变成了固定的多分支融合,和BiFPN的优势就没有了。另外,门控的局部分支也很关键,只靠全局池化提供的权重,无法做到空间上的自适应,涨点减弱了。
实操心得:如果你想在计算资源受限的设备上部署,可以考虑把门控的局部分支去掉,只用全局分支。模型会从像素级自适应退化为图像级自适应,涨点会掉0.3个点左右,但GFLOPs能下降约2.8,适合边缘设备。
5. 踩坑记录与排查技巧
5.1 专家退化:某个专家梯度长期为零
这是结合MoE最常见的问题,我前几次训练也中了招。训练到第50个epoch时,发现专家C的输出范数一直在减小,最后变得几乎为零。原因是门控网络学会了把所有区域的权重都分配给专家A和B,专家C完全拿不到梯度信号。
排查方法是在训练日志中记录每个专家被选中的平均概率。如果某个专家的平均概率小于0.1,就需要干预。我的缓解办法是给门控权重的熵加一个正则项,鼓励它均匀分布,然后再施加L1正则。两个正则共同作用时,权重既不会全糊在一起,也不会完全集中在某一个专家上。最终我用了两个正则,系数分别设置为1e-4和1e-3。
5.2 显存爆炸:门控权重图占据太多显存
AMoFE在P2或P3层使用时,门控输出是(B, N, H, W)的浮点张量。假设输入为640×640,P2是320×320,B为16,N为3,那么门控张量的大小是 16×3×320×320 = 4.9M个浮点数,约20MB。这还不算反向传播时要保存梯度,一翻就是40MB。如果你叠加两个AMoFE,显存开销就会多出约1.5GB。
解决方法有两个:一是把门控权重图的存储位置调整,不保存中间梯度,而是用ReLU或直接对门控进行池化来降低分辨率。我最终放弃了P2级别的AMoFE,只在P3和P4上做,显存占用就正常了。如果你需要P2级别,可以把门控输出用双线性插值上采样到需要的尺寸,或者把门控的空间尺寸降低到输入的1/4,然后再上采样回去,精度影响不大。
5.3 加载预训练权重时报shape不匹配
YOLOv11默认会加载COCO预训练权重。因为你改了neck结构,权重文件里没有AMoFE对应的层,所以加载时会出现缺失键。这个时候千万不要直接删掉缺失层,否则会导致模型随机初始化一部分层而破坏整体稳定性。我的做法是冻结backbone参数前100个epoch,只训练neck和head。这样缺失的AMoFE层虽然随机初始化,但backbone不更新,整个网络依然能在一个合理的语义特征空间上做自适应。等AMoFE稳定下来后,再解冻backbone统一微调。
另外还需要注意,如果你在yaml里替换了原始Concat,预训练权重里那个Concat没有参数,所以不会报错。但如果替换的是C3k2,由于C3k2有卷积层参数,就会报形状不匹配。所以我在设计插入点时,选择在SPPF后面插入AMoFE,而不是直接替换已有的卷积模块,避免复杂度上升。
5.4 训练后期loss震荡
AMoFE引入后,大约在200个epoch附近出现了loss忽高忽低的情况。排查发现是门控网络和专家分支之间的相互作用导致梯度异常。门控输出经过sigmoid后范围在0到1之间,如果门控输出接近1,专家输出的梯度会乘以1,如果门控输出接近0,梯度被阻断。训练后期门控容易饱和,造成梯度波动。
缓解方法很粗暴但有效:在门控sigmoid之前加一个dropout,让门控的输入随机置零一部分,这样能迫使门控不要过早饱和。我用的是nn.Dropout(p=0.1)。这算是一个小trick,但确实让loss曲线稳定下来了。
6. 后续扩展思路
这个AMoFE模块不止可以放在YOLOv11里,它本身是通用的特征融合单元,可以替换任何需要融合两个特征图的地方。比如多模态输入(RGB图用浅层特征,深度图用深层特征),或者视频帧融合(前一帧的特征和当前帧的特征),理论上都能套这个结构。我最近在做的一个小项目是把AMoFE用在语义分割的编码器-解码器连接上,替代原来的U-Net式skip connection,效果也还不错。
另一个值得尝试的方向是让专家分支更极端一些,比如把其中一个专家设计成频域专家(用FFT提取高频或低频成分),另一个设计成姿态敏感专家(对旋转目标更友好),这样可以通过动态路由让模型自己针对不同目标类型选择不同的专家。不过目前我还没在这块做完整实验,等数据跑出来后再写一篇详细记录。
如果你手头正好在做YOLOv11的涨点实验,直接照着我这版代码试一下,先看看自己数据集上的门控权重分布是否合理。每个数据集不一样,可能需要稍微调一下门控正则强度,但一般不需要动专家结构。希望这篇记录能给你省下一些摸索时间,毕竟我在门控和专家结构上至少重写了四五个版本才最终稳定下来。