深度学习进阶(九)池化技术的初步改进:RoI Pooling
<p>在上一篇里,我们已经完整介绍了 Swin Transformer 的模型逻辑,知道了:<strong>Swin Transformer 的核心,并不是简单地“模仿 CNN”,而是在保留归纳偏置的同时,让建模方式更加灵活。</strong></p><p>换句话说,它做的事情是:</p>
<blockquote>
<p><strong>在“约束”与“自由”之间找到一个平衡点。</strong></p>
</blockquote>
<p>而在上一篇的末尾,我们也提到:当 Swin 在学习“约束”的同时,CNN 也在学习“自由”。</p>
<p>实际上,CNN 体系内部早就已经出现了这种“<strong>让结构变得更灵活</strong>”的趋势并发展出了繁多的落地技术。<br>
因此,在正式展开“ CNN 的 Transformer 化 ” 前,为了不让内容过于割裂,还需要展开一些前置内容,引入一些 CNN 内的一些相关改进来简单<strong>过渡</strong>。<br>
本篇的内容关于<strong>池化技术的初步改进:RoI Pooling</strong>。</p>
<h1 id="1-基本池化-pooling">1. 基本池化 Pooling</h1>
<p>先回顾一下最基本的池化操作,以最常见的最大池化(Max Pooling)为例:</p>
<p></p><div class="math display">\[y(i,j) = \max_{(m,n)\in \mathcal{R}} x(i+m, j+n)
\]</div><p></p><p>这里的 <span class="math inline">\(\mathcal{R}\)</span> 表示池化窗口,我们用最大池化来筛选“最强响应”。</p>
<p>此外,还可以使用平均池化来保留“整体信息”:</p>
<p></p><div class="math display">\[y(i,j) = \frac{1}{|\mathcal{R}|} \sum_{(m,n)\in \mathcal{R}} x(i+m, j+n)
\]</div><p></p><p>不再展开过多细节,这里附上我们之前介绍池化的内容:池化操作与卷积中的反向传播。</p>
<p><strong>总的来说,Pooling 的本质是通过下采样来压缩信息,从而减少计算量。</strong><br>
同时,由于其在局部窗口内进行聚合,它实际上引入了一种重要的归纳偏置:</p>
<blockquote>
<p><strong>局部不变性先验:在小范围内的变化不会显著影响输出。</strong></p>
</blockquote>
<p>也正因为这一点,在不显著改变语义的前提下,Pooling 还可以间接扩大后续网络的感受野。</p>
<p>当然,最基础的 Pooling 也存在明显的局限:</p>
<blockquote>
<p><strong>采样位置是完全固定的。</strong></p>
</blockquote>
<p>换句话说,它的行为是“刚性的”:窗口位置固定、采样方式固定、<strong>不随输入内容发生变化。</strong><br>
总结一下就是<strong>不够灵活</strong>,这在分类任务中通常问题不大,但在<strong>目标检测</strong>等任务中就会变得非常明显。<br>
比如目标刚好落在池化窗口边界或发生轻微形变或偏移,此时的 Pooling 可能会丢失部分关键信息甚至直接削弱目标响应。</p>
<p>问题的关键在于:</p>
<blockquote>
<p><strong>Pooling 仍然是在“整张特征图上使用统一规则”,但有时我们真正关心的是“某一个局部目标”。</strong></p>
</blockquote>
<p>因此,Pooling 的第一步改进就出现在检测任务中,我们继续。</p>
<h1 id="2-region-of-interest-pooling">2. Region of Interest Pooling</h1>
<p><strong>Region of Interest Pooling 简称 RoI Pooling</strong>,它起源于 15 年的论文:<strong><em>Fast R-CNN</em></strong>。<br>
Fast R-CNN 是一个目标检测模型,它首次系统性提出了 RoI Pooling,<strong>用于将任意大小的候选框映射为固定尺寸特征。</strong></p>
<p>同样,如果你对检测任务本身的相关概念有些遗忘了,可以在这里回顾:目标定位与特征点检测。<br>
首先要说明的是,Fast R-CNN 并不像现在主流的 YOLO 一样是端到端模型,它的目标检测逻辑是<strong>先使用物理方法得到候选框,再把候选框映射输入网络进行分类</strong>,是一个两阶段模型。<br>
我们具体展开其逻辑如下:</p>
<h2 id="21-候选框映射">2.1 候选框映射</h2>
<p><img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134603862-640873830.png" alt="image.png" loading="lazy"><br>
如图所示,<strong>第一步就是把物理方法生成的候选框在原图中的相对位置映射到原图卷积后得到的特征图上。</strong></p>
<p>这个映射本质上依赖于一个非常关键的参数: <strong>空间缩放比例(spatial scale)</strong>。<br>
如果原图尺寸为 <span class="math inline">\(H \times W\)</span>,特征图尺寸为 <span class="math inline">\(H' \times W'\)</span>,那么:</p>
<p></p><div class="math display">\[\text{scale}_x = \frac{W'}{W}, \quad
\text{scale}_y = \frac{H'}{H}
\]</div><p></p><p>假设原图上的 RoI 为:</p>
<p></p><div class="math display">\[(x_1, y_1, x_2, y_2)
\]</div><p></p><p><span class="math inline">\((x_1, y_1)\)</span>是<strong>左上角坐标</strong>,<span class="math inline">\((x_2, y_2)\)</span>是<strong>右下角坐标</strong>。<br>
那么映射后就是:</p>
<p></p><div class="math display">\[x_1' = x_1 \cdot \text{scale}_x,\quad
y_1' = y_1 \cdot \text{scale}_y
\]</div><p></p><p></p><div class="math display">\[x_2' = x_2 \cdot \text{scale}_x,\quad
y_2' = y_2 \cdot \text{scale}_y
\]</div><p></p><p>我们再看看图里的实例:</p>
<ul>
<li>原图:<span class="math inline">\(128 \times 128\)</span></li>
<li>feature map:<span class="math inline">\(6 \times 6\)</span><br>
则:</li>
</ul>
<p></p><div class="math display">\[\text{scale} = \frac{6}{128} \approx 0.046875
\]</div><p></p><p>假设原图中有一个候选框:</p>
<p></p><div class="math display">\[(32,\ 32,\ 96,\ 96)
\]</div><p></p><p>映射到 feature map:</p>
<table>
<thead>
<tr>
<th>坐标</th>
<th>原图值</th>
<th>计算公式</th>
<th>feature map 值</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="math inline">\(x_1\)</span></td>
<td>32</td>
<td><span class="math inline">\(32 \times \frac{6}{128}\)</span></td>
<td><span class="math inline">\(1.5\)</span></td>
</tr>
<tr>
<td><span class="math inline">\(y_1\)</span></td>
<td>32</td>
<td><span class="math inline">\(32 \times \frac{6}{128}\)</span></td>
<td><span class="math inline">\(1.5\)</span></td>
</tr>
<tr>
<td><span class="math inline">\(x_2\)</span></td>
<td>96</td>
<td><span class="math inline">\(96 \times \frac{6}{128}\)</span></td>
<td><span class="math inline">\(4.5\)</span></td>
</tr>
<tr>
<td><span class="math inline">\(y_2\)</span></td>
<td>96</td>
<td><span class="math inline">\(96 \times \frac{6}{128}\)</span></td>
<td><span class="math inline">\(4.5\)</span></td>
</tr>
</tbody>
</table>
<p>所以得到:</p>
<p></p><div class="math display">\[(1.5,\ 1.5,\ 4.5,\ 4.5)
\]</div><p></p><p><strong>这里就有一个问题:要不要取整?</strong><br>
在 <strong>RoI Pooling(Fast R-CNN 原始做法)</strong> 中,会进行直接取整,具体如下:</p>
<ul>
<li>左上角:向下取整 <span class="math inline">\(\lfloor x_1' \rfloor\)</span>。</li>
<li>右下角:向上取整 <span class="math inline">\(\lceil x_2' \rceil\)</span>。</li>
</ul>
<p>逻辑还是比较清晰的:<strong>扩大一点范围,保证不漏。</strong> 但同样,这也带来了误差。<br>
由此得到最终结果:</p>
<p></p><div class="math display">\[(1.5,1.5,4.5,4.5) \rightarrow (1,1,5,5)
\]</div><p></p><p>这就是 RoI Pooling 的第一步。</p>
<h2 id="22-划分子区域">2.2 划分子区域</h2>
<p>现在,我们得到了将候选框映射到特征图中的区域,下一步是<strong>把该区域划分为 <span class="math inline">\(k \times k\)</span> 个子区域。</strong><br>
<img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134605492-769978170.png" alt="image.png" loading="lazy"></p>
<p>要说明的是,这里并不是把区域切成 <span class="math inline">\(k \times k\)</span> 个正方形,而是<strong>按宽和高分别等分,形成网格</strong>。<br>
对最终划分好的每个子网格,我们就叫它一个 <strong>bin</strong> 。</p>
<p>展开来说,设当前 RoI 为:</p>
<p></p><div class="math display">\[(x_1', y_1', x_2', y_2')
\]</div><p></p><p>则:</p>
<p></p><div class="math display">\[w = x_2' - x_1', \quad h = y_2' - y_1'
\]</div><p></p><p>划分为 <span class="math inline">\(k \times k\)</span> 后,<strong>一个 bin 的尺寸</strong>就是:</p>
<p></p><div class="math display">\[bin_{width} = \frac{w}{k},\quad bin_{height} = \frac{h}{k}
\]</div><p></p><p>继续上一节的例子:</p>
<p></p><div class="math display">\[(1,\ 1,\ 5,\ 5)
\]</div><p></p><p>所以:</p>
<p></p><div class="math display">\[w = 4,\quad h = 4
\]</div><p></p><p>设 <span class="math inline">\(k = 2\)</span> 则:</p>
<p></p><div class="math display">\[bin_{width} = 2,\quad bin_{height} = 2
\]</div><p></p><p>但实际上,这只是一种最理想的请况,新的问题是:</p>
<blockquote>
<p><strong>候选框不是正方形怎么办?最终 bin 的尺寸不是整数怎么办?</strong></p>
</blockquote>
<p>第一个问题只要跳出思维定视就好,答案是:<strong>一样切</strong>。<br>
关键在于第二个问题:<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134603422-1892048372.png" alt="image.png" loading="lazy"><br>
如图所示,在真实情况下:</p>
<p></p><div class="math display">\[\frac{w}{k},\ \frac{h}{k}
\]</div><p></p><p><strong>很可能不是整数。</strong><br>
例如图中的:</p>
<p></p><div class="math display">\[h = 5,\ k = 2 \Rightarrow bin_{height} = 2.5
\]</div><p></p><p>而原始 RoI Pooling 给出的答案是和上部分是一样的:</p>
<blockquote>
<p><strong>每个 bin 的边界都会再次取整。</strong></p>
</blockquote>
<p>其实现方式是,<strong>对于每一个方向</strong>:</p>
<ul>
<li>起点:向下取整 <span class="math inline">\(\lfloor \cdot \rfloor\)</span> 。</li>
<li>终点:向上取整 <span class="math inline">\(\lceil \cdot \rceil\)</span> 。</li>
</ul>
<p>例如两 bin 的<strong>高度方向</strong>:</p>
<ul>
<li>bin1:</li>
</ul>
<p></p><div class="math display">\[ \rightarrow
\]</div><p></p><ul>
<li>bin2:</li>
</ul>
<p></p><div class="math display">\[ \rightarrow
\]</div><p></p><p>这里的关键点是:<strong>RoI Pooling 的 bin 划分是在连续空间完成的,而取整是在每个 bin 内独立进行的。</strong></p>
<p>但这种取整策略也会带来相应的问题:<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134607707-1506554308.png" alt="image.png" loading="lazy"><br>
因此,这种取整策略便让 RoI Pooling 的逻辑中出现了局限。<br>
我们先继续整体逻辑,再专门来阐述这个问题。</p>
<h2 id="23-区域池化">2.3 区域池化</h2>
<p>最后一步就较为简单了:<strong>在每个划分好的 bin 内进行最大或平均池化得到最终结果</strong>。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134604006-1354847953.png" alt="image.png" loading="lazy"></p>
<p>这便是 RoI Pooling 的完整逻辑和其在检测任务中发挥的作用:</p>
<blockquote>
<p><strong>任意大小的候选框都可以通过 bin 的划分映射为固定尺寸特征,从而统一维度用于判定。</strong></p>
</blockquote>
<p>到这里,你可能会有这样一个问题:</p>
<blockquote>
<p><strong>这不就是在图像里挑了一块进行普通池化吗?并没有改变池化的逻辑吧?归根结底只是池化在检测任务中的特化,在哪里体现出“更灵活”了?</strong></p>
</blockquote>
<p>我们就这个问题来总述一下这段逻辑:</p>
<h1 id="3-roi-pooling-的意义和局限">3. RoI Pooling 的意义和局限</h1>
<h2 id="31-什么是更灵活的池化">3.1 什么是“更灵活”的池化?</h2>
<p>如果从计算流程上看,RoI Pooling 似乎只是做了一件很简单的事情:</p>
<blockquote>
<p><strong>从 feature map 中截取一个区域,然后做一次固定尺寸的池化。</strong></p>
</blockquote>
<p>但它真正的意义并不在“操作复杂度”,而在于:</p>
<blockquote>
<p><strong>它第一次在 CNN 中引入了“基于目标的特征对齐机制”。</strong></p>
</blockquote>
<p>展开来说,在传统 Pooling 中,我们默认:<strong>池化窗口是预先固定的,与输入内容无关。</strong><br>
这时的池化本质就是一个“<strong>空间无关的压缩操作</strong>”。</p>
<p>而 RoI Pooling 做了一件关键变化:</p>
<blockquote>
<p><strong>池化窗口不再固定,而是由“候选目标(RoI)”决定。</strong></p>
</blockquote>
<p>从这个角度看,“更灵活”体现在:Pooling 的位置由输入决定、 Pooling 的范围随目标变化 。</p>
<p>因此,它的“灵活性”本质是:<strong>从“固定操作”变成了“针对目标的操作”。</strong></p>
<p><strong>当然,这只是针对池化的初步改进,就现在的眼光来看,它的逻辑并不复杂,在所有 CV 任务中的泛用性也并不强。</strong><br>
但这是一个“<strong>起点</strong>”,我们在其后续的改进中就能越发体会到“灵活”二字。<br>
以此,我们来展开 RoI Pooling 的局限问题。</p>
<h2 id="32-roi-pooling-的对齐问题">3.2 RoI Pooling 的对齐问题</h2>
<p>RoI Pooling 的逻辑中存在一个非常根本的问题:<strong>它并没有做到完善的几何对齐。</strong></p>
<p>首先,RoI Pooling 至少经历了两次对齐问题:RoI 映射时的取整和 bin 划分时的取整。<br>
<img src="https://img2024.cnblogs.com/blog/3708248/202604/3708248-20260419134606953-1696088766.png" alt="image.png" loading="lazy"></p>
<p>如图所示,原本连续的目标区域,在进入特征图后被“折断”了两次。最终采样的位置并不一定与真实 RoI 对齐。<br>
而这种偏差会让最终池化得到的是:<strong>一个“近似区域的统计值”,而不是“精确区域的响应”</strong>。<br>
用专业名词来说,这叫:</p>
<blockquote>
<p><strong>量化误差,即连续值被映射到离散值时产生的误差。</strong></p>
</blockquote>
<p>这种误差在检测任务中会被放大,最终的效果就是:</p>
<blockquote>
<p><strong>模型知道“这里有东西”,但不知道“边界到底在哪里”。因为我们没有提取到精准的边界。</strong></p>
</blockquote>
<p>此外,pooling 抹平空间细节,对<strong>小目标/边界的检测</strong>也并不友好。</p>
<p>针对这些问题,自然就会有相应的改进,我们下一篇再详细展开。</p><br><br>
来源:https://www.cnblogs.com/Goblinscholar/p/19890795
頁:
[1]