CNN基础:卷积与池化

深度学习专题 · 卷积神经网络的核心运算

专题:深度学习系统学习

关键词:CNN, 卷积, 池化, 感受野, 权值共享, 空洞卷积, 转置卷积, 特征图

一、卷积运算基础

卷积(Convolution)是卷积神经网络(CNN)中最核心的运算操作。与传统的全连接层不同,卷积层通过卷积核在输入数据上滑动并进行点积运算,从而提取局部特征。这种设计极大地减少了参数量,同时保留了空间结构信息。

1.1 什么是卷积运算

数学上,二维卷积操作定义为卷积核与输入特征图对应位置的元素相乘后求和。给定一个输入矩阵 I 和一个卷积核 K,在位置 (i, j) 处的卷积输出为:

S(i,j) = (I * K)(i,j) = ∑mn I(i+m, j+n) · K(m,n)

在实际的深度学习框架中,我们通常实现的是"互相关"(cross-correlation)运算,它与严格数学定义上的卷积略有区别(不翻转卷积核),但出于习惯,仍被称为"卷积"。

1.2 卷积核与滑动窗口

卷积核(Kernel/Filter)是一个小尺寸的权重矩阵,常见的尺寸有 3×3、5×5、7×7。卷积核在输入上从左到右、从上到下逐步滑动,每次滑动计算一次点积,最终生成一张特征图(Feature Map)。每个卷积核负责检测某一种特定的局部模式,如边缘、纹理、颜色斑块等。

直观理解:可以将卷积核想象成一个"模式探测器"。当输入中出现了与卷积核相似的局部模式时,该位置的响应值就会很高。多个卷积核叠加使用,就可以检测多种不同的特征。

1.3 特征图的生成

一张输入图像经过一个卷积核的处理,会生成一张二维特征图。假设输入尺寸为 H×W×C(高度×宽度×通道数),使用 K 个卷积核,每个卷积核尺寸为 kh×kw×C,则输出特征图的尺寸为 H'×W'×K,其中每个通道对应一个卷积核的响应结果。

1.4 单通道卷积的 NumPy 实现

import numpy as np def conv2d_single(image, kernel, stride=1, padding=0): """ 单通道二维卷积操作 Args: image: 输入图像 (H, W) kernel: 卷积核 (kh, kw) stride: 步长 padding: 填充像素数 Returns: 输出特征图 """ if padding > 0: image = np.pad(image, padding, mode='constant') H, W = image.shape kh, kw = kernel.shape out_h = (H - kh) // stride + 1 out_w = (W - kw) // stride + 1 output = np.zeros((out_h, out_w)) for i in range(out_h): for j in range(out_w): region = image[i*stride:i*stride+kh, j*stride:j*stride+kw] output[i, j] = np.sum(region * kernel) return output

1.5 多通道卷积

实际应用中的输入图像通常是多通道的(例如RGB图像有3个通道),此时卷积核的深度必须与输入的通道数一致。每个卷积核对所有输入通道分别做卷积,然后将结果逐元素相加,再加上偏置项,得到一个输出通道。如果有 K 个卷积核,则输出 K 个通道。

def conv2d_multi_channel(image, kernels, bias, stride=1, padding=0): """ 多通道卷积 Args: image: 输入图像 (C, H, W) kernels: 卷积核集合 (K, C, kh, kw) bias: 偏置 (K,) stride, padding: 步长与填充 Returns: 输出特征图 (K, out_h, out_w) """ C, H, W = image.shape K, _, kh, kw = kernels.shape if padding > 0: image = np.pad(image, ((0,0), (padding,padding), (padding,padding)), mode='constant') out_h = (H - kh) // stride + 1 out_w = (W - kw) // stride + 1 output = np.zeros((K, out_h, out_w)) for k in range(K): for c in range(C): for i in range(out_h): for j in range(out_w): region = image[c, i*stride:i*stride+kh, j*stride:j*stride+kw] output[k, i, j] += np.sum(region * kernels[k, c]) output[k] += bias[k] return output

要点总结:① 卷积核的深度必须等于输入通道数;② 输出通道数等于卷积核的个数;③ 每个输出通道融合了所有输入通道的信息;④ 偏置项为每个输出通道增加一个可学习的偏移量。

