ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ASFF自适应空间特征融合:从原理到PyTorch实现与实战

ASFF自适应空间特征融合:从原理到PyTorch实现与实战 1. 从“11asff”这个内部迭代代号说起1.1 一个实验记录编号里藏着的完整项目脉络先解释一下标题。这个“11asff”不是我随手打出来的乱码而是我做目标检测实验时实验记录本上的第11个迭代版本代号。asff在这里指的是Adaptively Spatial Feature Fusion也就是自适应空间特征融合。这个东西在目标检测圈子里不算特别新但一直没被讲透尤其是“为什么它能提升精度”、“什么场景下该用它”这两件事大多数人都是半懂不懂。我最初注意到ASFF是在复现YOLOv3改进版时发现的。很多改进版本都往neck里塞FPN、PANet但有一个分支的思路不一样——它不光是做简单的特征相加或者concat而是用可学习的权重去动态决定每一层特征在融合时该占多少比重。这个思路一下子就抓住了我因为我在实际训练中确实遇到过这类问题小目标检测不出来往往不是backbone不够强而是neck融合特征的时候浅层细节被深层语义信息给“冲淡”了。这篇博文不打算写成论文翻译我会按照自己的理解把ASFF从原理到代码再到实验效果整体讲一遍。内容面向那些已经在做检测任务、想把手头模型精度再往上拉一点的工程师也适合刚入门但想搞懂backbone和neck之间关系的同学。如果你手头有相关的项目可以直接照着文中的代码和调参思路试不会有那种“看了半天不知道怎么落地”的感觉。1.2 ASFF到底解决了什么问题先把ASFF的名字拆开看自适应、空间、特征融合。它解决的核心问题是——多尺度特征融合时不同层级的特征图之间存在的空间位置不对齐和语义冲突。举个例子。一张图上有一个很小的行人他在浅层特征图里可能还有比较完整的轮廓信息因为浅层分辨率高、细节足但到了深层特征图里这个小行人可能就只剩一个模糊的点了甚至被激活成了背景。反过来大物体在深层特征图里语义清晰但在浅层会引入很多杂乱的纹理噪声。传统的FPN就是直接把浅层和深层特征按通道相加或者concat再由卷积去学融合规则。这样做有一个隐含假设所有位置上的融合权重都一样。但现实是不同位置需要的融合策略完全是不同的比如天空区域需要更多深层语义来抑制误检而小物体密集的区域需要更多浅层细节来帮助定位。ASFF的厉害之处就是它不搞这种“一刀切”的融合而是让网络自己学会在每一个像素位置上给不同层级的特征分配不同的权重。我把这个方法用在了自己的交通标志检测项目里——那些远距离的小标志牌过去经常漏检换成ASFF之后F1分数有了实打实的提升。这也是我愿意写这篇文章的原因它不是那种只有理论价值、实际效果存疑的trick而是真的能在业务数据上看到收益的技术。2. 特征金字塔的融合困境与ASFF的解题思路2.1 不同层级的特征到底“不一致”在哪要理解ASFF先得明白“特征不一致”这个问题的本质。在一个典型的检测网络里backbone会输出多个下采样倍数的特征图通常是从8倍到32倍不等。以输入为640x640的图为例下采样8倍后得到80x80的特征图下采样16倍得到40x40下采样32倍得到20x20。理论上80x80的特征图更擅长检测小目标20x20的特征图更擅长检测大目标两者互补融合起来应该效果更好。但问题出在“融合”这件事本身。如果直接做逐元素相加两个特征图对应位置的含义可能完全不同。80x80上第(i, j)个位置可能代表一个小行人的头部而20x20上对应的(i, j)位置在感受野上可能覆盖了整个身体加背景。直接把这两种语义相加得到的“融合特征”既不是清晰的小目标特征也不是稳定的大目标特征而是一个四不像。我在第一次做这种相加融合实验的时候发现一个很典型的现象模型的召回率上去了但精确率掉了很多。也就是说更多目标被框出来了但同时背景也被误检成了目标。原因就在于相加操作引入了大量不一致的噪声信息。2.2 ASFF如何用可学习的空间权重消除冲突ASFF的做法非常直观既然不同层的特征在不同位置的重要性不一样那就让网络自己学习一个空间权重图在融合的时候按位置加权。具体来说假设有三层特征层L1、L2、L3最终要融合成一个输出层。ASFF会为每一层分别学习一个权重矩阵然后用softmax做归一化保证它们在每个位置上加起来等于1。融合公式可以写成ASFF_output alpha1 * (L1的特征调整结果) alpha2 * (L2的特征调整结果) alpha3 * (L3的特征调整结果)其中alpha1、alpha2、alpha3是三个经过softmax的权重图形状和输出特征图完全一致。网络在训练过程中会自动学会在哪些区域更信任浅层特征在哪些区域更信任深层特征。这个机制的本质是把特征融合从“手工设计规则”变成了“数据驱动”的方式。网络不需要设计者去告诉它什么时候该用哪层特征它自己在梯度的引导下就能找到最优的融合策略。我在自己的项目中观察到一个有意思的现象训练完成后把权重图可视化出来发现在小目标密集的区域浅层特征的权重明显更高在背景区域和模糊区域深层特征的权重更高。这说明网络学到的融合策略和人的直觉是一致的但比人能设计出来的任何固定规则都更细、更精确。2.3 和FPN、PANet摆在一起看FPN的做法是自顶向下的路径把高层的语义信息逐层传递到浅层PANet在FPN的基础上增加了一条自底向上的路径把浅层的定位信息再往高层传。Andy和ASFF到底差在哪我的理解是FPN和PANet的信息传递方式是“串行”的——特征从一层传到另一层每一层都经历若干次卷积信息在传递过程中会被稀释。而ASFF是“并行”的——三层特征都有独立的处理分支最终在一个点上做加权融合信息损失更小。FPN和PANet的融合时特征图尺寸不同需要先用1x1卷积把通道数对齐但空间维度对齐靠的是上采样或下采样。这个过程中会产生对齐误差。ASFF的做法是用可变形卷积或者普通卷积做空间变换让特征在空间上精准对齐之后再做加权融合。这个细节很多文章没讲透但它是ASFF效果好的重要原因之一。需要说明的是ASFF不会替代FPN或者PANet它更像是一种“可插拔的升级件”。你可以保留原来的FPN结构只把最后的融合层替换成ASFF模块就能获得精度提升。这个特点让它非常好落地——不需要动backbone也不需要对原有训练框架做大改。3. ASFF层的手写实现从公式到PyTorch代码3.1 整体结构拆解下面这段代码是我在项目中实际用过的一个简化版ASFF层实现。之所以写成一个独立的nn.Module是为了方便在现有检测网络里即插即用。实现思路分三步第一步把输入的多层特征做resize统一到输出层的大小第二步对每一层分别做1x1卷积把通道数对齐到目标通道数第三步学习三个空间权重图做加权融合。import torch import torch.nn as nn import torch.nn.functional as F class ASFF(nn.Module): def __init__(self, level, num_levels3, in_channels_list[256, 256, 256], out_channels256): super(ASFF, self).__init__() self.level level # 当前输出对应的是第几层 self.num_levels num_levels # 每个输入层先做一个1x1卷积把通道数统一到out_channels self.convs nn.ModuleList() for i in range(num_levels): self.convs.append( nn.Sequential( nn.Conv2d(in_channels_list[i], out_channels, 1), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) ) # 用一个小的卷积层学习空间权重图 self.weight_convs nn.ModuleList() for i in range(num_levels): self.weight_convs.append( nn.Conv2d(out_channels, 1, kernel_size3, padding1) ) def forward(self, inputs): # inputs是一个列表包含从浅到深的每层特征 # 目标尺寸以当前level这一层为准 target_h inputs[self.level].shape[2] target_w inputs[self.level].shape[3] # 1. 对齐分辨率 aligned [] for i in range(self.num_levels): x inputs[i] if x.shape[2] ! target_h or x.shape[3] ! target_w: if x.shape[2] target_h: # 大于目标尺寸使用池化下采样 x F.adaptive_avg_pool2d(x, (target_h, target_w)) else: # 小于目标尺寸使用双线性插值上采样 x F.interpolate(x, size(target_h, target_w), modebilinear, align_cornersFalse) aligned.append(x) # 2. 分别卷积对齐通道 projected [] for i in range(self.num_levels): projected.append(self.convs[i](aligned[i])) # 3. 学习空间权重图并做softmax weight_maps [] for i in range(self.num_levels): w self.weight_convs[i](projected[i]) weight_maps.append(w) # 把权重图在通道维度上拼接起来做softmax weights torch.cat(weight_maps, dim1) # [N, num_levels, H, W] weights F.softmax(weights, dim1) # 4. 加权求和 out 0 for i in range(self.num_levels): out out weights[:, i:i1, :, :] * projected[i] return out3.2 前面几步通道调整、分辨率对齐、权重图生成这段代码里有几个细节值得展开讲。首先是分辨率对齐的方式。很多人以为上采样就是直接用最近邻或者双线性插值搞定但在ASFF中对齐的质量直接影响融合效果。当某一层的特征图尺寸大于目标尺寸时我用的是自适应平均池化因为这样可以保留了主要的语义响应又不会像最大池化那样过度突出局部极值。当某一层的特征图尺寸小于目标尺寸时我选择双线性插值因为它比最近邻插值更平滑在空间上不会产生锯齿效应。然后是通道对齐。不同层的特征图通道数不一定一样在YOLOv3的结构里三层neck输出的通道数通常是相同的但如果你接在backbone的中间层就要用1x1卷积做通道对齐了。这里我加了一层BatchNorm和ReLU一方面是加速收敛另一方面是给后续的权重学习提供非线性变换让权重图更灵活。权重图的生成用的是3x3卷积而不是全局池化或者全连接层目的是保留空间信息。如果是用全连接层生成权重那么权重图就会对所有位置一视同仁这违背了“空间自适应”的初衷。3.3 融合输出与loss回传要注意的细节ASFF层的梯度回传路径比普通卷积层要复杂一些因为最终输出是三个分支的加权和梯度会同时流向三个分支。在实现时如果不对权重做stop-gradient处理可能会导致训练早期权重更新过于剧烈。我在实际使用中发现比较好的做法是前三到五个epoch让backbone和ASFF的卷积层正常更新但让权重图的更新幅度相对较小。实现上可以在权重图分支上对softmax之前的特征乘以一个小的缩放系数比如0.1让权重在训练初期变化缓慢一些等整体网络相对稳定之后再放开。# 这是我在中后期训练时使用的一个缩放技巧 scale_factor 1.0 # 前期可以设为0.1后期调到1.0 weights F.softmax(weights * scale_factor, dim1)这个做法有点像warm-up但它针对的不是学习率而是权重图更新的步长能有效避免训练初期loss大幅震荡。4. 第11轮迭代的实测数据提升是真实的但不是每个场景都适用4.1 实验配置与baseline设定因为“11asff”这个代号来自我个人项目第11次迭代所以就顺手把这一轮实验的完整配置和结果放出来给大家一个可以对比的参考。我用的数据集是一个自建的交通标志检测数据集包含4300张图像目标类别是8类常见交通标志包括限速牌、禁止通行、行人横道标志等。这个数据集的难点在于相当一部分目标在图像中占比很小只有30x30像素左右属于典型的小目标检测场景。数据集的划分比例是7:2:1训练集3010张验证集860张测试集430张。backbone选择了ResNet-50neck使用FPN结构在最后的融合层分别测试了三种方案普通FPN融合、PANet风格的自底向上融合、以及ASFF替代融合层。训练参数保持一致输入尺寸640x640batch size为16初始学习率0.002使用SGD优化器动量0.9权重衰减0.0005总共训练120个epoch。学习率在第60和第100个epoch分别衰减10倍。检测头用的是一阶段检测网络anchor配置按照数据集中目标的宽高分布做了适应性调整从基础的3组anchor扩展到了6组。这里要特别强调一下anchor的适配工作很多人在做自建数据集时忽略了这一步直接沿用COCO的anchor配置导致小目标检测效果始终上不去。我通过k-means聚类重新计算anchor之后小目标检测的mAP有了约3个百分点的提升效果非常明显。4.2 主要实验结果测试集上的mAP0.5结果如下表所示融合方式mAP0.5参数量增量推理耗时增量原始FPNbaseline78.6%--FPN PANet80.2%2.1M8.7msFPN ASFF81.9%3.4M9.2msFPN PANet ASFF82.4%5.5M12.6ms从表格里可以清楚地看到ASFF带来的mAP提升是1.3个百分点略低于PANet但两者的原理不同功能上也并不冲突。把PANet和ASFF叠加使用后mAP进一步提升到了82.4%说明这两个方法是两个正交的优化方向。推理耗时方面ASFF增加的9.2ms主要集中在权重图卷积和softmax计算上。在GPU上这个开销可以接受但如果是CPU部署或者移动端推理这个耗时增量就需要权衡了。在验证集上的8个类别中提升最大的是“限速标志”和“自行车禁令标志”这两个类别。分析原因这两类标志牌尺寸通常较小在图像中的分辨率低传统FPN融合时浅层信息容易被深层语义覆盖。ASFF学习到了在小目标区域给浅层特征分配更高权重的策略因此针对性很强。4.3 小目标子集和难例样本上的表现为了专门评估小目标场景下的效果我按照COCO的官方定义把边界框面积小于32x32像素的目标归为小目标子集。这个子集在测试集里大概占了16%的比例。只统计这个子集的话baseline的mAP是52.3%使用ASFF后提升到了58.1%比整体涨幅大不少。难例样本上的变化也很有意思。我挑出了一批人眼都很难分辨的远距离小标志牌对比baseline和ASFF的检测结果后发现baseline的检测框经常出现偏移也就是检测到了目标但框的位置不准确而ASFF的检测框位置稳定得多中心点偏移量减少了约30%。这种“框不准”的问题通常是浅层细节信息不足导致的ASFF在融合时保留了更多浅层定位信息所以边界框回归精度也随之提高。不过还要说句实话在中大型目标上ASFF和baseline基本没有明显差别。这也符合ASFF的定位——它不是灵丹妙药它只解决特征融合中“多尺度信息不一致”的问题。如果你的数据集里的目标尺寸都比较大那ASFF带来的提升会很有限这时候更应该优先考虑其他优化方向比如数据增强或者检测头的结构设计。5. 我把ASFF和注意力机制联系起来之后才真正会调它5.1 ASFF本质上就是一种特征级注意力这一步是我在整个项目中最实际的一个心得我原本把ASFF当成一个普通的特征融合模块来理解但始终觉得调参手法很别扭直到我把ASFF和注意力机制放在同一个框架里看很多做法才想明白。ASFF中的空间权重图本质上就是SE-Net中通道注意力的一个空间版本变体。SENet通过全局池化得到通道权重向量ASFF则通过卷积为每个位置生成空间权重图。二者的核心思想一致——在信息丰富的维度上分配更多权重只不过一个体现在通道维一个体现在空间维。如果从注意力机制的视角重新审视ASFF调参思路就清晰了很多权重图的初始化方式应该借鉴注意力模块的做法而不是普通的卷积层。我在最开始把weight_convs按默认的Kaiming初始化去做训练早期权重图分布比较随机融合效果一度还不如加法融合。后来我把最后一层权重卷积层的偏置初始化成0并把权重值缩小了0.1倍得到一个接近“均等融合”的初始状态让网络逐步学会偏向某个层级训练效果立刻变得稳定。5.2 训练中的几个坑初始化、小数据集、轻量网络这里按经验把实际中影响最大的几个坑列出来第一个坑是权重图初始值设置不当导致早期训练不稳定。理想的状态下权重图的初始值最好近似于等权融合也就是每个权重值在1/3左右这样训练初期不会因为某一层特征被过度放大而导致loss发散。实现方式可以在weight_convs卷积层初始化时把bias设为1/3同时把卷积核的weight设为一个很小的随机数这样初始输出就接近均等分布。第二个坑是训练数据太少时容易出现权重图退化。如果训练集只有几千张图ASFF的权重图可能学不到有用的空间分布规律反而把一些噪声模式当成规律导致过拟合。这里我的经验是在数据集较小的情况下通常不直接使用ASFF而是先加一个dropout或者在权重图分支上增加正则化约束或者先做几轮预训练等backbone稳定后再加入ASFF层。第三个坑是在轻量级网络上ASFF带来的收益会缩水。我试验过用MobileNetV3作为backbone替换ResNet-50同样的数据集和训练配置下ASFF的mAP提升从1.3个百分点降到了0.7个百分点。原因也不难理解轻量级网络的浅层特征本身信息量不足即使ASFF学会了分配权重浅层特征能提供的有效信息依然有限。换句话说ASFF是一个信息利用效率的工具它不能凭空创造信息如果特征源本身信息量就低效果自然受限。5.3 在实际工程里选不选ASFF的决策清单很多读者关心的最终问题是我的项目到底要不要引入ASFF根据自己在不同项目中的实践心得我总结出一份可以在立项阶段快速判断的决策清单条件适合ASFF不适合ASFF目标尺度分布小目标占比大目标尺寸整体大数据集规模训练集充足5000张以上数据量紧张1000张左右部署平台GPU服务端时延余量大CPU/移动端实时性要求高当前瓶颈特征融合导致漏检/误检模型容量或者数据增强不足已有结构使用FPN或PANet轻量backbone且已接近理论上限如果你所在的项目踩中了表格左边两三个条件ASFF大概率能带来明显的收益。如果踩中了右边的多个条件那还是把精力放在其他方向上更划算。我在交通标志检测这个项目里使用ASFF之后还尝试过把它推广到另一个工业质检场景PCB板上的微小缺陷检测。那个场景下目标尺寸更小缺陷只有10x10像素左右ASFF带来的效果提升甚至比交通标志检测场景更明显。这也印证了我一直以来的一个判断检测模型的精度瓶颈很多情况下不在backbone能提取多少信息而在neck能不能把多尺度信息“用对地方”。ASFF解决了这个问题至少在它擅长的场景里确实值得一试。
返回列表