14 从 MLP 到 LeNet:卷积后为什么还要池化?一文讲清下采样保留了什么、又丢掉了什么

张开发
2026/6/9 10:46:29 15 分钟阅读
14 从 MLP 到 LeNet:卷积后为什么还要池化?一文讲清下采样保留了什么、又丢掉了什么
卷积后为什么还要池化一文讲清下采样保留了什么、又丢掉了什么很多人学卷积神经网络时对卷积层还能勉强建立一点直觉卷积核在图像上滑动每个位置得到一个响应值最后形成特征图但一到池化层很多人就开始懵了池化层是不是只是把图缩小下采样到底在保留什么既然它会丢信息为什么网络里还总要加它它和卷积层到底怎么分工这些内容和后面要讲的 LeNet 又有什么关系如果这些问题没想清楚池化层就很容易被学成一句空话“哦池化嘛就是把特征图变小。”这当然不算错但远远不够。这篇文章就专门把池化层讲清楚不只讲“怎么做”而是重点讲池化层到底在干嘛下采样到底保留了什么下采样又丢掉了什么为什么这种“丢失”有时反而是好事它和 LeNet 的结构到底怎么对应起来一、池化层到底在干嘛先用一句话说清楚可以把池化层理解成在已经得到的特征图上再做一次更粗粒度的信息保留。卷积层更像是在图像局部区域里找模式得到特征图。而池化层通常不会再去重新找新特征而是对这些已经出现的响应做一次整理和压缩保留更有代表性的局部信息同时减小空间尺寸让后续网络更容易处理所以池化层的重点不只是“把图变小”而是把特征图压缩成一个更紧凑的表示。二、什么叫下采样其实就是把特征图“压小”“下采样”这个词说白了就是把原来比较大的特征图变成更小的一张图。比如原来是 4×4 的特征图经过一个 2×2 的池化窗口后可能变成 2×2。它做的事情其实很朴素取一个小区域用某种规则把这个小区域压成一个值再移动到下一个区域重复这个过程最后得到一张更小的输出图所以“下采样”并不是神秘操作本质上就是用更少的位置去概括原来更细的局部信息。三、最大池化和平均池化到底有什么区别池化最常见的两种方式就是1最大池化Max Pooling最大池化的规则非常简单在一个小区域里只保留最大的那个值。它的直觉可以理解成这个区域里最强的响应最重要。如果某个模式在这个局部区域里特别明显那最大池化会优先把它留下来。2平均池化Average Pooling平均池化也很直观在一个小区域里把这些值取平均。它的直觉可以理解成这个区域整体的平均响应水平更重要。相比最大池化平均池化更关注“整体趋势”而不是最强激活点。四、先别急着上 toy 示例直接用一张真实图片看看这次不再用 4×4 或 15×15 的人工矩阵直接用skimage自带的一张真实灰度图来演示。这样更接近真实图像场景也更容易看出池化到底对图像做了什么。代码读取真实图片importnumpyasnpimportmatplotlib.pyplotaspltfromskimageimportdata imgdata.camera().astype(float)plt.figure(figsize(6,6))plt.imshow(img,cmapgray)plt.title(原图)plt.axis(off)plt.show()print(原图 shape:,img.shape)这段代码和前面卷积那篇保持一致图片进入程序之后本质上还是一个二维数组只不过这次不是人为构造的小矩阵而是一张真实灰度图 [15]。五、用代码看看池化前后到底发生了什么下面先写两个最基础的池化函数2×2 最大池化2×2 平均池化defmax_pooling_2x2(x):h,wx.shape outputnp.zeros((h//2,w//2))foriinrange(0,h,2):forjinrange(0,w,2):patchx[i:i2,j:j2]output[i//2,j//2]np.max(patch)returnoutputdefavg_pooling_2x2(x):h,wx.shape outputnp.zeros((h//2,w//2))foriinrange(0,h,2):forjinrange(0,w,2):patchx[i:i2,j:j2]output[i//2,j//2]np.mean(patch)returnoutput max_pooledmax_pooling_2x2(img)avg_pooledavg_pooling_2x2(img)print(最大池化后 shape:,max_pooled.shape)print(平均池化后 shape:,avg_pooled.shape)如果原图是 512×512那么做一次 2×2 池化后就会变成 256×256。这就是最直观的下采样。六、把原图、最大池化、平均池化放在一起看只看数字还不够直接把结果画出来最直观。plt.figure(figsize(14,5))plt.subplot(1,3,1)plt.imshow(img,cmapgray)plt.title(原图)plt.axis(off)plt.subplot(1,3,2)plt.imshow(max_pooled,cmapgray)plt.title(最大池化后)plt.axis(off)plt.subplot(1,3,3)plt.imshow(avg_pooled,cmapgray)plt.title(平均池化后)plt.axis(off)plt.tight_layout()plt.show()把这三张图放在一起看会有几个很直观的现象池化后图像尺寸变小了图像整体结构还在局部细节没有原图那么丰富了最大池化和平均池化的风格不一样这里特别值得观察的是最大池化更像在保留局部区域里最强的信号平均池化更像在保留局部区域整体的平均趋势七、下采样保留了什么保留的不是“所有信息”这是理解池化层最关键的问题。池化层真正保留下来的不是“所有信息”而是局部区域里更有代表性的响应。对最大池化来说保留的是一个小区域里最强的激活也就是“这里最明显的那个响应”如果把它放到真实图片上去理解可以想成这个局部窗口里最突出的像素响应被优先保留下来了。对平均池化来说保留的是这个区域整体的平均响应水平它不强调某一个点而是强调整体。所以如果从感觉上区分最大池化更偏向“抓亮点”平均池化更偏向“看整体”八、下采样又丢掉了什么这些细节会被压缩掉池化层一定会丢信息这一点必须明确。因为你把一个小区域压成一个值本来就不可能把原来的细节全部保留下来。池化层通常会丢掉这些东西1更精细的位置关系比如在一个 2×2 局部区域里强响应在左上还是在右下池化之后这种更细的位置差异会被弱化。2较弱但可能有用的响应最大池化特别明显。它天然更关注“最大的那个值”所以其他较弱响应可能会被忽略。3更高分辨率的局部细节看真实图片尤其明显。池化后整体结构还在但很多边缘细节、纹理细节、微小变化都会变得更粗糙。所以可以把下采样的代价总结成一句话它保留了更粗粒度的重要信息但牺牲了一部分细节。九、既然丢信息为什么池化层反而常常有用很多人一听“丢信息”第一反应就是这是不是坏事不一定。因为模型并不总需要保留所有细节。对于很多分类任务来说更重要的不是某个模式精确出现在第几行第几列而是这个模式大概有没有出现它是不是足够明显它在某个局部区域里是不是存在也就是说模型有时候更关心“有没有”而不是“像素级地在哪”。这就是池化的价值它有意识地牺牲一部分细节换来更紧凑、更稳定的表示。十、卷积负责“找特征”那池化到底负责什么这个地方很多人会混。可以简单这么分卷积层更像在做发现模式提取局部特征生成特征图池化层更像在做压缩特征图保留局部最重要的信息让表示更紧凑所以池化层通常不是独立存在的而是比较自然地接在卷积层后面。卷积层负责“找特征”池化层负责“压特征”。十一、严格一点说池化通常作用在“特征图”上而不是原图上前面为了把池化这件事讲直观我们直接把真实灰度图拿来做了池化。这在教学和可视化上完全没问题。但如果从 CNN 的真实结构来说更准确的表述应该是池化层通常接在卷积层后面作用对象更常见的是卷积产生的特征图而不是原始图片本身。也就是说真实网络里的流程通常更像这样原图 → 卷积层 → 特征图 → 池化层下采样 → 更小的特征图所以你现在看到的“图片池化效果”更适合把它理解成用真实图像先建立池化直觉再迁移到“真实网络里它是在特征图上做同样的事情”这样理解会更顺。十二、放到 LeNet 里看池化层到底在起什么作用这个系列最终目标是LeNet 实战那池化层就不是一个孤立知识点而是在给 LeNet 铺路。因为 LeNet 的前半部分本质上就是把卷积层池化层下采样再卷积再池化按顺序组织起来让一张图片从原始像素一步步变成更适合分类的表示。也就是说卷积层先把局部模式提出来池化层再把这些模式响应做一次压缩和整理最后网络再基于这些更紧凑的表示去做分类所以你前面讲的“下采样到底保留了什么、又丢掉了什么”放到 LeNet 里其实就是在回答LeNet 里的池化层具体在起什么作用答案就是它负责把卷积层已经提出来的局部模式响应压缩一下保留更有代表性的局部信息同时减小空间尺寸。这和前面讲的下采样逻辑是一一对应的。十三、顺便说一句上采样又是什么它和下采样是反过来的吗下采样是把特征图缩小。那上采样就是把特征图放大。但这里要注意上采样不是把下采样丢掉的细节完美恢复回来。因为很多信息在下采样时已经被压缩掉了。上采样更像是在把较小的特征图放大恢复到更高的空间尺寸方便做更细致的输出所以在分类网络里下采样更常见而在图像分割、生成、重建这类任务里上采样会更重要。十四、如果只记一句话池化层到底是什么可以先记这句池化层不是在重新找特征而是在局部区域里把卷积层已经找到的响应做一次压缩尽量保留重要信息同时减少空间尺寸。十五、完整代码真实图片 最大池化 平均池化下面把这篇文章里用到的代码整理成一版可以直接运行的完整示例。importnumpyasnpimportmatplotlib.pyplotaspltfromskimageimportdata# # 1. 读取真实灰度图# imgdata.camera().astype(float)print(原图 shape:,img.shape)# # 2. 定义池化函数# defmax_pooling_2x2(x):h,wx.shape outputnp.zeros((h//2,w//2))foriinrange(0,h,2):forjinrange(0,w,2):patchx[i:i2,j:j2]output[i//2,j//2]np.max(patch)returnoutputdefavg_pooling_2x2(x):h,wx.shape outputnp.zeros((h//2,w//2))foriinrange(0,h,2):forjinrange(0,w,2):patchx[i:i2,j:j2]output[i//2,j//2]np.mean(patch)returnoutput# # 3. 执行池化# max_pooledmax_pooling_2x2(img)avg_pooledavg_pooling_2x2(img)print(最大池化后 shape:,max_pooled.shape)print(平均池化后 shape:,avg_pooled.shape)# # 4. 可视化# plt.figure(figsize(14,5))plt.subplot(1,3,1)plt.imshow(img,cmapgray)plt.title(原图)plt.axis(off)plt.subplot(1,3,2)plt.imshow(max_pooled,cmapgray)plt.title(最大池化后)plt.axis(off)plt.subplot(1,3,3)plt.imshow(avg_pooled,cmapgray)plt.title(平均池化后)plt.axis(off)plt.tight_layout()plt.show()认真看才能开出差异尴尬十六、最后把池化层压缩成 5 句话如果把池化层压缩成最关键的几个点可以记住这些池化层最常见的作用是下采样也就是把特征图变小。它保留的是局部区域里更有代表性的响应比如最大池化保留最强响应平均池化保留整体平均水平。它丢掉的是更细的位置关系和部分细节因为一个区域被压成一个值信息不可能完整保留。这种丢失不一定是坏事因为很多任务更关心“模式有没有出现”而不是所有细节都完整保留。这和 LeNet 是直接相关的LeNet 里的池化层本质上就是在做这种下采样 。

更多文章