二、卷积的变体

在基础卷积之上,研究者们提出了多种卷积变体,以满足不同场景下的需求。这些变体在参数量控制、感受野扩展、分辨率保持等方面各有优势。

2.1 1×1 卷积

1×1 卷积(Network in Network)是一种特殊的卷积,其卷积核尺寸为 1×1。虽然它不捕捉空间局部信息,但能实现跨通道的信息融合和维度变换。主要用途包括:降维(减少通道数以减少后续计算量)、升维(增加通道数以丰富特征表达)、跨通道交互(以极小的参数量实现通道间的线性组合)。在 ResNet 的瓶颈模块(Bottleneck)中,1×1 卷积被广泛用于先降维再升维,大幅降低计算量。

# 1x1 卷积示例:将 256 通道降为 64 通道 # 输入 (batch, 256, 56, 56), 输出 (batch, 64, 56, 56) import torch.nn as nn conv_1x1 = nn.Conv2d( in_channels=256, out_channels=64, kernel_size=1, stride=1, padding=0 )

2.2 空洞卷积(Dilated/Atrous Convolution)

空洞卷积通过在卷积核元素之间插入"空洞"(即间隔),在不增加参数量的情况下扩大感受野。空洞率(Dilation Rate)r 表示卷积核元素之间的间隔,r=1 时即为标准卷积,r=2 时表示相邻权重之间间隔1个像素。空洞卷积在语义分割任务中非常常用(如 DeepLab 系列),因为它可以在不降低特征图分辨率的情况下获取更大范围的上下文信息。

# 空洞卷积示例:3x3 卷积核,空洞率=2,感受野相当于 5x5 conv_dilated = nn.Conv2d( in_channels=64, out_channels=128, kernel_size=3, dilation=2, padding=2, stride=1 ) # padding 需设置为 dilation * (kernel_size - 1) // 2 以保持尺寸
空洞率 (r)有效卷积核尺寸感受野参数量
13×33×39
25×55×59
37×77×79
49×99×99

2.3 转置卷积(Transposed Convolution)

转置卷积(又称反卷积/Deconvolution)主要用于上采样,即从小尺寸的特征图得到大尺寸的输出。它常用于语义分割(如FCN、U-Net)和生成模型(如GAN的生成器)。需要注意的是,转置卷积并不是卷积的逆运算,而是实现可学习的上采样操作。其核心思想是将输入中的每个元素与卷积核相乘并按步长排放到输出中,重叠部分进行累加。

# 转置卷积示例:上采样 2 倍 conv_transpose = nn.ConvTranspose2d( in_channels=128, out_channels=64, kernel_size=4, stride=2, padding=1 ) # kernel_size=4, stride=2, padding=1 是标准的 2x 上采样配置 # 输出尺寸 = 输入尺寸 * stride

2.4 分组卷积(Group Convolution)

分组卷积将输入通道分成若干组,每组独立进行卷积。最早出现在 AlexNet 中(受限于GPU内存),后被 ShuffleNet、MobileNet 等轻量级网络广泛采用。分组卷积的参数量为标准卷积的 1/g(g为分组数),但组与组之间缺乏信息流通,因此常配合通道混洗(Channel Shuffle)使用。

可分离卷积(Depthwise Separable Convolution):是分组卷积的极端情况——先对每个输入通道做独立卷积(Depthwise Convolution,分组数=输入通道数),再用 1×1 卷积融合通道(Pointwise Convolution)。MobileNet 系列即基于此设计,参数量和计算量远低于标准卷积。

卷积变体选择指南:需要跨通道融合用 1×1 卷积;需要扩大感受野但不降分辨率用空洞卷积;需要上采样用转置卷积;需要轻量模型用深度可分离卷积。

三、池化操作

池化(Pooling)是 CNN 中另一个关键操作,主要用于降低特征图的空间尺寸、减少参数量和计算量、并增强平移不变性。池化操作在空间维度上对局部区域进行下采样,是保持最显著特征、丢弃次要信息的有效手段。

3.1 最大池化(Max Pooling)

最大池化在滑动窗口内取最大值作为输出。它对特征的局部强度变化不敏感,特别适合提取边缘、纹理等"最显著"特征。池化窗口通常为 2×2,步长为 2,可将特征图尺寸减半。最大池化是最常用的池化方式,在 LeNet、AlexNet、VGGNet 等经典网络中均有使用。

