
做语义分割的都知道一张 512×512 的输入图在经典分割网络里要完整跑完主干、解码器和分割头才能输出像素级结果。无论画面内容多简单计算量一分不少。这也是语义分割和图像分类最不一样的地方——逐像素分类带来的计算开销在全图范围扩散任何冗余都不能靠裁剪简单地省掉。我之前在复现分割模型时一直在想有没有可能让网络根据图像内容自己决定走到哪一层、走哪个分支简单画面走轻量路径复杂场景走重一变体。这个想法其实就是“Learning Dynamic Routing for Semantic Segmentation——学习用于语义分割的动态路径”要解决的问题。它和常见的模型压缩、剪枝不一样不是把每张图都缩成同一个轻量网络而是在推理时像 Google Maps 规划路线一样动态给每个输入分配一条计算路径。这篇文章适合三类人一是对语义分割效率敏感、做自动驾驶或无人机落地的开发者二是研究动态网络、条件计算、自适应推理的同学三是刚入门语义分割想了解固定网络之外还有什么结构创新的读者。我会从动态路径的核心动机讲起再拆机制、给训练策略、最后摆出我在实操中踩过的坑和排查方法。1. 动态路径在语义分割中的价值定位1.1 固定计算图的算力浪费到底在哪先看一个很现实的数据把一张 512×512 的输入图跑进一个参数约 40M 的分割网络总体计算量FLOPs通常在 200G 以上。即使输入换成一幅几乎全黑的夜间图或是一张只有天空和道路的简单高速场景网络依然要消耗同等算力。而事实上这类简单图像在浅层卷积之后特征已经高度可判别后面很多层只是在重复提取低频信息。你当然会说用更小的 Backbone 不就行了。但问题在于模型经过训练后容量是固定的。轻量网络在复杂场景下会掉 mIoU重网络在简单场景下又在浪费。语义分割的数据分布天然是“长尾且不均衡”的像素类别随时间、空间变化很大每张图内的难度也极端不均匀。一个自适应的方法比简单地折中到“中量级网络”更有吸引力。动态路径的核心就是把“所有图片走同一路径”改成“每条输入走属于自己的路径”。这和注意力机制、SENet 的通道加权有本质区别——动态路径选择的是宏观计算路线而不是对同一特征做微调缩放。它想节省的是整个网络结构的浮点运算不是单个特征图的权重重标定。1.2 动态路由的基本直觉难例走长路易例走短路动态路由的直觉说穿了就一句话给简单输入分配低计算量的路径给难输入分配高计算量的路径。实现上会在网络中间位置插入一个“门控模块”它读入中间特征输出一个路径选择信号。比如典型的方法会设计两个分支轻量分支跑得快、精度稍低重磅分支跑得慢、精度更高。门控会估算当前特征处理难度然后决定当前样本走轻分支还是重分支。实际复现中你会发现真正有效果的动态路由并不只是“快跳几步”的 early exit而是一个完整的路径决策空间。一方面门控要把“要不要提前退出”和“选择哪个分支”统一起来另一方面要保证梯度能穿过离散选择传回给门控参数。如果门控本身设计得不正确反而会造成训练不稳定甚至产生新瓶颈。这也是为什么“Learning Dynamic Routing”这类工作的核心卖点不是“动态”本身而是“学习”如何路由——让门控具备足够的判别能力同时又不过度增加参数量和额外计算。一个轻量门控如果只有一个全局平均池化加一个全连接往往天真到无法区分场景难度但门控如果做成一个完整分割网络那动态路由就成了“大模型套小模型”成本也完全不可接受。这条平衡线是落地时最需要花精力打磨的地方。1.3 动态化的三个层级深度、宽度、路由动态网络不是一个全新概念拆开看有三条技术路线。第一类是深度方向比如随机深度、early exit 分类器网络根据输入决定前向传播经过多少层第二类是宽度方向使用可变宽度的卷积层或根据输入动态选择通道组第三类就是我本篇重点讨论的“路由级”在多个并行分支或子网络之间做选择。对语义分割而言路由级动态化比深度级更契合任务本身。原因是分割对不同类别需要不同等级的感受野识别“天空”和“道路”在浅层局部特征里基本就够了但识别“障碍物边缘”和“车道线粘连区域”需要全局上下文和更深的语义信息。如果只做 early exit简单类别可能在浅层就分类退出但语义分割的输出是一张像素级标签图不可能每个像素独立选择退出时机。所以在分支之间做路由既能做到整图级路径分配又能保留像素级稠密预测的完整性。这也是“动态路径”这个叫法的由来网络不是你在编译时静态展开的而是在推理时根据输入动态规划。后续很多工作都沿用这个思路把“路径”理解为从输入到输出的一个完整前向计算图子集。2. 动态路由网络的核心机制设计2.1 软路由和硬路由为什么最终要转向 Gumbel-Softmax路由选择本质上是离散决策走 A 分支还是 B 分支。直接对这个离散选择端到端训练最简单的做法是用强化学习REINFORCE把“走哪个分支”当作动作把分割损失当作奖励。但 REINFORCE 方差大训练极不稳定尤其在语义分割这种每个样本损失变化幅度都很剧烈的任务上门控很容易训飞。更实用的方案是软路由门控输出一个连续分布比如 [0.7, 0.3]然后在前向时对两个分支做加权融合或者直接用 Gumbel-Softmax 采样出近似离散的 one-hot 选择。Gumbel-Softmax 的好处是前向时通过 softmax 温度参数做可微采样温度高时接近均匀分布、梯度平稳温度低时趋近 argmax、行为接近硬选择。这样的“软-硬过渡”让门控在前期能正常收到梯度在后期又能真正逼近离散路径。我在实际代码里会把温度做成一个可调的 schedule而不是固定值。训练前期温度设成 5 左右让门控先探索各个分支训练中段降到 1.0开始利用模式后段降到 0.1~0.3让门控决策趋硬。如果从头到尾都用低温门控梯度会频繁出现“死区”参数更新几乎停滞。2.2 门控网络怎么训练三部分损失缺一不可动态路由的损失不能只有分割损失。我复现时发现最少需要三块主任务损失、路由熵损失、算力惩罚项。主任务损失保证精度路由熵损失防止门控过早陷入单一路径失去动态性算力惩罚项则让网络在“复杂分支”“轻量分支”之间真正做出算力上的权衡。先看主任务损失。如果走软路由两个分支的输出会先按门控概率加权融合再进分割头。这样一个 batch 里每个样本的融合权重不同相当于网络有了一丝“注意力分配”的意味。但要注意软路由做训练可以推理时如果不收缩成硬选择性能上会有问题——因为加权融合让两个分支必须同时计算省不了算力。路由熵损失通常写成门控概率分布的熵的负数鼓励分布偏熵小一点。一个常见误区是熵损失加太大门控直接收敛成“总是选概率最高的分支”动态能力被压制。我在实践里会把熵损失的系数设置在 0.01 到 0.1 之间并按 epoch 逐步衰减避免动态能力在后期完全消失。算力惩罚项需要量化一下。假设重分支 FLOPs 为 F_heavy轻分支 FLOPs 为 F_light网络期望计算量就可以写成平均 FLOPs。算力惩罚项被定义为超出某个目标算力的惩罚量或直接定义为整体 FLOPs 与常数 λ 的乘积梯度通过门控概率传播回去。这里有个很容易被忽略的细节如果算力惩罚和分割损失的比例不对模型会走极端——要么门控全部选择轻分支导致精度崩坏要么算力惩罚失去意义、网络又退回重分支。合理做法是把惩罚系数定在“省 30% 算力但 mIoU 不掉 1 个点”这个尺度上通过小网格搜索确定。2.3 参数共享与 FLOPs 平衡数学上要算清楚这笔账动态路由有个隐藏风险两个分支如果完全不共享参数模型参数直接翻倍最后“省算力”只是省了 FLOPs却多了大量内存和存储。实用做法是让两个分支共享前面大部分滤波器只在最后几只卷积层用不同通道数或不同膨胀率。这样参数增加可控且两个分支的特征提取器拥有共同底座门控做决策时才不必面对完全不同的特征分布。我以一个简单示例来算一下账。假设输入特征图尺寸是 64×64通道数是 256重分支带一个膨胀率 2 的 3×3 卷积轻分支是一个普通 3×3 卷积。两个分支都输出 256 通道。普通 3×3 卷积在 64×64×256 输入上的 FLOPs 约为 64×64×256×256×9 ≈ 2.4G。膨胀卷积因为相同输出尺寸、输入输出通道数相同有效卷积核尺寸扩大但权重数量不变FLOPs 与普通卷积相同。所以单看这一层两个分支 FLOPs 一样动态路由没省出任何计算量。只有让轻分支在通道数上“缩水”比如输出通道降到 128FLOPs 才变成 2.4G×0.51.2G。当门控以 0.6 的概率走轻分支时该层平均 FLOPs 变成 0.6×1.2G 0.4×2.4G 1.68G相比全重分支省了 30%。这就是动态路由省算力的数学本质它省的从来不是“单次推理的峰值计算量”而是“批量请求下的平均计算量”。算清了这笔账你才会明白为什么动态路由要在“分支差异大”才有意义。如果两个分支只是卷积核大小不同、计算量差不多那动态路由就是白忙活真正有价值的是把一个路径做成明显更轻量、另一个明显更重但更准用门控概率去平衡两者。3. 语义分割场景下如何落地动态路由3.1 主干网络与路由模块的衔接位置落地动态路由第一件事是决定门控和二分支插在哪。我的经验是不要插在浅层。以 ResNet 结构为例前两个 stage 的特征图分辨率高、通道少语义信息还没成型这时候做路由门控很难判断“这个样本难不难”反而增加浅层计算负担。插在最后一个 stage 之前才比较合理此时特征图已经缩到 1/8 或 1/16语义信息足够丰富门控的轻量全局池化就能提取有效全局描述。还有一个位置选择分割解码器比编码器更适合做轻量分支。我在实验里常用一个结构主编码器走完整路径轻量分支从中间特征直接接一个轻量解码头并输出分割图重分支则继续走完所有编码器再接完整解码器。这样设计的直观原因是轻量分支的误差主要来自“上下文缺失”而不是“分辨率不足”。对大面积类别天空、道路、草地来说中间特征已经足够做不错的预测门控只需要把复杂样本导向重分支就能达到比较理想的精度-效率平衡。另外需要考虑门控输入的特征尺度。我建议用全局平均池化替代直接拉平避免门控输入端出现超大向量。比如中间特征 64×64×512直接拉平会产生 200 多万维向量一个全连接层参数量根本无法接受。先做全局平均池化特征降到 512 维再接两层 128 维的全连接门控参数量在十万级几乎可以忽略。3.2 训练策略先静态预训练后动态联调动态路由端到端直接训练非常容易翻车。我的复现流程是分三步走每一步都有明确目的。第一步先把整个分割网络当成普通静态网络训练一遍此时两个分支都要参与前向但不是靠门控选择而是人为保证每个 batch 里大约一半样本走 A、一半走 B让两条路径各自的特征提取器和分割头都有一个稳定的初始化。这一步做不好后面门控接到的输入特征质量太差决策当然也不准。第二步接入门控模块打开动态路由训练。这个阶段我会把门控的输出的温度设为较高值让采样分布“软”一些。同时分割损失在两个分支的输出上都计算不过重分支的损失权重可以适当调高 20% 到 50%因为最终推理时一部分样本还是会走重分支其精度直接影响上限。第三步如果需要固定稀疏模式做工程部署我会在训练尾段用蒸馏的方法把动态路由网络的输出拉向一个更纯的轻分支版本或者反过来把重分支监督到轻分支上从而获得一个可静态优化的最终模型。这一步不是为了精度提升是为了在硬件上能稳定复现。每一步里都要盯一个指标路由概率的分布以及 FLOPs 均值。如果分布偏离“部分走轻、部分走重”说明训练出了偏差需要回到损失系数调整。3.3 推理时的自适应行为与工程取舍动态路由推理时有两种执行模式。第一种是真自适应每个 batch 内的每条样本都单独过门控然后各自前向不同的分支GPU 上表现为条件控制流第二种是“分布控制”门控不逐样本判断而是输出一个 batch 级的概率分布网络按比例随机给不同样本分配路径。第一种更优雅省算力效果更好第二种对工程更友好能避免碎片化。我实际测试过第一种模式遇到一个尴尬问题GPU 对动态分支的并行效率很差。当一个 batch 里有 32 张图16 张走轻分支、16 张走重分支两个分支的卷积核尺寸不同、通道数不同batch 会散成两个较小 batchTensor Core 利用率和算子启动开销会吃掉一部分收益。尤其当轻分支和重分支的计算量差距不够大时实测 FPS 反而可能下降。所以如果你做的是自动驾驶之类对延迟敏感的场景我会建议认真考虑“离线聚类 编译优化”路线训练完动态网络后把门控对验证集样本的决策记录下来聚成 2 到 4 个静态拓扑然后在部署代码里把每个拓扑写成独立分支推理时用查表方式做索引。这样既保留了动态路由的大部分收益又避免了 GPU 控制流碎片化。4. 复现与实验中的常见问题排查4.1 门控塌缩所有样本都选重分支这个现象我在第一次复现时几乎必然遇到训练到后期门控概率分布直接集中到重分支上轻分支形同虚设。排查时我会先打印每个 batch 中门控输出的平均概率如果整个训练过程重分支概率始终大于 0.95基本就是算力惩罚项太小网络没有感受到省算力的压力。修复手段有五种按优先级排序增大算力惩罚系数给轻分支输出加一个额外的辅助损失人为提升轻分支精度让门控有理由选择它在路由熵损失里加入“目标分布”正则项要求门控近似按 0.5/0.5 的比例分路在门控的输入特征中增加类别尺寸先验比如告诉网络当前图像中是否存在大量“小目标区域”最后可以尝试把门控换成了解更多信息的“预测信心估计”模块而不是简单池化特征。4.2 训练不稳定、损失震荡怎么定位动态路由训练最头疼的是分割损失下降正常但门控的置信度变动剧烈。原因是门控是一个“元决策器”它收到的是上游特征而特征在训练初期也在剧烈变化。门控参数更新受两个影响一是主任务损失在两条分支梯度上的叠加二是路由采样本身的随机性。这两个因素叠加时会带来很大的方差。我的排查思路是把训练日志里的门控输出、温度参数、算力惩罚三者在不同时间点的数值拉出来对齐。如果发现门控输出快速在两个极端之间摆动那就是温度太高或者熵损失太小如果门控逐渐僵死反而是熵损失太大。多试几组温度和损失系数组合通常能找到稳定区间。另外给门控加一个 EMA指数滑动平均的“稳定门控输出”用于前向决策而用原始值计算梯度也能明显降低训练震荡。4.3 部署时 FPS 不升反降的坑动态路由模型在 FLOPs 上很好看但部署后帧率没提升甚至下降这个问题很常见根本原因在于动态分支破坏了 GPU 的并行性。CUDA 核心擅长处理“一模一样”的算子小 batch 和大 batch 性能不成线性。轻分支如果只省 20% FLOPs而 batch 撕裂带来 30% 开销整体就是负优化。我踩过的一个具体例子FP16 混合精度下动态路由的轻分支使用 half 计算重分支因为精度敏感必须用 float32两套精度的算子切换和同步反而引入了不少等待时间。解决办法是在训练时就尽量限制“轻分支必须是也可以用低精度跑”并且在部署脚本里把轻分支和重分支分别做算子融合减少 runtime 分支调度的损失。也可以直接把门控的决策提前到“预处理阶段”用图像下采样后的简单分类器做初筛避免把门控放在每个 batch 正中间。4.4 常见问题速查表问题现象可能原因排查方向常用解法门控全走重分支算力惩罚不足、轻分支太弱查看门控平均概率、奖励曲线增大惩罚系数、给轻分支加辅助头门控全走轻分支算力惩罚过大、重分支初始化不好查看重分支单独精度的变化降低惩罚系数、先固定门控训练重分支训练损失震荡温度过高、路由采样方差大观察门控输出温度曲线加入 EMA 门控、调低温度衰减速度验证集精度下降但训练集不掉路由过拟合到训练分布对验证集做路由概率分布对比门控输入端加 Dropout、限制门控参数量部署后 FPS 反降动态分支碎片化算子分析、对比静动态拓扑时延改成静态稀疏模式、限制分支数两个分支差距不足路由收敛到相近特征比较两分支输出特征余弦相似度扩大分支结构差异、增加重分支容量5. 关于动态路由我个人的几个实操体会如果你只是想在语义分割上提升精度动态路由大概率不是最省事的选择——直接换更强 Backbone 或加更好的上下文模块收益更直接。动态路由真正适用的场景是平均算力预算严格受限你又不能接受在简单场景上丢精度的妥协。它用“运行时决策”换“模型容量弹性”这对边缘设备上跑多场景分割任务是值得的。我最后一个小建议是设计门控时不要只从 Backbone 的某一个特征层输出信号试试把低层纹理特征和高层语义特征做一次极简单的融合再进门控。比起单独使用高层特征融合后的门控对“小目标密集场景”的捕捉能力明显更好而额外计算开销只有一两个卷积层的成本。这个方向的后续扩展空间也很明确把动态路由的思路迁移到多任务学习里让网络在不同任务之间共享路径或者在生成式模型里让不同的生成阶段自动规划计算路径。但这些都建立在把“动态路径”的工程细节做扎实的基础上——先能让它在分割任务上真实跑起来、算得清收益再谈进一步的想象力。