ARTICLE DETAIL

资讯详情

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

卷积尺度全解析:感受野、特征尺度与采样尺度工程实践

卷积尺度全解析:感受野、特征尺度与采样尺度工程实践 把卷积核从 3×3 换成 7×7参数量涨了 5.4 倍算力跟着涨模型却未必看得更远。这是我做遥感影像小目标检测时被打脸的第一次。当时想当然认为大核等于大视野等于能抓大目标结果换完核之后大目标召回没涨多少小目标反而掉了两个点。后来把每一层的感受野手算了一遍又跑了一轮缩放扫描才发现问题根本不在核大小上而在我一直把卷积的尺度特性当成一个东西在谈。卷积的尺度特性拆开看至少有三个互不相同、又互相纠缠的维度感受野尺度说的是一个输出像素能看见多大范围特征尺度说的是同一个物体在网络里越往后越小采样尺度说的是stride 和池化把空间分辨率换成了多少倍的下采样。这三件事里只有第一件是核尺寸直接决定的后面两件得靠网络结构、训练策略和推理时的输入分辨率一起凑。这篇内容写给三类人手上正在调检测、分割、视频理解模型发现换个输入分辨率结果就崩的工程师看论文时被 dilation rate、output stride、scale jittering 这些词绕晕的同学以及想把 3D 卷积、图卷积、球面卷积串起来理解的人。公式、手算表、踩坑记录和一套能复现的体检流程我都摆出来可以直接抄作业。1. 卷积的尺度其实有三层意思混着谈必然吵起来这个行业里关于卷积尺度的争论十有八九是两个人说的不是同一个尺度。有人说加深网络就能扩大感受野另一个人说我加了十层也没用双方都没错因为后者说的是有效感受野前者说的是理论感受野。所以先把三个维度拆干净后面所有讨论才有共同语言。1.1 感受野尺度一个输出像素到底能看见多大范围感受野是输入空间里的概念指某个输出像素的值在原始输入图上受哪些像素影响。注意它跟特征图的尺寸不是一回事一个 7×1 的特征图可以有 100×100 的感受野一个 100×100 的特征图也可以只有 3×3 的感受野。前者说明信息高度浓缩后者说明特征图看起来很大但每个点看到的上下文很窄。这个区别在多尺度任务里非常关键。做小目标检测时你会希望浅层特征既保留高分辨率采样尺度小又有足够大的感受野感受野尺度大这两个诉求天然打架——因为传统的扩大感受野手段就是下采样。这也是空洞卷积能火起来的根本原因它把这两件事解耦了。1.2 特征尺度同一个人脸在浅层是 40 像素在深层是 3 像素特征尺度是特征空间里的概念。一张 1024×1024 的图里面有个 64×64 的人脸经过 stride 8 的骨干网络之后这个人在特征图上只有 8×8 大小。如果网络设计时锚框anchor最小只覆盖到 32×32 特征尺度那这张脸就落在检测器的盲区里。工程上所有的多尺度设计本质都是在不同特征尺度上各自布一套预测头FPN 之所以要做 P3 到 P7 五层就是因为单层的特征尺度覆盖不了现实场景里 10 倍以上的目标尺寸跨度。你以为是在堆结构其实是在给尺度打补丁。1.3 采样尺度stride 和池化决定了尺度的汇率采样尺度就是下采样倍数也叫 output stride。它决定了特征图上的 1 像素对应原图的多少像素。这个数字是尺度的汇率一旦定下来网络内部所有的空间量锚框、偏移量、坐标回归的目标值都要按这个汇率换算。我给团队新人讲这块时喜欢用地图类比output stride 就是地图比例尺。你在 1:10000 的图上量出 3 厘米换成实地就是 300 米。改成 1:50000 之后同样量 3 厘米就变成 1500 米了。锚框尺度忘了跟着改等于用旧比例尺的尺子量新地图结果必然是系统性偏差而不是随机噪声——这个特征很好辨认后面第 6 章会详细讲怎么排查。提示看到一个多尺度相关的 bug先问自己是感受野不够、特征尺度没覆盖、还是采样尺度换算错了。这三个问题对应完全不同的修法混着改只会越改越乱。2. 感受野的账要算清楚3×3 堆叠为什么能顶掉 7×7网上的文章经常给一个结论两层 3×3 等效一个 5×5但从不说这个等效是在什么条件下成立的。严格说只有感受野范围等效表达能力、非线性次数、参数分布都不一样。把账算细一点选型时心里才有底。2.1 递推公式与一张手算表单个维度的感受野递推式非常简洁RF_l RF_(l-1) (k_l - 1) × S_(l-1) S_l S_(l-1) × s_l 初始条件RF_0 1, S_0 1其中 k 是核尺寸s 是这一层的 strideS 是到这一层为止的累计下采样倍数。关键点在S_(l-1)这一项感受野的增长被前面所有层的下采样倍数放大了。这就是为什么在 stride 4 的特征图上加一个 3×3感受野能涨 8 个像素而在 stride 1 上同样加一个 3×3只涨 2 个像素。拿一个 VGG 风格的小骨干手算一遍比看十遍公式管用层核尺寸stride累计下采样 S感受野 RFconv13113conv23115pool12226conv331210conv431214pool222416conv531424conv631432看完这张表就能理解两个常见现象。第一网络后半段的感受野增长比前半段快得多同样一个 3×3 在 pool2 之后贡献 8 个像素在 pool1 之前只贡献 2 个。第二如果骨干网络有六层卷积两个池化最终感受野只有 32而输入图是 224那意味着网络在最后一层也没有看到全局——这就是为什么分类网络尾巴上要接全局平均池化它是唯一能把感受野一次性撑满整个图的操作。再算参数量的账。三层 3×3 的感受野是 73→5→7参数量是3 × 3 × 3 × C² 27C²忽略偏置一个 7×7 的感受野也是 7参数量是49C²。前者只有后者的 55%但中间多了两次非线性激活。这两次非线性带来的表达能力提升往往比大核带来的视野提升更值钱——除非你的任务确实需要提取低频、大范围的纹理模式比如风格迁移或者某些遥感场景。2.2 空洞卷积把核摊开参数不涨空洞卷积也叫扩张卷积的思路是核里插空格让采样点散开。有效核尺寸和感受野的算法k_eff (k - 1) × r 1 RF_l RF_(l-1) (k_l - 1) × r_l × S_(l-1)3×3 的核r2 时有效尺寸是 5r3 时是 7参数量始终是 9 个权重一点没涨。这就是它在语义分割里几乎是标配的原因在不损失分辨率的前提下把感受野撑到整张图。但空洞卷积有个非常隐蔽的坑叫网格伪影gridding artifact。当多个空洞层叠加时如果 dilation rate 互相之间有公因数、或者排列方式不对采样点会退化成稀疏的栅格特征图上出现棋盘状的周期性条纹。有些像素从来没被采样到有些被反复采样。这个现象在小目标上体现得特别明显目标恰好落在没被采样的位置上响应就很弱位置一挪就正常了你会误以为是数据不均衡。业界的处理办法是采用混合空洞卷积HDC的膨胀率设计比如[1, 2, 5]这种锯齿状序列而不是[2, 4, 8]这种等比的。经验规则有两条一是整组膨胀率里不能有长度超过 1 的公约数[2,4,8]全都能被 2 整除最糟糕二是后一层的最大膨胀率不能超过本层的核尺寸覆盖范围否则层与层之间会出现感受野空洞。这两条规则来自 HDC 那篇论文我在实际项目里验证过改完膨胀率之后小目标的召回一般能回升 1 到 3 个点且不需要重新设计结构。2.3 理论感受野和有效感受野差着一条街理论感受野是从公式里算出来的最大可能范围但实际训练出来的网络每个像素对输出的影响力不是均匀分布的。用梯度反传可视化会发现影响力近似一个高斯分布中心像素权重极高边缘像素权重趋近于零。这意味着三层 3×3 算出来的理论感受野是 7但真正有显著贡献的可能只有中心那 5×5 甚至 3×3。更反直觉的是有效感受野的大小大致按层数的平方根增长而不是线性增长。也就是说你把层数翻四倍有效感受野才翻一倍。我在做缺陷检测时吃过这个亏按理论感受野算出 400 像素足够覆盖一个缺陷实际标注框是 380 像素结果边缘区域死活检不出来。后来把上采样后的特征图叠了一层 3×3把有效感受野的中心区域往边缘推了一点召回就上来了。所以我的建议是理论感受野算出来之后打个七折当有效值用特别敏感的任务打六折。3. 平移等变是天赋尺度等变得自己挣卷积之所以统治了视觉任务核心原因是一条漂亮的数学性质平移等变性。但同一套推导里尺度等变性是不成立的——这是很多人直觉上过不去的一道坎也是多尺度问题的理论根源。3.1 卷积为什么天生平移等变严格定义一下。设T_δ是把图像在空间上平移 δ 个像素的操作* 表示卷积那么对任意输入 x 和卷积核 w(T_δ x) * w T_δ (x * w)翻译成人话先把图片往右挪 5 像素再做卷积和先做卷积再把结果往右挪 5 像素结果完全一样。这个性质来自卷积的权重共享——同一个核在图像每个位置用同一套权重位置本身不携带任何特殊含义。从频域看更直观。卷积定理告诉我们空间域的卷积等于频域的乘法F(x * w) F(x) · F(w)。滤波器在频域上就是一个固定的通带和阻带形状。这里埋着下一节的关键滤波器对频率的响应是固定的不随输入变化的。3.2 把图放大两倍卷积响应为什么塌了现在把尺度变换引进来。设S_s是把图像放大 s 倍的操作。注意这里S_s(x) * w ≠ S_s(x * w)为什么会不等因为放大在空间域对应的是压缩在频域。图片放大 2 倍它原本占据的频段整体向低频方向平移了一半。而卷积核 w 的通带位置是固定的没有跟着移动。原来正好落在通带里的那些纹理模式放大之后就掉到通带外面去了响应强度自然崩掉。我做过一个特别直观的小实验拿一个在 ImageNet 上预训练的 ResNet输入一张 224 的猫取最后卷积层的特征图记录激活值最大的位置和强度。然后把这张猫裁出来单独放大到 224 再喂进去同一个卷积核的响应值掉了将近一半而且响应最强的位置偏移了几十个像素。同一个物体同一个网络只是尺度变了行为完全不同。这就是 CNN 缺乏尺度不变性的本质。它并不是没学到尺度变化而是每次尺度变化都要靠数据重新教一遍。训练集里没出现过的尺度组合网络就是不会。3.3 尺度空间理论高斯金字塔和 DoG 的经典解法在深度学习之前尺度空间理论已经把这件事想得很透了。核心思想是不要把尺度当成要克服的干扰而是当成一个显式的维度引入进来构建一个图像的尺度族。具体做法是把图像和一串逐步增大的高斯核做卷积L(x, y, σ) G(x, y, σ) * I(x, y)σ 就是尺度参数。σ 越大图像越模糊细节越少剩下的是低频结构。这一串不同 σ 的结果叠起来就是一个三维的尺度空间。这个构造不是随便选的它满足几个很硬的公理线性、平移不变、旋转不变、尺度不变而高斯核是唯一同时满足这些条件的核。这不是工程偏好是数学结论。真正漂亮的是归一化高斯拉普拉斯真正的尺度不变检测要用σ²∇²G那个 σ² 因子是为了抵消微分带来的幅值衰减。实际操作里没人真的去算 LoG因为差分近似就够了G(kσ) - G(σ) ≈ (k - 1) σ² ∇²G这就是 DoG高斯拉普拉斯的近似比值 (k-1) 是常数所以 DoG 的极值位置和归一化 LoG 几乎一致。SIFT 就是靠这个建立金字塔每个 octave 里放三层k 取 2 的三分之一次方然后找尺度空间和空间位置上的三维极值点。一个特征点如果同时在多个尺度上都很显著它就会在不同尺度上被重复检测出来——这就是尺度不变性的实现方式。这套东西对今天的深度学习依然有启发与其指望网络自己学会尺度不变不如把尺度显式地喂进去。第 4 章讲的四种工程打法本质上都是这个思路的深度学习版本。4. 工程上补尺度的四种打法各自适合什么场景补尺度这件事没有银弹四种主流打法各有明确的适用边界和代价。选错方向的代价不只是精度还有推理延迟和工程复杂度。4.1 图像金字塔与测试时多尺度最朴素也最可靠的做法把输入图缩放到若干尺度每个尺度独立跑一遍网络最后把结果融合。检测任务里常用{0.75, 1.0, 1.25, 1.5}这组再叠加水平翻转也就是常说的多尺度测试增强TTA。它的优点是不需要重新训练对任何已训练好的模型都直接生效而且对小目标的提升非常明显小目标在放大后的图里变大落到了检测器熟悉的尺度区间。缺点是推理成本按尺度数量线性增长五个尺度就是五倍延迟再叠翻转就是十倍。而且融合逻辑有讲究分类分支一般取平均回归框要对齐到原图坐标再做 NMS。如果各个尺度之间置信度没有做归一化直接取最大值融合容易出现同一个物体在多个尺度上都被检出、NMS 又因为框差异太大而没能合并的情况。适用场景离线批处理、精度优先的竞赛或者标注补全任务。实时线上服务基本用不起。4.2 特征金字塔与多分支空洞卷积这是把多尺度做进网络结构里的主流方案代表是 FPN 和 ASPP。FPN 的思路是自顶向下加横向连接深层特征语义强但分辨率低浅层特征分辨率高但语义弱把深层上采样后和浅层相加每一层都能同时拿到两种信息然后每层各自出预测头。ASPP 走的是另一条路在同一个特征图上并联几组不同膨胀率的空洞卷积加上一个全局平均池化分支把它们拼接起来。这样单层特征就能同时覆盖多个感受野尺度不需要靠结构层级来分散。这里有个很多人忽略的细节ASPP 的膨胀率必须跟着 output stride 一起改。经典配置是 output stride 16 时用[6, 12, 18]如果把 output stride 降到 8 以获得更精细的分割边界膨胀率要相应翻倍成[12, 24, 36]否则感受野相对缩小了一半全局上下文就丢了。这个坑我在第一次做高分辨率分割时踩得很结实改完 output stride 之后模型收敛完全正常但大面积的天空、路面区域开始出现碎裂查了两天才意识到是膨胀率没同步。方案多尺度来源参数量代价训练复杂度典型场景图像金字塔 TTA输入侧0无需训练离线推理、竞赛FPN结构层级中中通用检测ASPP单层多分支低低语义分割可变形卷积采样位置学习低高需专门初始化形变目标、密集预测4.3 可变形卷积把采样位置交给网络学前面的方案里采样位置都是人工设计的固定规则。可变形卷积的思路是给每个采样点学一个偏移量原来在 3×3 网格上采 9 个点现在这 9 个点各自可以在邻域内自由游走。第二版还加了一个调制标量每个采样点乘一个 0 到 1 的权重让网络同时决定在哪里采和这个点重要不重要。这套机制对非刚性形变特别有效比如动物姿态变化、衣服褶皱。用可变形卷积有几个实操经验。第一学习率不能直接用骨干网络的偏移量分支一般要调低我用的是骨干的 0.1 倍否则训练初期偏移会乱飘特征图完全对不上。第二偏移量的初始值必须显式置零不置零的话初始状态就是随机扰动。第三推理时偏移量是逐图计算的所以它对 batch size 敏感度较高训练时如果 batch 太小偏移量的梯度噪声会很大。4.4 多尺度训练与尺度抖动这是最省事、收益最稳定的一招训练的时候就把输入尺度随机化让网络见过各种尺度组合。检测里叫尺度抖动scale jittering常用的短边范围是 640 到 800每个 batch 随机采一个。分类任务里更激进有的会从 0.35 倍到 1.0 倍之间随机裁切再缩放到固定尺寸。原理跟第 3 章讲的频率响应一致网络通过数据见到了同一个物体落在不同频段的样本各个卷积核就学会了在更宽的频段上响应本质上是在训练过程中隐式地做了一次多核融合。尺度抖动有一个坑必须提前说BatchNorm 的统计量会跟着尺度分布一起漂。训练时尺度在 640 到 800 之间跳BN 学到的是这一段的混合统计量推理时如果你固定用 800统计量其实有轻微不匹配如果你用 1200 去推理分布差异更大。缓解办法一是把 BN 换成 GroupNorm代价是精度通常略降二是在模型表达能力足够时直接冻结骨干的 BN三是尺度抖动的范围别开太大抖动幅度超过一倍通常会造成训练不稳定。5. 卷积变体里尺度的变形记上面讲的都是二维图像卷积。当你换到视频、点云、球面数据、图结构上尺度的定义会跟着变很多在图像上成立的直觉会失效。5.1 3D 卷积时间维是一把新尺子把 2D 卷积核在时间维度上扩展就得到 3D 卷积核形状变成k_t × k_h × k_w。感受野公式一字不改只是每个维度各算一份RF_t RF_t (k_t - 1) × S_t RF_h RF_h (k_h - 1) × S_h × r_h关键的设计问题是时间核该开多大直接用 3×3×3 的问题在于时间维和空间维的尺度语义完全不同。空间上相邻的 3 个像素是强相关的时间上相邻的 3 帧几乎一模一样信息冗余极高参数花在这上面很不划算。I3D 那篇工作给的方案很优雅叫膨胀初始化inflation把预训练好的 2D 卷积核在时间维度上复制k_t份再整体除以k_t。除这一步是为了保持输出激活的量级不变——如果直接复制不除输出响应会放大k_t倍接在后面的 BN 层统计量直接失衡训练一开始就会炸。这个技巧让我第一次做视频任务时省掉了从零训练的成本直接用图像预训练权重初始化就收敛了。另一个时间维特有的问题是时间尺度的采样如果视频抽帧太快动作在两个相邻帧之间几乎没有位移时间感受野在大动作上就不够用抽帧太慢快速动作又会产生混叠。这个折中跟图像里降采样之前的抗混叠滤波是同一类问题我的经验是动作识别抽帧步长控制在 4 到 8 之间比较稳检测和跟踪则要更密。5.2 深度可分离与转置卷积分辨率和通道的重新分配深度可分离卷积把标准卷积拆成两步先对每个通道单独做空间卷积depthwise再用 1×1 卷积做通道混合pointwise。参数量的比值是(k²·C_in C_in·C_out) / (k²·C_in·C_out) 1/C_out 1/k²3×3 的核在 C_out 很大时参数量大约压到原来的 1/9。它本身不改变感受野尺度但因为它把空间卷积和通道混合解耦了你可以放心地把 k 开大一点来扩大感受野参数压力不大。这在移动端模型里很实用。顺带说一句1×1 卷积在尺度视角下是一个非常特殊的东西它的感受野是 1×1也就是完全没有空间上下文只在通道维度做线性组合。理解这一点之后很多结构设计就说得通了ResNet 的瓶颈结构先用 1×1 降维是为了在通道上省钱然后把省下来的算力交给 3×3 做空间建模。全局平均池化则相反它是把感受野一次性撑到整张图代价是所有空间位置信息全部丢失。转置卷积负责把特征图放大回来输出尺寸公式out (in - 1) × s - 2p k output_padding它有先天缺陷当 stride 大于 1 且核尺寸不能被 stride 整除时不同的输出位置被覆盖的次数不一样多余的次数就表现为棋盘格状的高频纹理。这个问题的本质还是尺度错配——转置卷积想用一套固定权重同时完成放大和加权求和两件事而这两件事在尺度上是矛盾的。稳妥的做法是先用双线性插值把图放大到目标尺寸再接一个 3×3 卷积做细化把放大和滤波拆开。5.3 图卷积、球面卷积尺度不再是欧氏距离到了非欧几里得数据上尺度这个概念得重新定义这是我觉得最值得琢磨的一块。图卷积里的尺度有两个维度。一是邻域跳数一阶邻居、二阶邻居、k 跳邻居跳数越多覆盖的节点越多相当于感受野越大。二是谱域频率图傅里叶变换把信号投到拉普拉斯矩阵的特征向量上特征值 λ 小对应低频相邻节点取值相近的平滑模式λ 大对应高频。λ0 对应的特征向量是常向量那就是图信号里最大尺度的成分。图卷积网络本质上就是一个在谱域上做低通滤波的操作层数堆多了会过平滑over-smoothing——所有节点的表示趋同这跟图像里堆太多层导致特征图糊成一片是同一个道理。球面卷积面对的是另一个问题球面上不存在一个全局的平移定义你没法把北极附近的卷积核直接搬到赤道。解决思路是限制卷积核为各向同性只依赖两点之间的测地距离。这样尺度就由一个参数控制——球谐函数的带宽也就是保留多少阶的球谐系数。带宽越大空间分辨率越高越能表达高频细节。用等面积像素化方案比如常见的层次化球面分块方法时层级每细分一级分辨率翻倍尺度呈现完美的二次幂阶梯。我在做球形全景图像处理时最大的感受是图像领域里那些靠堆层池化扩大感受野的习惯做法在球面上要格外小心因为池化的下采样在球面上会造成不均衡不同纬度区域的失真程度不一样靠近极点的区域会被严重挤压。业界的处理方式是改用各向同性的核加上带宽控制让尺度变化在球面上保持均匀。5.4 门控卷积、三角卷积把尺度选择交给数据这类变体的共同点是把固定的核换成由数据决定权重的核。门控卷积的典型形式是y (W_f x) ⊙ σ(W_g x)其中 ⊙ 是逐元素乘σ 是 sigmoid。它最初是为了解决图像修复里不规则孔洞的问题普通卷积会把孔洞区域当成有效像素一起加权求和导致周边的颜色和纹理渗进孔洞边缘修复结果看上去像糊了一层。门控分支让网络自己学出这个位置该不该算权重接近 0 的区域就自动被屏蔽了。这跟尺度有什么关系关系在于孔洞的尺寸是会变的。小孔洞靠 3×3 的核就能处理好大孔洞必须靠堆层来扩大有效感受野门控只解决了哪些像素有效解决不了范围够不够。所以实际做修复模型时往往要把门控卷积和空洞卷积配合起来用前者管有效性后者管覆盖范围。我在复现这类模型时的经验是门控分支的初始化不能太激进如果开局的 sigmoid 输出接近 0.5前面几层的梯度会被压得比较厉害收敛明显变慢。至于三角卷积这类命名不同社区给出的实现并不完全一致有的指把卷积核的支撑区域裁成三角形有的指在三角网格上做插值卷积。共通的思想是一致的放弃矩形规则采样改用符合数据几何特征的支撑形状。这种做法的收益在几何形状有明显方向性的场景里比较明显代价是核的索引逻辑变得复杂并行效率下降。如果你手上的实现跟我描述的不一样以源码和原论文的定义为准——这类名词的歧义度比想象中大得多别照着二手博客手撸实现。6. 多尺度任务里我踩过的四个坑理论讲完讲讲真实的翻车现场。这四个坑有一个共同特征报错信息完全正常loss 曲线也很漂亮但精度就是不对排查起来极其耗时。6.1 BN 统计量和输入尺度绑死了第一个项目分类模型在 224 上训到 78% 的准确率换到 448 上推理直接掉到 62%。我当时以为是模型没有尺度泛化能力准备上多尺度训练。后来做了个对照实验把输入缩放到 448 之后网络的中间层激活值分布和训练时差了将近一个标准差。原因就在 BatchNorm。BN 在训练时统计的是当前尺度下经过每一层卷积和池化之后的激活均值方差。你改输入尺度特征图的尺寸跟着变而池化和卷积的组合对空间频率的响应不是尺度不变的所以每一层的激活分布都会偏移。BN 用的是训练时冻结下来的统计量跟推理时的真实分布对不上输出就偏了。解决办法我试过三种一是在推理时用更大的 batch 重新估计 BN 统计量也就是让 BN 跑训练模式但不动权重简单有效代价是需要一批无标注样本过一遍网络二是训练时就做尺度抖动让 BN 见到更大的尺度范围三是换掉 BN。实测第一种最省事在无法重新训练的场景下几乎是唯一选择。6.2 改了输入分辨率anchor 和 stride 忘了改第二个坑更隐蔽。我们把检测模型的输入从 800×1333 提到 1024×1024 以提升小目标召回mAP 反而降了。查了一整天最后发现是输入尺度变了但锚框尺度没变。原来的锚框是按 800 的输入标定的最小组 32×32 对应 stride 8 的特征层。输入提到 1024 之后同一个物理目标在特征图上变大了 1.28 倍原来匹配 32×32 锚框的目标现在应该匹配 41×41 的锚框。锚框没跟着调正样本匹配率就下降了尤其是中等尺寸目标——它们从正好匹配变成了刚好错开被分到了负样本或者被忽略。这类问题的诊断方法很简单统计一下每个锚框组分配到的正样本数量。正常情况下各个尺度的锚框分配应该是相对平衡的。如果某一组的正样本数接近零而相邻组暴涨那基本可以确定是尺度匹配出了问题。改法有两种按比例缩放所有锚框尺寸或者保持锚框不变、改输入时同步缩放标定基准。6.3 转置卷积的棋盘伪影本质是尺度错配分割任务的输出图上出现规律的网格状纹理一开始我以为是训练不足加了 epoch 和更强的数据增强都没用。后来把转置卷积的输出单独可视化才发现是棋盘伪影。原因就是第 5.2 节讲的那个stride 2 的转置卷积核尺寸 4 时输出位置上被覆盖的次数呈 1-2-1-2 的周期性分布核尺寸 3 时更糟会出现明显的 1-1-2-1-1-2 模式。覆盖次数多的位置输出值偏大覆盖次数少的位置偏小这就是棋盘。修复方案按效果排序用双线性上采样加 3×3 卷积最稳我现在的默认选择或者把核尺寸调到能被 stride 整除比如 stride 2 配核尺寸 4覆盖次数变成均匀的 2-2-2最后还可以在转置卷积后面接一层平滑卷积但这是治标。6.4 上采样方式选错小目标直接消失这个坑是我在做人脸检测时踩的。FPN 的上采样默认用了最近邻插值结果小目标召回一直上不去。换成双线性插值之后涨了两个多点。原因是最近邻插值在放大时直接复制像素不产生新的中间值也就不能在频域上做任何平滑。深层特征图里本来就只剩低频信息了最近邻放大之后高频位置是空白的叠加上来的横向连接里的浅层高频信息会被这种块状结构干扰。双线性插值的加权平均相当于一次低通滤波能把块状边界抹平融合效果更自然。反过来的场景也有如果你做的是超分辨率或者需要锐利边缘的任务双线性反而会糊掉细节这时候可以考虑亚像素卷积像素重排它是把通道维度重排成空间维度没有任何插值运算理论上无信息损失。代价是对上采样倍数的灵活性差一些只能做整数倍。7. 一套可以照抄的尺度体检流程前面讲的都是零散的知识点最后给一套我每次接手新模型都会跑的检查流程。三步半天之内能做完能提前发现八成的尺度相关问题。7.1 第一步量化感受野别靠脑补写一个几十行的感受野计算器把骨干网络每一层的核尺寸和 stride 喂进去输出完整的感受野表和下采样倍率。这一步必须在动任何代码之前做完。def rf_table(layers): layers: [(name, kernel, stride), ...] rf, s 1, 1 rows [] for name, k, st in layers: rf rf (k - 1) * s s s * st rows.append((name, k, st, s, rf)) return rows backbone [ (stem_conv, 3, 2), (stem_pool, 2, 2), (stage1_conv1, 3, 1), (stage1_conv2, 3, 1), (stage2_conv1, 3, 2), (stage2_conv2, 3, 1), (stage3_conv1, 3, 2), (stage3_conv2, 3, 1), ] for r in rf_table(backbone): print(f{r[0]:16s} k{r[1]} s{r[2]} - out_stride{r[3]:2d} rf{r[4]})跑完拿到输出对照你的任务问三个问题训练集里目标的典型像素尺寸是多少最小的目标尺寸是多少最后一层特征图的感受野能不能覆盖最大目标我见过太多模型最后一层感受野只有 100 像素训练数据里却有大到 300 像素的目标那网络根本就没法把这些目标当成一个整体看待。感受野的有效值再用梯度法验证一次比公式更接近真实import torch def effective_rf(model, target_cls, in_size224): model.eval() x torch.zeros(1, 3, in_size, in_size, requires_gradTrue) out model(x) if out.dim() 4: h, w out.shape[-2:] score out[0, target_cls, h // 2, w // 2] else: score out[0, target_cls] score.backward() grad x.grad.abs().sum(dim1)[0] # 用 95% 能量占比的范围作为有效感受野 flat grad.flatten().sort(descendingTrue).values cumsum flat.cumsum(0) / flat.sum() n int((cumsum 0.95).sum().item()) 1 side int(n ** 0.5) return side, grad这个函数返回的是覆盖 95% 梯度能量所需的边长比理论感受野小一大截但它才是网络真实用到的范围。我一般会把理论值和有效值都记下来两个数字的比值长期稳定在 0.6 到 0.75 之间如果某次算出比值只有 0.3说明网络深处有大量用了但没用上的参数值得回头看看是不是膨胀率设计有问题。7.2 第二步尺度扫描曲线一把尺子量到底固定权重、固定数据只改推理时的输入尺度从 0.5 倍扫到 2.0 倍记录每个尺度下的精度指标。import torch import torchvision.transforms.functional as TF torch.no_grad() def scale_sweep(model, images, targets, evaluator, scales(0.5, 0.75, 1.0, 1.25, 1.5, 2.0)): results {} for s in scales: preds, gts [], [] for img, tgt in zip(images, targets): h, w img.shape[-2:] nh, nw int(h * s), int(w * s) big TF.resize(img, [nh, nw]) out model(big.unsqueeze(0))[0] # 把预测框坐标映射回原图尺度 out_boxes out[boxes] / s preds.append({boxes: out_boxes, scores: out[scores]}) gts.append(tgt) results[s] evaluator(preds, gts) return results拿到曲线之后重点看三件事。一是峰值位置如果你的模型在 1.0 倍时最好说明训练时的尺度分布和测试集一致没有额外余量如果峰值出现在 1.5 倍说明模型系统性地偏向大目标小目标在 1.0 倍时根本没被充分激活。二是曲线两侧的下降速度往小尺度方向下降太快说明浅层特征的高频建模不足往大尺度方向下降快说明感受野不够。三是同一类目标在不同尺度下的分数方差方差大的类别就是尺度敏感的类别值得针对性加数据。这套扫描做完多尺度 TTA 该用哪几个尺度就一目了然了——只选那些单尺度精度还不错的尺度做融合如果某个尺度的精度已经掉到峰值的一半以下把它加进 TTA 只会拖后腿。这个判断标准比盲目堆尺度实用得多。7.3 第三步把尺度参数整理成一张配置表排查完、调完最后一定要把跟尺度相关的所有参数集中到一处写成一张表放在仓库文档里。这不是形式主义我在接手别人的项目时最快建立直觉的办法就是看这张表。参数作用维度当前值调整依据input_size采样尺度1024×1024小目标最短边不低于 16 像素output_stride采样尺度16与分割边界精度要求折中dilation_rates感受野尺度[6, 12, 18]output_stride16 的标准配置anchor_scales特征尺度[32, 64, 128, 256, 512]与 FPN 各层 stride 对齐scale_jitter训练尺度[640, 800]抖动幅度不超过 1.25 倍tta_scales推理尺度[0.75, 1.0, 1.25]由尺度扫描曲线确定upsampling尺度还原bilinear 3×3 conv避免棋盘伪影这张表的价值在于任何时候模型的输入尺度变了你都能顺着作用维度这一列快速找出所有需要同步修改的参数而不是靠记忆。我们团队后来把这张表的每一项都写成了配置项加断言检查如果input_size改了而anchor_scales没改训练脚本直接报错退出。这个断言拦下过至少三次线上事故成本几乎为零。最后分享一个我觉得最实用的判断习惯任何关于卷积尺度的问题先问是哪个尺度。感受野不够就加膨胀率或者堆层特征尺度没覆盖就加 FPN 层级采样尺度换算错了就统一改锚框和坐标回归的基准。三个问题对应三套完全不同的修法分清楚了大部分所谓的玄学调参其实都是可以推导的工程问题。
返回列表