def max_pooling(feature_map, pool_size=2, stride=2): """最大池化的 NumPy 实现""" H, W = feature_map.shape out_h = (H - pool_size) // stride + 1 out_w = (W - pool_size) // stride + 1 output = np.zeros((out_h, out_w)) for i in range(out_h): for j in range(out_w): region = feature_map[i*stride:i*stride+pool_size, j*stride:j*stride+pool_size] output[i, j] = np.max(region) return output

3.2 平均池化(Average Pooling)

平均池化取滑动窗口内的平均值作为输出。它对整体区域的响应更平滑,在保留背景信息方面优于最大池化。在网络的最后阶段,全局平均池化(Global Average Pooling)被广泛用于替代全连接层,如 ResNet、GoogLeNet 等。

# PyTorch 中的池化层 import torch.nn as nn # 最大池化 max_pool = nn.MaxPool2d(kernel_size=2, stride=2) # 平均池化 avg_pool = nn.AvgPool2d(kernel_size=2, stride=2) # 全局平均池化 global_avg_pool = nn.AdaptiveAvgPool2d(output_size=(1, 1))

3.3 全局池化(Global Pooling)

全局池化将整个特征图压缩成一个值(全局最大池化或全局平均池化)。它的最大优势是不需要引入额外参数,且不受输入尺寸限制。在分类网络的最后阶段,全局平均池化将每个特征图压缩为一个数值,直接喂入 softmax 分类器,避免了全连接层的大量参数和过拟合风险。

3.4 重叠池化(Overlapping Pooling)

当池化窗口的步长小于窗口尺寸时,相邻池化窗口之间会发生重叠,称为重叠池化。AlexNet 中使用了 stride=2、pool_size=3 的重叠池化,相比非重叠池化(stride=2、pool_size=2),能略微提升分类精度并缓解过拟合。

3.5 空间金字塔池化(SPP)

空间金字塔池化(Spatial Pyramid Pooling)由何恺明等人提出,解决了 CNN 需要固定输入尺寸的问题。SPP 在特征图的最后一级使用多个不同尺寸的池化窗口(如 1×1、2×2、4×4),将不同大小的特征图统一池化为固定长度的特征向量,拼接后送入全连接层。这使得网络可以接受任意尺寸的输入图像。

# 空间金字塔池化的 PyTorch 实现(简化版) class SPPLayer(nn.Module): def __init__(self, levels=[1, 2, 4]): super().__init__() self.levels = levels def forward(self, x): N, C, H, W = x.shape features = [] for level in self.levels: kernel_size = (H // level, W // level) stride = kernel_size pool = nn.AdaptiveAvgPool2d((level, level)) feat = pool(x).view(N, C, -1) features.append(feat) return torch.cat(features, dim=-1)
池化类型特点典型窗口/步长适用场景
最大池化保留最显著特征,对噪声鲁棒2×2 / 2特征提取、分类网络
平均池化保留整体信息,更平滑2×2 / 2背景信息保留
全局平均池化无参数,任意输入尺寸自适应分类层前替代FC
重叠池化相邻窗口重叠3×3 / 2AlexNet等早期网络
空间金字塔池化多尺度,支持任意输入多级SPPNet、目标检测

经验法则:(1)最大池化通常优于平均池化,因为它对特征位置的微小变化更鲁棒;(2)在网络早期(靠近输入)使用池化下采样以降低分辨率;(3)在网络后期使用全局平均池化代替全连接层可大幅减少参数量;(4)池化窗口 2×2 步长 2 是标准的"无损"下采样配置(无信息损失,只有分辨率降低)。

四、填充与步长

填充(Padding)和步长(Stride)是控制卷积输出尺寸的两个关键超参数,直接影响特征图的分辨率和信息的边界保留程度。

4.1 填充(Padding)

填充是在输入特征图的边界周围补上额外的像素值(通常为0),用来控制输出特征图的空间尺寸。填充的主要作用包括:保持边界信息不被丢弃(让卷积核也能作用于图像边缘像素);控制输出尺寸使其与输入一致(Same Padding);在转置卷积中控制输出尺寸的整倍数关系。

4.2 步长(Stride)

步长是卷积核每次滑动的像素数。步长越大,输出特征图的空间尺寸越小。步长 > 1 时可作为下采样手段(代替池化)。在实践中,stride=1 是最常用的设置(保留尺寸),stride=2 可用于在卷积的同时做下采样。

4.3 输出尺寸计算公式

给定输入尺寸 Hin × Win,卷积核尺寸 k,填充 p,步长 s,输出尺寸为:

Hout = ⌊(Hin + 2p - k) / s⌋ + 1

Wout = ⌊(Win + 2p - k) / s⌋ + 1

def conv_output_size(input_size, kernel_size, padding=0, stride=1, dilation=1): """ 计算卷积输出尺寸(支持空洞卷积) 有效卷积核尺寸 = kernel_size + (kernel_size - 1) * (dilation - 1) """ effective_kernel = kernel_size + (kernel_size - 1) * (dilation - 1) output_size = (input_size + 2 * padding - effective_kernel) // stride + 1 return output_size # 示例:计算各种配置下的输出尺寸 print("3x3 conv, pad=1, stride=1:", conv_output_size(32, 3, 1, 1)) # 32 (Same) print("3x3 conv, pad=0, stride=1:", conv_output_size(32, 3, 0, 1)) # 30 (Valid) print("3x3 conv, pad=1, stride=2:", conv_output_size(32, 3, 1, 2)) # 16 (下采样) print("5x5 conv, pad=2, stride=1:", conv_output_size(32, 5, 2, 1)) # 32 (Same)

4.4 几种典型配置的输出尺寸

输入尺寸卷积核填充步长输出尺寸说明
32×323×30130×30Valid, 尺寸略微缩小
32×323×31132×32Same, 尺寸不变
32×323×31216×16下采样, 尺寸减半
32×325×52132×32Same, 大核保持尺寸
32×323×3 (d=2)2132×32空洞卷积, 保持尺寸
32×323×30215×15下采样, 向下取整

填充与步长设计原则:① 保持特征图尺寸时使用 Same Padding(pad=(k-1)//2, stride=1);② 下采样时通常使用 stride=2 或池化层;③ 空洞卷积的 padding 需设置为 dilation × (k-1)//2 以保持尺寸;④ 步长和池化是两种不同的下采样方式,CNN 中常结合使用。

五、感受野

5.1 感受野的定义

感受野(Receptive Field, RF)是指特征图上某个像素点对应到原始输入图像上的区域大小。理解感受野对于设计网络结构至关重要——它决定了每个神经元能"看到"多大的输入范围。

浅层网络的感受野较小,提取的是边缘、纹理等局部特征;深层网络的感受野较大,可以捕捉物体的整体轮廓和语义信息。感受野的大小与卷积核尺寸、空洞率、层数等因素相关。

5.2 感受野的计算

感受野的计算遵循从后向前的递推方式。假设第 l 层的感受野为 RFl,第 l-1 层的感受野为 RFl-1,第 l-1 层的卷积核尺寸为 kl-1,步长为 sl-1

RFl = RFl-1 + (kl-1 - 1) × ∏i=0l-2 si

其中乘积项累乘了之前所有层的步长。从这个公式可以看出,越靠前的层的步长对感受野的放大效应越显著(因为它在乘积中出现的次数最多)。

def compute_receptive_field(layer_configs): """ 计算每层的感受野 layer_configs: [(kernel_size, stride, dilation), ...] """ rf = 1 stride_product = 1 for i, (k, s, d) in enumerate(layer_configs): effective_k = k + (k - 1) * (d - 1) # 空洞卷积等效尺寸 rf = rf + (effective_k - 1) * stride_product stride_product *= s print(f"Layer {i+1}: RF = {rf}, stride_product = {stride_product}") return rf # VGG16 的前几层感受野计算 configs = [ (3, 1, 1), # conv1_1: RF = 3 (3, 1, 1), # conv1_2: RF = 5 (2, 2, 1), # pool1: RF = 6 (3, 1, 1), # conv2_1: RF = 10 (3, 1, 1), # conv2_2: RF = 14 (2, 2, 1), # pool2: RF = 16 ] compute_receptive_field(configs)

5.3 堆叠小卷积核增大感受野

VGGNet 的一个重要贡献是证明了堆叠多个 3×3 小卷积核可以替代一个更大的卷积核,同时获得更大的感受野和更少的参数量。例如,两个 3×3 卷积(stride=1)堆叠得到 5×5 感受野,三个 3×3 堆叠得到 7×7 感受野。

堆叠配置等效感受野参数量对比大核参数量
2× (3×3)5×52×9×C² = 18C²1×25×C² = 25C²
3× (3×3)7×73×9×C² = 27C²1×49×C² = 49C²
4× (3×3)9×94×9×C² = 36C²1×81×C² = 81C²

堆叠小卷积核的额外好处还包括:更多非线性激活(增加模型表达能力)、更少的参数量(正则化效果)、更深的网络结构(更强的特征抽象能力)。

5.4 空洞卷积与感受野

空洞卷积可以有效扩大感受野而不增加参数量和计算量。对于空洞率为 d 的 k×k 卷积核,其有效感受野尺寸为 k + (k-1)×(d-1)。在语义分割等密集预测任务中,空洞卷积被大量使用,可以在不降低分辨率的情况下捕获多尺度上下文信息。

感受野设计原则:① 浅层用小感受野(3×3、5×5)提取局部细节;② 深层需要大感受野捕获全局信息;③ 堆叠小卷积核 + 池化是最经典的感受野扩大策略;④ 空洞卷积是"不降分辨率、不增参数"扩大感受野的最佳选择。

六、权值共享与局部连接

权值共享(Weight Sharing)和局部连接(Local Connectivity)是卷积神经网络的两大核心设计思想,也是 CNN 相比全连接网络(FCN)具有巨大优势的根本原因。

6.1 局部连接(稀疏连接)

在全连接网络中,每个输出神经元与所有输入神经元相连。对于一张 224×224×3 的图像,第一个全连接层的每个神经元就需要 224×224×3 = 150,528 个权重。而在卷积层中,每个神经元只与输入的一个局部区域(感受野)相连,这个区域的大小就是卷积核的尺寸(如 3×3×3 = 27)。这种局部连接方式基于图像的局部相关性假设——距离较近的像素相关性较强,距离较远的像素相关性较弱。

6.2 权值共享(参数共享)

权值共享是指在同一张特征图内,所有位置共享同一组卷积核参数。这意味着无论卷积核在图像的哪个位置滑动,使用的权重都是相同的。权值共享基于平移不变性假设——一个特征检测器(如边缘检测)在图像的不同位置应该具有相同的功能。

6.3 参数量对比

假设输入为 224×224×3,输出 64 个特征图,特征图尺寸为 224×224(stride=1, padding=same):

连接方式参数量计算量缩放比例
全连接(3×224×224) × (64×224×224) ≈ 4.8×1012≈ 4.8×10121
局部连接+权值共享3×3×3×64 = 1,7281,728 × 224² ≈ 8.7×107≈ 2.8×109
局部连接+无权值共享3×3×3×64×224² ≈ 2.6×109≈ 2.6×109≈ 1,848 倍

上述对比清晰展示了权值共享和局部连接在压缩参数量和计算量方面的惊人效果。正是这种设计使得深层 CNN 在 GPU 上得以高效训练。

6.4 平移不变性(Translation Invariance)

权值共享赋予了 CNN 天然的平移不变性。当输入图像中的物体发生少量平移时,卷积核仍然能够在新的位置检测到相同的特征,只是特征图上的位置发生了对应平移。这种特性对于图像分类等任务至关重要——我们希望无论猫出现在图像的哪个位置,网络都能识别出它是猫。

需要注意的是,池化操作进一步增强了平移不变性。最大池化在局部窗口内只保留最大值,对特征位置的小幅度变化不敏感,因此池化层也被称为"局部平移不变性层"。

# 验证权值共享:同一卷积核在不同位置产生相同的响应模式 import torch import torch.nn.functional as F # 创建一个简单的卷积层 conv = nn.Conv2d(1, 1, kernel_size=3, padding=1, bias=False) # 创建包含两个相同边缘的输入 x = torch.zeros(1, 1, 8, 8) x[0, 0, 2, 2:4] = 1 # 位置A的水平边缘 x[0, 0, 5, 5:7] = 1 # 位置B的水平边缘 # 前向传播 output = conv(x) # 两个位置的响应值应该相同(卷积核权值共享) print(f"位置A响应: {output[0,0,2,2].item():.4f}") print(f"位置B响应: {output[0,0,5,5].item():.4f}") # 输出: 位置A响应 == 位置B响应,验证了权值共享

6.5 稀疏连接的可视化理解

全连接网络中,每个输出由所有输入像素加权求和得到;而在卷积网络中,每个输出仅由卷积核覆盖的局部区域内的输入像素决定。这种稀疏连接结构使得网络可以学习到局部的、层次化的特征表示——底层学习边缘、纹理,中层学习形状、部件,高层学习完整的物体语义。

核心优势总结:局部连接和权值共享使 CNN 具有三个关键特性——(1)参数效率极高(比全连接少几个数量级);(2)平移不变性(同一特征检测器可在任意位置工作);(3)层次化特征学习(从局部到全局逐步抽象)。

七、CNN 经典网络结构中的卷积与池化

7.1 LeNet-5 (1998)

LeNet-5 是 CNN 的开山之作,由 Yann LeCun 提出用于手写数字识别。其结构为:卷积(5×5)→ 平均池化(2×2)→ 卷积(5×5)→ 平均池化(2×2)→ 全连接 → 高斯连接。LeNet-5 奠定了 CNN 的基本范式:"卷积提取特征 + 池化下采样 + 全连接分类"。

7.2 AlexNet (2012)

AlexNet 在 ImageNet 上以巨大优势夺冠,引爆了深度学习热潮。其创新包括:使用 ReLU 激活函数加速训练;采用重叠池化(3×3, stride=2);使用 Dropout 防止过拟合;利用 GPU 并行计算实现大规模网络训练。AlexNet 使用了 11×11 和 5×5 的大卷积核,后续 VGG 证明了这些大核可以用堆叠的小核替代。

7.3 VGGNet (2014)

VGGNet 的核心贡献是证明了"堆叠 3×3 小卷积核"的优越性。VGG 的全部卷积层均使用 3×3 卷积核(padding=1, stride=1)和 2×2 最大池化(stride=2)。堆叠两个 3×3 等效于 5×5 感受野(参数量更少),堆叠三个 3×3 等效于 7×7(参数量更少且多一层非线性)。

7.4 ResNet (2015)

ResNet 通过残差连接(Skip Connection)解决了深层网络的退化问题。其中 Bottleneck 模块大量使用了 1×1 卷积进行通道数调整:先用 1×1 卷积降维(如 256→64),再用 3×3 卷积提取特征,最后用 1×1 卷积升维(如 64→256)。这种设计大幅降低了计算量。ResNet 的全部下采样通过 stride=2 的卷积或池化实现。

# ResNet Bottleneck 模块的 PyTorch 实现 class Bottleneck(nn.Module): expansion = 4 def __init__(self, in_planes, planes, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(planes * self.expansion) self.relu = nn.ReLU(inplace=True) # 捷径连接:维度匹配 self.shortcut = nn.Sequential() if stride != 1 or in_planes != planes * self.expansion: self.shortcut = nn.Sequential( nn.Conv2d(in_planes, planes * self.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(planes * self.expansion) ) def forward(self, x): out = self.relu(self.bn1(self.conv1(x))) out = self.relu(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) out += self.shortcut(x) # 残差连接 out = self.relu(out) return out

八、PyTorch / TensorFlow 卷积层实现

在实际的深度学习项目中,我们使用成熟的框架来构建卷积神经网络,而无需手动实现卷积运算。以下是 PyTorch 和 TensorFlow 中构建 CNN 的常见方式。

8.1 PyTorch 中的卷积层

PyTorch 使用 torch.nn.Conv2d 作为二维卷积层,支持所有常见的卷积变体配置。

import torch import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() # 卷积层: 1x32x32 -> 32x32x32 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) self.bn1 = nn.BatchNorm2d(32) # 池化: 32x32x32 -> 32x16x16 self.pool1 = nn.MaxPool2d(2, 2) # 卷积层: 32x16x16 -> 64x16x16 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm2d(64) # 池化: 64x16x16 -> 64x8x8 self.pool2 = nn.MaxPool2d(2, 2) # 全局平均池化 + 全连接分类 self.global_pool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(64, num_classes) def forward(self, x): x = self.pool1(F.relu(self.bn1(self.conv1(x)))) x = self.pool2(F.relu(self.bn2(self.conv2(x)))) x = self.global_pool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x # 实例化并测试 model = SimpleCNN(num_classes=10) x = torch.randn(4, 1, 32, 32) # batch=4, 灰度图 32x32 y = model(x) print(f"输出形状: {y.shape}") # [4, 10] # 打印模型参数量 total_params = sum(p.numel() for p in model.parameters()) print(f"参数量: {total_params:,}")

8.2 TensorFlow/Keras 中的卷积层

TensorFlow 通过 tf.keras.layers 模块提供 Conv2D 和池化层,API 设计与 PyTorch 类似但略有差异。

import tensorflow as tf from tensorflow.keras import layers, models def create_cnn(input_shape=(32, 32, 1), num_classes=10): model = models.Sequential([ # Conv1: (32,32,1) -> (32,32,32) layers.Conv2D(32, (3, 3), padding='same', input_shape=input_shape), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # (32,32,32) -> (16,16,32) # Conv2: (16,16,32) -> (16,16,64) layers.Conv2D(64, (3, 3), padding='same'), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling2D((2, 2)), # (16,16,64) -> (8,8,64) # 全局平均池化 + Dense 分类 layers.GlobalAveragePooling2D(), layers.Dense(num_classes, activation='softmax') ]) model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) return model model = create_cnn() model.summary()

8.3 自定义卷积层的注意事项

常见陷阱:(1)PyTorch 中 Conv2d 的默认 bias=True,而 BatchNorm 自带 bias 参数,叠加会导致参数浪费,通常设置 bias=False;(2)TensorFlow 中 Conv2D 默认 padding='valid',如要保持尺寸需显式设置 padding='same';(3)空洞卷积的 padding 需要根据空洞率计算,规则为 padding = dilation × (kernel_size - 1) // 2;(4)权值初始化:ReLU 激活推荐使用 Kaiming 初始化,sigmoid/tanh 推荐 Xavier 初始化。

效率优化建议:① 使用 torch.backends.cudnn.benchmark = True 启用 cuDNN 自动调优;② 在 TensorFlow 中使用 tf.data 流水线进行数据预处理;③ 使用混合精度训练(AMP)减少显存占用和加速计算;④ 对于大卷积核(>7×7)考虑使用可分离卷积替代。

框架对比小结:PyTorch 和 TensorFlow 在卷积层的核心实现上趋同,均支持 Conv2d、MaxPool2d、AvgPool2d 等标准操作以及空洞卷积、转置卷积等变体。PyTorch 的 API 更接近 Python 风格(面向对象、动态图),TensorFlow 的 Keras API 更简洁(函数式或序列式)。两者底层均调用 cuDNN 进行高效的 GPU 卷积计算。

九、核心要点总结

一、卷积运算:卷积核在输入上滑动并进行点积运算,提取局部特征。通过多通道卷积、1×1卷积、空洞卷积、转置卷积等变体,满足不同应用场景的需求。

二、池化操作:通过最大池化、平均池化、全局池化等方式实现下采样,降低分辨率、减少参数量、增强平移不变性。空间金字塔池化支持任意尺寸输入。

三、填充与步长:控制输出尺寸的核心参数。Same Padding保持尺寸,stride>1实现下采样。输出尺寸计算公式是网络设计的基石。

四、感受野:特征图像素对应的输入区域。堆叠小卷积核(3×3)可以在更少参数下获得更大的感受野。空洞卷积在不降分辨率的情况下扩大感受野。

五、权值共享与局部连接:CNN参数量远少于全连接网络的关键设计。局部连接实现稀疏交互,权值共享实现平移不变性,两者共同实现了极高的参数效率。

六、框架实现:PyTorch和TensorFlow均提供了完整的卷积层 API,支持各种卷积变体和池化操作,底层调用 cuDNN 进行高效计算。

"卷积神经网络的设计哲学是:通过局部连接、权值共享、池化下采样三个核心技术,在保留空间结构信息的同时,以极少的参数实现强大的特征提取能力。"

进一步学习方向