PyTorch 深度学习专题【左扬精讲】—— 卷积神经网络 CNN
全连接神经网络(MLP)在处理表格数据和低维向量时表现稳定,但当它面对图像、视频这类高维结构化数据时,局限性便暴露无遗——参数量爆炸、缺乏平移不变性、无法共享特征。这些问题严重制约了 MLP 在视觉任务上的表现。
卷积神经网络(CNN)的提出,正是为了从根本上解决 MLP 在处理图像和视频数据时的这些痛点。CNN 通过局部连接和权重共享两大核心机制,大幅降低了参数量,同时赋予网络捕捉空间层次结构的能力,成为现代计算机视觉的基础架构。
本文将系统讲解 CNN 的定义与核心思想、权重共享机制、基本结构(输入层、卷积层、激活函数、池化层、全连接层、输出层)、卷积操作的原理与参数控制,以及多通道卷积的工作方式。
torch.nn.Conv2d ← 2D 卷积层
torch.nn.Conv1d ← 1D 卷积层
torch.nn.MaxPool2d ← 最大池化层
torch.nn.AvgPool2d ← 平均池化层
torch.nn.Linear ← 全连接层
torch.nn.ReLU ← 激活函数(线性整流)
torch.nn.Sequential ← 容器:按顺序堆叠各层
PyTorchCNN卷积层池化层权重共享特征提取
学习重点
- 必须掌握
- MLP 处理图像的局限性
- CNN 的局部连接与权重共享机制
- 卷积操作的原理与输出尺寸计算
- CNN 基本结构各层的作用
- 理解即可
- 多通道卷积的工作方式
- CNN 的优势与局限性
目录
- MLP 及其局限性
- CNN 的定义与核心思想
- 权重共享机制
- CNN 的基本结构
- 卷积操作详解
- 卷积参数控制与输出尺寸
- 多通道卷积
- CNN 的优势与劣势
- FAQ(20 组)
- Roadmap 预告
一、MLP 及其局限性
What — MLP 是什么?
MLP(Multi-Layer Perceptron,多层感知机)是最基础的深度神经网络结构,由输入层、若干隐藏层和输出层组成。每层的每个神经元与上一层的所有神经元相连(全连接),因此称为 全连接层(Fully Connected Layer)。
# MLP 的全连接层:每个神经元与上一层所有神经元相连
import torch
import torch.nn as nn# 例如,一张 224x224 的彩色图像(3 通道)
# 用 MLP 第一层处理:输入维度 = 224 * 224 * 3 = 150528
mlp = nn.Sequential(nn.Linear(in_features=150528, out_features=512), # W: (512, 150528) = 约 7700 万参数nn.ReLU(),nn.Linear(512, 256),nn.ReLU(),nn.Linear(256, 10) # 输出 10 类
)
# 仅第一层的权重矩阵就有 150528 x 512 ≈ 7700 万个参数
Why — MLP 处理图像有哪些局限性?
问题一:参数量爆炸
对于一张 224×224 的彩色图像(3 通道),MLP 第一层的输入维度高达 150,528 个神经元。如果第一层输出 512 个神经元,权重矩阵规模就是 512 × 150,528 ≈ 7700 万个参数。这仅仅是第一层,整个网络的参数量会轻易达到数亿级别,极易过拟合,且训练和推理的计算成本极高。
问题二:缺乏平移不变性
MLP 将图像展平为向量后,丢失了像素之间的空间位置关系。一只猫出现在图像左上角和右下角,在 MLP 看来是两个完全不同的输入模式,网络需要分别学习两个位置的猫的pattern,无法利用"猫的特征是通用的"这一先验知识。因此 MLP 对图像的平移、旋转、尺度变化非常敏感。
问题三:无法共享特征
在 MLP 中,每个位置的权重都是独立的。图像左上角的边缘特征和右下角的边缘特征,由完全不同的权重参数负责学习。这意味着网络需要为每个空间位置独立学习"什么是边缘",导致大量重复的参数学习,效率极低。
问题四:无法捕捉空间层次结构
图像具有天然的空间层次:像素→边缘→纹理→部件→物体。MLP 将所有像素同等对待,无法逐层抽象出这种层次化的特征表示。
没有 CNN 会发生什么?
- 参数量巨大,训练困难,容易过拟合
- 对图像平移、旋转敏感,泛化能力差
- 无法有效利用图像的空间结构信息
- 无法处理大分辨率图像(内存和算力都无法承受)
本节小结
- MLP:全连接层,每个神经元与上一层所有神经元相连
- 参数量爆炸:高分辨率图像导致全连接层参数规模巨大
- 平移不变性缺失:无法利用"特征与位置无关"的先验
- 权重独立:每个空间位置独立学习,无法共享特征
二、CNN 的定义与核心思想
What — CNN 是什么?
CNN(Convolutional Neural Network,卷积神经网络)是一种专门为处理具有网格结构数据(如图像)而设计的神经网络架构,由 Yann LeCun 等人在 1998 年提出(LeNet),并在 2012 年 AlexNet 出现后开始主导计算机视觉领域。
卷积网络的核心思想是用卷积运算替代全连接运算,通过局部连接和权重共享两大机制,大幅降低参数量,同时具备平移不变性和捕捉空间层次结构的能力。
Why — CNN 如何解决 MLP 的问题?
核心机制一:局部连接(Local Connectivity)
卷积层中的每个神经元只与输入的一个局部区域(称为感受野,Receptive Field)相连,而不是与整个输入相连。例如,处理一张图像时,每个神经元只关心一个小区域内的像素(如 3×3 或 5×5),而非整张图像。这大幅减少了参数量,同时让网络能够捕捉局部特征(如边缘、角点)。
核心机制二:权重共享(Weight Sharing)
同一个卷积核(kernel)在整个输入图像上滑动时,使用相同的权重参数。也就是说,检测"边缘"这个特征时,无论边缘出现在图像的哪个位置,都用同一组权重去检测。这解决了 MLP 中每个位置独立学习的问题,让网络具有平移不变性。
核心机制三:空间层次结构
通过堆叠多个卷积层,网络能够逐层抽象特征:底层卷积层捕捉边缘、角点等低级特征;中层卷积层组合低级特征形成纹理、部件等中级特征;高层卷积层进一步组合形成物体类别等高级特征。这种层次化的特征提取是 CNN 强大表达能力的关键。
# MLP:全连接,参数量 = 输入维度 x 输出维度
# 例如:150528 x 512 = 77,070,336 个参数# CNN:局部连接 + 权重共享,参数量大幅减少
# 例如:3x3 卷积核,输入通道 3,输出通道 64
# 参数量 = 3(输入通道) x 3(核高) x 3(核宽) x 64(输出通道) = 1,728 个参数
conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3)
print(conv.weight.shape) # torch.Size([64, 3, 3, 3]) = 1728 个权重
print(conv.bias.shape) # torch.Size([64]) = 64 个偏置# 即使输出 64 个通道,参数量也只有 1792 个
# 相比 MLP 的 7700 万,减少了约 4 万倍
卷积层的参数量与输入图像尺寸无关,只与卷积核大小和通道数有关。这是 CNN 能够高效处理高分辨率图像的根本原因。
本节小结
- CNN:卷积神经网络,专门处理网格结构数据
- 局部连接:每个神经元只连接局部区域,降低参数量
- 权重共享:同一卷积核全局共享权重,赋予平移不变性
- 空间层次:逐层抽象,低层特征组合成高层特征
三、权重共享机制
What — 权重共享是什么?
权重共享(Weight Sharing)是指在卷积神经网络中,同一个卷积核的权重参数在整个输入图像上被重复使用。具体来说,一个卷积核从图像左上角滑动到右下角的每一步,都使用完全相同的权重值进行点积运算。
这与 MLP 中每个神经元都有独立权重参数的做法形成鲜明对比。MLP 中,第 i 个神经元和第 j 个神经元之间的连接权重是独一无二的;而在 CNN 中,所有空间位置共享同一组卷积核权重。
Why — 权重共享解决了什么问题?
问题一:参数量骤降
假设输入是一张 224×224 的图像,用一个 3×3 的卷积核处理。如果没有权重共享,每个像素位置都需要独立的 3×3 权重矩阵,参数量为 224×224×3×3 ≈ 45 万。而有权重共享时,仅需 3×3 = 9 个参数(加上一个偏置共 10 个)。参数量降低了约 4.5 万倍。
问题二:带来平移不变性
正是因为所有位置使用相同的卷积核权重,无论图像中的目标移动到哪里(平移),卷积核都会以相同的方式检测相同的特征。图像中一只狗从左边移到右边,CNN 仍然能识别出"狗"这个类别——这就是平移不变性(Translation Invariance)。
问题三:更强的正则化效果
权重共享强制网络在所有位置学习相同的特征模式,相当于给模型添加了一个强约束(正则化),使模型更难过拟合,同时也提升了泛化能力。
没有权重共享会发生什么?
- 参数量回到 MLP 的量级,无法训练深层网络
- 模型失去平移不变性,每个位置需要单独学习
- 容易过拟合,泛化能力差
import torch
import torch.nn as nnconv = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3) # 创建卷积层
print(conv.weight.shape) # torch.Size([1, 3, 3, 3]),即 1 个输出通道的权重
print(conv.weight.numel()) # 27 = 3 * 3 * 3# 同一个 weight 在整个输入上共享
input_tensor = torch.randn(1, 3, 5, 5) # batch=1, 通道=3, 高=5, 宽=5
output = conv(input_tensor) # 输出 (1, 1, 3, 3)
print(output.shape)# 验证:权重是同一个对象(在内存中共享)
# PyTorch 中同一个 Module 的 weight 是一个 Parameter 对象
# 在前向传播中,这个 Parameter 被反复用于计算不同位置的输出
PyTorch 的 nn.Conv2d 中,weight 是一个 nn.Parameter 对象,在前向传播时这个 Parameter 被同一个卷积窗口在整个图像上复用。权重在训练时通过反向传播更新,所有位置共享更新后的值——这就是"权重共享"的真正含义。
本节小结
- 权重共享:同一卷积核的权重在全局重复使用
- 参数量锐减:从 O(H×W) 降到 O(K_h×K_w)
- 平移不变性:特征检测与位置无关
- 正则化:约束模型,减少过拟合风险
四、CNN 的基本结构
CNN 的完整结构通常由以下六类层级组成
一个典型的卷积神经网络按照数据流动方向,依次包含:输入层(Input)、卷积层(Conv)、激活函数层(Activation)、池化层(Pooling)、全连接层(FC)和输出层(Output)。其中卷积层和池化层通常组合出现(全连接层在网络末端),形成"交替堆叠"的结构。
输入图像 (H, W, C) → [卷积层 + 激活] → [池化层] → [卷积层 + 激活] → [池化层] → ... → [展平] → [全连接层] → 输出
层级详解
输入层(Input Layer)
CNN 的输入通常是一张图像,表示为三维张量 (H, W, C),其中 H 和 W 是图像的高度和宽度,C 是通道数。对于彩色 RGB 图像,C=3;对于灰度图像,C=1。输入层的任务是将原始图像数据标准化到网络可以处理的数值范围(如 [0, 1] 或 [-1, 1])。
卷积层(Convolutional Layer)
卷积层是 CNN 的核心,通过卷积核在输入图像上滑动进行局部特征提取。每个卷积核的权重在整个输入上共享,输出称为特征图(Feature Map)。多个卷积核可以并行提取不同类型的特征。卷积层通常后面接激活函数,引入非线性。
激活函数层(Activation Layer)
激活函数为网络引入非线性,使 CNN 能够拟合任意复杂的非线性映射。常用的激活函数包括 ReLU(线性整流)、Leaky ReLU、ELU 等。ReLU 是目前最广泛使用的激活函数,它将负值置为零,保留正值,计算高效且能有效缓解梯度消失问题。
池化层(Pooling Layer)
池化层对特征图进行空间降采样,在不损失重要信息的前提下缩小特征图的宽高,从而减少后续层的计算量和参数量。同时,池化操作增强了网络对输入微小变化的鲁棒性(对平移、旋转、缩放不敏感)。常见的池化方式有最大池化(MaxPooling,取局部最大值)和平均池化(AvgPooling,取局部平均值)。
全连接层(Fully Connected Layer)
全连接层位于 CNN 的末端,将前面卷积层提取的高维特征映射到最终的输出空间(如分类标签)。在结构上,全连接层与 MLP 的隐藏层相同,每个神经元与上一层的所有神经元相连。CNN 通常在靠近输出的位置使用 1~2 层全连接层,将特征图展平后的向量转换为最终的预测结果。
输出层(Output Layer)
输出层根据任务类型选择不同的激活函数。分类任务常用 Softmax 函数(多分类)或 Sigmoid 函数(二分类);回归任务则直接输出连续值。
import torch
import torch.nn as nnclass SimpleCNN(nn.Module):def __init__(self, num_classes=10):super().__init__()# 第 1 个卷积块:Conv -> ReLU -> MaxPoolself.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1) # 卷积层提取局部特征self.relu1 = nn.ReLU() # 激活函数引入非线性self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 池化层降采样,尺寸减半# 第 2 个卷积块:Conv -> ReLU -> MaxPoolself.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)self.relu2 = nn.ReLU()self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)# 全连接层:将特征图展平后分类self.fc1 = nn.Linear(in_features=64 * 7 * 7, out_features=128) # 全连接层整合全局特征self.relu3 = nn.ReLU()self.fc2 = nn.Linear(in_features=128, out_features=num_classes) # 输出层,10 类分类def forward(self, x):x = self.conv1(x) # 输入 (batch, 3, 28, 28) -> (batch, 32, 28, 28)x = self.relu1(x)x = self.pool1(x) # -> (batch, 32, 14, 14)x = self.conv2(x) # -> (batch, 64, 14, 14)x = self.relu2(x)x = self.pool2(x) # -> (batch, 64, 7, 7)x = torch.flatten(x, start_dim=1) # 展平:(batch, 64*7*7)x = self.fc1(x)x = self.relu3(x)x = self.fc2(x)return xmodel = SimpleCNN(num_classes=10)
x = torch.randn(1, 3, 28, 28) # 模拟一张 28x28 的 RGB 图像
output = model(x)
print(output.shape) # torch.Size([1, 10])
各层参数量对比
- 卷积层:参数量 = out_channels × in_channels × k_h × k_w + out_channels(偏置)。与输入图像尺寸无关。
- 全连接层:参数量 = in_features × out_features + out_features(偏置)。与输入维度直接相关,通常参数量最大。
- 池化层:无可学习参数(仅做固定计算)。
本节小结
- 输入层:接收标准化后的图像张量 (H, W, C)
- 卷积层:局部特征提取,权重共享
- 激活函数:引入非线性,常用 ReLU
- 池化层:空间降采样,增强鲁棒性
- 全连接层:整合全局特征,输出预测结果
- 输出层:Softmax(Sigmoid)映射到分类概率
五、卷积操作详解
What — 卷积操作是什么?
卷积(Convolution)是 CNN 的核心数学运算。与信号处理中的卷积不同,深度学习中的卷积实际上是一种互相关(Cross-Correlation)运算——不需要对卷积核进行翻转,直接将卷积核与输入的局部区域进行逐元素相乘后求和。
卷积核(Kernel)是一个小型的参数矩阵(如 3×3 或 5×5),它在输入图像上按步长(stride)滑动,每次覆盖一个局部区域,计算该区域的加权和作为输出。
卷积核、卷积过程与特征图
卷积核(Kernel)
卷积核是一个可学习的参数矩阵,其大小由 kernel_size 指定。一个卷积核的权重在全局共享,每个卷积核负责检测一种特定的局部特征(如水平边缘、垂直边缘、纹理等)。多个卷积核并行工作,输出多个特征图。
卷积过程
卷积核从输入图像的左上角开始,以步长(stride)为单位向右滑动。当一行滑到边界后,回到左端并向下一行移动。滑动的每一步,卷积核与输入的局部区域进行逐元素相乘,然后将所有乘积求和,得到输出特征图上对应位置的一个标量值。
特征图(Feature Map)
特征图是卷积运算的输出结果,每个特征图对应一个卷积核的检测结果。特征图上的每个值表示在对应位置检测到该卷积核所代表特征的程度(响应值)。正值表示该特征存在,负值表示该特征以相反形式存在。
输入图像(5x5) 卷积核(3x3) 输出特征图(3x3)
┌─────────────┐
│ 1 2 3 4 5│ ┌─────────────┐ ┌─────────────┐
│ 6 7 8 9 10│ ⊗ kernel= │ 0 1 0│ ──→ stride=1 │ 27 33 36│
│11 12 13 14 15│ [[0,1,0], ├─────────────┤ │ 57 63 66│
│16 17 18 19 20│ [1,-4,1], │ 1 -4 1│ │ 87 93 96│
│21 22 23 24 25│ [0, 1, 0]] └─────────────┘ └─────────────┘
└─────────────┘第 (0,0) 位置的卷积计算:
1×0 + 2×1 + 3×0 + 6×1 + 7×(-4) + 8×1 + 11×0 + 12×1 + 13×0 = 27特征图上的每个值 = 卷积核与输入局部区域的逐元素乘积之和
以右侧的卷积核(中心 -4、四周 1)为例,这是一个经典的边缘检测算子,对中心像素与周围像素差异大的区域产生高响应。输入图像中数值变化剧烈的区域(如 7 周围是 6,8,12),输出特征图对应位置的响应值会很高。
本节小结
- 卷积核:可学习的小型参数矩阵,每个核检测一种局部特征
- 卷积过程:核在输入上滑动,逐元素相乘后求和
- 特征图:卷积核检测结果的输出,表示特征响应的空间分布
- 本质:深度学习中的卷积等价于互相关(无需翻转核)
六、卷积参数控制与输出尺寸
卷积操作的输出尺寸由哪些参数控制?
PyTorch 中 torch.nn.Conv2d 的主要参数控制着卷积操作的行为和输出尺寸。这些参数相互配合,决定了特征图的空间维度和网络的表达能力。
六大核心参数
参数一:in_channels 和 out_channels(通道数)
in_channels 指定输入特征图的通道数(如 RGB 图像为 3,灰度图为 1)。out_channels 指定输出特征图的数量,即卷积核的数量。每个输出通道对应一个独立的卷积核,检测一种类型的特征。权重张量的形状为 (out_channels, in_channels, k_h, k_w)。
参数二:kernel_size(卷积核大小)
卷积核的空间尺寸。常见选择有 1×1、3×3、5×5、7×7。核越大,单次感受野越大,能捕捉更大范围的特征,但参数量呈平方增长,计算成本也更高。3×3 是最常用的选择——它足够大能捕捉有意义的局部模式,又足够小保持高效。1×1 卷积核不在空间维度做卷积,仅在通道维度做线性变换。
参数三:stride(步长)
卷积核每次移动的像素数。stride=1 时核逐像素滑动;stride=2 时核每次跳过一个像素,输出尺寸缩小一半。增大步长可以降低输出分辨率、减少计算量,同时扩大每个输出位置对应的输入感受野。
参数四:padding(填充)
在输入图像边界周围填充的像素数。padding=0 时不使用填充,输入边界处只能被核的左上角覆盖,产生" valid"卷积效果,输出尺寸小于输入。padding=1 时在四周各填充一圈零值,对于 3×3 卷积核,输出尺寸与输入尺寸相同(stride=1 时)。合理使用 padding 可以控制输出尺寸并保留图像边缘信息。PyTorch 中 padding 仅接受整数或二元组,不接受字符串(如 'same'),需手动根据核大小计算填充值:padding = kernel_size // 2。
参数五:dilation(空洞率)
控制卷积核内部元素之间的间距。dilation=1(默认值)时,核内元素紧密相邻,核的有效感受野等于其物理尺寸。dilation>1 时,核内元素之间插入空洞(零值),在不增加参数量的情况下扩大有效感受野。例如 dilation=2 的 3×3 卷积核,实际采样间隔为 2,有效感受野相当于 5×5,但参数量仍为 3×3。空洞卷积(Dilation Convolution)广泛用于语义分割(如 DeepLab 系列),在扩大感受野的同时保留高分辨率特征图。
参数六:bias(偏置)
控制是否添加可学习的偏置项。bias=True(默认值)时,每个输出通道有一个偏置参数,加在卷积结果上。bias=False 时,仅通过权重矩阵的线性变换产生输出。对于后续接 BatchNorm 的网络,关闭 bias 可以减少冗余计算(BatchNorm 本身包含可学习的缩放和偏移参数)。
参数七:padding_mode(填充模式)
指定在 padding 区域使用何种值填充。默认为 'zeros'(零填充)。其他可选模式包括:'reflect'(沿边界镜像反射)、'replicate'(复制边界像素值)、'circular'(环绕填充)。不同填充模式会影响边界区域的卷积结果,'reflect' 和 'replicate' 在边缘检测任务中通常优于零填充。
PyTorch 中 torch.nn.Conv2d 的输出尺寸公式(dilation=1 时)为:
H_out = ⌊(H_in + 2 × padding - kernel_size) / stride⌋ + 1
W_out = ⌊(W_in + 2 × padding - kernel_size) / stride⌋ + 1
逐参数验证,以输入 (N=1, C_in=3, H=7, W=7),kernel_size=3,stride=1,padding=0 为例:
import torch
import torch.nn as nn# 示例 1:padding=0, stride=1(valid 卷积,尺寸收缩)
conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=0)
# H_out = ⌊(7 + 0 - 3) / 1⌋ + 1 = ⌊4⌋ + 1 = 5
input1 = torch.randn(1, 3, 7, 7)
output1 = conv1(input1)
print(output1.shape) # torch.Size([1, 16, 5, 5]) ✓# 示例 2:padding=1, stride=1(same 卷积,尺寸保持)
conv2 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# H_out = ⌊(7 + 2×1 - 3) / 1⌋ + 1 = ⌊6⌋ + 1 = 7
input2 = torch.randn(1, 3, 7, 7)
output2 = conv2(input2)
print(output2.shape) # torch.Size([1, 16, 7, 7]) ✓# 示例 3:padding=0, stride=2(下采样,尺寸减半)
conv3 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=2, padding=0)
# H_out = ⌊(7 + 0 - 3) / 2⌋ + 1 = ⌊2⌋ + 1 = 3
input3 = torch.randn(1, 3, 7, 7)
output3 = conv3(input3)
print(output3.shape) # torch.Size([1, 16, 3, 3]) ✓# 示例 4:padding=1, stride=2(带填充下采样)
conv4 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=2, padding=1)
# H_out = ⌊(7 + 2×1 - 3) / 2⌋ + 1 = ⌊3⌋ + 1 = 4
input4 = torch.randn(1, 3, 7, 7)
output4 = conv4(input4)
print(output4.shape) # torch.Size([1, 16, 4, 4]) ✓# 示例 5:kernel_size=5, padding=2, stride=1(更大核保持尺寸)
conv5 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1, padding=2)
# H_out = ⌊(7 + 2×2 - 5) / 1⌋ + 1 = ⌊6⌋ + 1 = 7
input5 = torch.randn(1, 3, 7, 7)
output5 = conv5(input5)
print(output5.shape) # torch.Size([1, 16, 7, 7]) ✓
常用配置对应的输出尺寸
| kernel_size | stride | padding | dilation | H_in | H_out | 说明 |
|---|---|---|---|---|---|---|
| 3 | 1 | 0 | 1 | 224 | 222 | valid 卷积,尺寸缩小 |
| 3 | 1 | 1 | 1 | 224 | 224 | same 配置,尺寸不变 |
| 3 | 2 | 0 | 1 | 224 | 111 | 下采样,尺寸减半 |
| 3 | 2 | 1 | 1 | 224 | 112 | 带填充下采样 |
| 5 | 1 | 2 | 1 | 224 | 224 | same 配置(5×5 核) |
| 3 | 1 | 0 | 2 | 7 | 3 | 空洞卷积,有效感受野相当于 5×5 |
一般规律:kernel_size = 2 × padding + 1 时(stride=1),输出尺寸等于输入尺寸。
本节小结
- in_channels / out_channels:输入/输出通道数,控制特征种类数量和权重形状
- kernel_size:卷积核空间尺寸,常用 3×3,控制感受野大小
- stride:滑动步长,控制输出分辨率,默认 1
- padding:边界填充,控制输出尺寸,常用 padding=1 使尺寸不变
- dilation:空洞率,扩大有效感受野,默认 1
- bias:偏置项,每个输出通道一个,默认开启
- padding_mode:填充模式,'zeros' / 'reflect' / 'replicate' / 'circular'
- 输出尺寸公式:H_out = ⌊(H_in + 2×pad - k) / s⌋ + 1(dilation=1 时)
七、多通道卷积
What — 多通道卷积是什么?
真实世界的图像和 CNN 中间层的特征图通常都有多个通道(如 RGB 图像有 3 通道,卷积层输出可能有 64 或 256 个通道)。多通道卷积描述的是:多个输入通道如何通过一个卷积核转换为单个输出通道,以及多个卷积核如何并行处理生成多个输出通道。
多通道卷积的工作方式
单核多通道输入
一个卷积核同时处理所有输入通道。卷积核的通道数必须等于输入通道数(C_in)。对每个输入通道,卷积核都有对应的一组 2D 权重矩阵。在滑动过程中,核在每个通道的局部区域分别做点积,然后将 C_in 个通道的乘积结果相加(加上偏置),得到该位置的一个标量输出。
多核并行输出
每个输出通道由一个独立的卷积核产生。假设有 C_out 个输出通道,则网络需要 C_out 个独立的卷积核,每个核的大小为 (C_in, K_h, K_w)。所有 C_out 个核并行处理输入,各自生成一个特征图,最终输出堆叠为 (C_out, H_out, W_out) 的三维张量。
权重张量形状
PyTorch 中 Conv2d 的权重张量形状为 (C_out, C_in, K_h, K_w)。参数量 = C_out × C_in × K_h × K_w + C_out(偏置)。
输入特征图 单个卷积核(3通道, 3x3) 输出特征图
(3, H, W) (3, 3, 3)通道 0: [[r]] ──→ 与核的通道 0 (3×3) 做点积 ──┐
通道 1: [[g]] ──→ 与核的通道 1 (3×3) 做点积 ──┼──→ 求和 + bias ──→ 1 个值
通道 2: [[b]] ──→ 与核的通道 2 (3×3) 做点积 ──┘完整多通道卷积(多个输出通道):
输入 (3, 7, 7)├── kernel_0 → feature_map_0 (7, 7)├── kernel_1 → feature_map_1 (7, 7)├── ...└── kernel_63 → feature_map_63 (7, 7)最终输出 (64, 7, 7),即 64 个特征图,每个 (7, 7)
import torch
import torch.nn as nn# 多通道输入:3 通道图像 (RGB),尺寸 7x7
conv_multi = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1)
# 权重形状:(64, 3, 3, 3) = 64 个卷积核,每个核处理全部 3 个输入通道
print(conv_multi.weight.shape) # torch.Size([64, 3, 3, 3])
print(conv_multi.bias.shape) # torch.Size([64])# 输入:batch=1,3 通道,高=7,宽=7
x = torch.randn(1, 3, 7, 7)
y = conv_multi(x)
# 输出:64 个通道,高=7,宽=7(same padding 保持尺寸)
print(y.shape) # torch.Size([1, 64, 7, 7])# 验证:每个输出通道只与对应索引的卷积核相连
# 输出通道 j = conv.weight[j] 与输入所有通道做卷积 + conv.bias[j]
for j in [0, 31, 63]:kernel_j = conv_multi.weight[j] # 形状 (3, 3, 3),第 j 个核print(f"第 {j} 个核的形状: {kernel_j.shape}")
通道数和填充模式的实用原则
- 首层:通常 in_channels=3(RGB),out_channels 常用 32 或 64
- 中间层:随着网络加深,通道数逐层翻倍(如 64→128→256→512),捕捉更丰富的特征
- 末层:通道数可能不再增加或开始减少,用于整合信息
- 1×1 卷积核:不改变空间尺寸,仅在通道维度做线性组合,用于调整通道数和减少计算量
- padding_mode:默认为 'zeros';边界敏感任务(如纹理分割)可尝试 'replicate' 或 'reflect'
本节小结
- 单核多通道:一个核的通道数 = 输入通道数,分别点积后求和输出一个标量
- 多核并行:每个输出通道由独立卷积核产生,最终堆叠为 (C_out, H_out, W_out)
- 权重形状:(C_out, C_in, K_h, K_w),参数量与输入尺寸无关
- 通道数翻倍:网络深层通常逐步增加通道数,捕捉更多特征类型
八、CNN 的优势与劣势
CNN 的优势
相比 MLP,CNN 在处理图像数据上具有压倒性优势,这些优势源于其独特的架构设计:局部连接减少参数量,权重共享带来平移不变性,层次化结构提取多尺度特征。
优势一:参数效率高
由于局部连接和权重共享,CNN 的参数量与输入图像的分辨率无关。一个 3×3 卷积层的参数量恒为 9×C_in×C_out,无论处理 224×224 还是 1024×1024 的图像,参数量都不变。这使 CNN 能够高效处理任意分辨率的图像。
优势二:平移不变性
权重共享确保同一卷积核在图像任何位置使用相同权重检测特征,使 CNN 对目标的位置变化具有天然的鲁棒性。无论猫在图像的左上角还是右下角,同一组卷积核权重都能检测出"猫"的特征。
优势三:捕捉空间层次结构
通过堆叠多个卷积-激活-池化块,CNN 逐层构建越来越抽象的特征表示。底层捕捉边缘和纹理,中层组合形成部件,高层识别物体类别。这种层次化表示与人类视觉皮层的工作方式有相似之处。
优势四:平移、旋转(一定程度)和尺度鲁棒性
池化层的降采样操作(尤其是最大池化)进一步增强了网络对微小平移和旋转的鲁棒性。即使目标略微偏移或缩放,池化层仍能保留主要特征响应。
CNN 的劣势
尽管 CNN 在计算机视觉领域取得了巨大成功,但它也有固有的局限性。
劣势一:固定几何结构,不适合非欧数据
CNN 的卷积和池化操作天然适合网格结构(图像、语音频谱图),但无法直接处理图结构数据(社交网络、分子图、知识图谱)和序列变长数据。这些场景需要图神经网络(GNN)或 Transformer 等替代架构。
劣势二:感受野受限,长距离依赖建模困难
卷积层的感受野由核大小和层数决定。在深层网络中虽然可以通过堆叠层数扩大感受野,但有效感受野的增长速度远低于理论值。对于需要建模图像中相距很远区域之间关系的长距离依赖任务(如全景分割、某些图像生成任务),CNN 往往不如 Transformer。
劣势三:池化操作丢失精细位置信息
最大池化在降采样的同时也会丢弃一些次要信息,可能导致精细定位任务(如语义分割的边缘精度)表现不佳。平均池化虽然更平滑,但也会模糊空间信息。
劣势四:缺乏对空间变换的显式建模
标准 CNN 对输入的空间变换(旋转、尺度变化、视角变化)缺乏显式建模能力。虽然通过数据增强(data augmentation)可以在训练时让网络见过各种变换,但仍不如显式建模空间变换的架构(如 Spatial Transformer Network)有效。
CNN vs Transformer 的选择
- 选 CNN:中小数据集、图像分类、目标检测、图像分割等视觉任务,以及对推理速度有较高要求的场景
- 选 Transformer:超大规模预训练、对长距离依赖建模要求高的任务、需要在视觉和语言之间融合的多模态任务
- CNN + Transformer 融合:现代视觉模型(如 Swin Transformer、ConvNeXt)也在吸取对方的设计优势,取长补短
本节小结
- 优势:参数效率高、平移不变性、空间层次特征、鲁棒性好
- 劣势:不适合非欧数据、长距离依赖弱、池化丢信息、空间变换建模差
- 选择原则:根据任务、数据规模和计算资源权衡选择
九、FAQ(20 组)
FAQ — 精选 20 问,深入理解卷积神经网络
Q1. CNN 和 MLP 的核心区别是什么?
MLP 是全连接网络,每个神经元与上一层所有神经元相连;CNN 是局部连接网络,每个神经元只与上一层的局部区域相连,且同一卷积核的权重在全局共享。 这使 CNN 的参数量与输入图像尺寸无关,极大降低了过拟合风险,同时赋予网络平移不变性。
Q2. 什么是感受野(Receptive Field)?
感受野是卷积神经网络中,输出特征图上的一个位置对应于输入图像上的区域大小。 第一个卷积层的感受野等于其卷积核大小;堆叠多层后,感受野逐层扩大。对于 3×3 卷积核、stride=1,堆叠 3 层后的理论感受野为 7×7(即输出位置能"看到"输入的 7×7 区域)。
Q3. 为什么 3×3 卷积核最常用,而不是 5×5 或 7×7?
3×3 卷积核的参数量最小,同时堆叠两个 3×3 层能达到与单个 5×5 层相同的感受野(7×7)。 两个 3×3 层的参数量为 2×3×3×C_in×C_out = 18×C_in×C_out,而一个 5×5 层的参数量为 25×C_in×C_out。更少的参数意味着更少的过拟合风险和更快的计算速度。此外,两个非线性层(两层 ReLU)比一层非线性更具有表示能力。
Q4. padding 有什么作用?有哪些取值?
padding 在输入图像边界周围填充像素(通常填充零值),主要作用是控制输出尺寸和保留边缘信息。 PyTorch 中 padding 可以是整数(四周相同填充)或二元组(分别指定高和宽的填充)。Conv2d 不接受 'same' 等字符串作为 padding 值,需要手动设置填充量:对于 kernel_size=3 设置 padding=1,对于 kernel_size=5 设置 padding=2,以此类推。
Q5. stride=2 和 MaxPool2d(stride=2) 有什么区别?
从输出尺寸角度,两者效果相同(尺寸减半),但卷积层有可学习参数,MaxPool 没有。 stride=2 的卷积层在下采样的同时还能学习特征变换;而 MaxPool 只是固定地取局部最大值(无参数),是一种硬性的下采样策略。在现代网络中,两种方式都可以使用,有时还会组合(如在 stride=2 卷积后不加池化)。
Q6. MaxPool 和 AvgPool 的区别是什么?何时使用?
MaxPool 取局部区域的最大值,强调最显著的特征;AvgPool 取局部区域的平均值,保留背景的平滑信息。 MaxPool 更常用,因为它具有更强的抗噪能力(取最大值而非平均值,不受噪声像素的太大影响)。AvgPool 常用于网络末端(如全局平均池化 GlobalAvgPool)将特征图压缩为单个值,用于分类。
pool_max = nn.MaxPool2d(kernel_size=2) # 取局部最大值
pool_avg = nn.AvgPool2d(kernel_size=2) # 取局部平均值
gap = nn.AdaptiveAvgPool2d((1, 1)) # 全局平均池化,将任意尺寸压到 1x1
Q7. 1×1 卷积核有什么作用?
1×1 卷积核不在空间维度做卷积,仅在通道维度做线性变换。 它的作用包括:调整通道数(升维或降维)、跨通道信息融合(如将多个通道的信息组合)、增加非线性(在卷积后加 ReLU)。1×1 卷积是 InceptionNet、ResNet 等现代架构中的核心组件。
Q8. CNN 为什么需要激活函数?不用可以吗?
CNN 中的卷积运算是线性运算(输入与权重的加权和),没有激活函数的话,多层卷积堆叠等价于单层线性变换,网络的表达能力极为有限。 激活函数(如 ReLU)引入非线性,使网络能够拟合任意复杂的非线性函数。实践中几乎不可能不使用激活函数。
Q9. 卷积层和全连接层各在什么位置使用?
卷积层位于网络的前中部(特征提取阶段),全连接层位于网络的末尾(分类/回归阶段)。 卷积层利用局部连接和权重共享高效提取空间特征;全连接层整合全局信息做最终决策。典型的现代 CNN(如 ResNet)会保留 1~2 层全连接层在末端。
Q10. 为什么卷积层后面通常接 ReLU 而不是 Sigmoid 或 Tanh?
ReLU 计算简单(max(0,x))、梯度稳定(正区间梯度恒为 1,不存在 Sigmoid 的梯度消失问题),且具有稀疏激活性(输出零值比例高,减少过拟合)。 Sigmoid 和 Tanh 在深层网络中容易导致梯度消失(sigmoid 饱和区梯度接近 0),严重影响训练效率。ReLU 是 CNN 中最广泛使用的激活函数。
Q11. 如何计算 CNN 的参数量?
卷积层:参数量 = out_channels × in_channels × k_h × k_w + out_channels(偏置);全连接层:参数量 = in_features × out_features + out_features(偏置)。 池化层没有可学习参数。例如 Conv2d(3, 64, kernel_size=3) 的参数量 = 64 × 3 × 3 × 3 + 64 = 1,792。
Q12. 为什么 CNN 深层比浅层能提取更抽象的特征?
深层网络中,每个位置的输出值是浅层特征的加权和。浅层特征(边缘、角点)组合后形成中层特征(纹理、部件),再组合形成高层特征(物体类别)。 这与人类视觉皮层的层次化处理方式相似:V1 区处理边缘,V2 区组合边缘形成轮廓,更高层处理部件和物体。
Q13. 什么是分组卷积(Grouped Convolution)?
分组卷积将输入通道和输出通道分成 G 组,每组只与对应组的通道进行卷积,从而减少参数量和计算量。 当 G=in_channels=C_out 时,即为深度可分离卷积(Depthwise Separable Convolution),是 MobileNet 的核心组件。极端情况下 G=1 即为标准卷积,G=C_in=C_out 时每个输入通道独立卷积。
Q14. CNN 在处理图像时如何对抗过拟合?
CNN 对抗过拟合的主要手段包括:数据增强(翻转、裁剪、颜色扰动)、Dropout(在全连接层随机丢弃神经元)、权重正则化(L2 惩罚)、BatchNorm(归一化的同时有正则化效果)、Early Stopping(监控验证集性能提前停止)。
Q15. 转置卷积(Transposed Convolution / Deconvolution)是什么?
转置卷积是一种上采样操作,用于从低分辨率特征图恢复到高分辨率(如语义分割的上采样阶段)。 它不是卷积的逆运算,而是卷积的转置(梯度反向传播时的对应操作)。通过设置 stride>1 和 padding,输出尺寸会比输入尺寸大,实现空间分辨率的扩展。
Q16. 空洞卷积(Dilation / Atrous Convolution)是什么?
空洞卷积通过在卷积核内部插入零值(dilation rate)来扩大感受野,同时不增加参数量和计算量。 例如 dilation=2 的 3×3 卷积核,实际采样间隔为 2,感受野相当于 5×5,但参数量仍为 3×3。空洞卷积广泛用于语义分割(如 DeepLab 系列),在扩大感受野的同时保留高分辨率特征图。
Q17. 为什么目标检测中常用 stride=2 卷积而非 MaxPool 做下采样?
stride=2 卷积在下采样的同时还能学习特征,而 MaxPool 只是固定取最大值(无参数、无学习能力)。 ResNet 等现代网络普遍用 stride=2 卷积替代早期的 MaxPool 做下采样,既减少计算量又保留特征学习能力。但某些轻量级网络(如 MobileNet V1)仍保留 MaxPool 以降低计算成本。
Q18. CNN 中的权重初始化有多重要?
权重初始化直接影响训练的收敛速度和最终性能,初始化不当会导致梯度消失或爆炸。 常用初始化方法包括 Xavier(适合 Sigmoid/Tanh)、He initialization(适合 ReLU)和正交初始化。PyTorch 默认使用 nn.init.kaiming_normal_(He 初始化)。BatchNorm 层在一定程度上能缓解初始化不当的问题。
Q19. 如何计算卷积层的 FLOPs(浮点运算量)?
卷积层 FLOPs ≈ 2 × C_out × C_in × K_h × K_w × H_out × W_out(乘加各算一次)。 其中系数 2 来自每次乘加运算(一次乘法 + 一次加法)。理解 FLOPs 有助于评估模型的计算复杂度,权衡精度与速度。
Q20. Global Average Pooling(GAP)相比全连接层有什么优势?
GAP 将每个通道的空间信息压缩为单个均值,完全消除中间的分类全连接层。 相比使用大型全连接层(如 512×7×7 → 4096)的网络,GAP 大幅减少参数量(对 512×7×7 的特征图,传统 FC 层需要 512×7×7×N 个参数,GAP 消除了这些参数,只需在 GAP 后接一个分类头)。此外,GAP 对输入特征图的尺寸没有硬性要求,使网络可以处理任意尺寸的输入图像。
# 全局平均池化:将每个通道压缩为 1 个值
gap = nn.AdaptiveAvgPool2d((1, 1))
x = torch.randn(1, 512, 7, 7) # 中间层特征图
y = gap(x) # 输出 (1, 512, 1, 1)
y = torch.flatten(y, 1) # 展平为 (1, 512)
# 之后接全连接层或直接接分类头
FAQ 总结
- MLP vs CNN:全连接 vs 局部连接+权重共享
- 感受野:输出位置对应的输入区域,随层数扩大
- 常用配置:3×3 核、stride=1 或 2、padding=1、dilation=1
- 下采样:stride=2 卷积 或 MaxPool(前者可学习,后者固定)
- 特殊卷积:1×1(通道变换)、空洞(dilation>1,扩大感受野)、分组(减少计算)
- 七大参数:in/out_channels、kernel_size、stride、padding、dilation、bias、padding_mode
- 选择原则:根据任务、算力和数据量权衡架构
十、Roadmap 预告
下篇预告:《经典 CNN 架构详解:从 LeNet 到 ResNet》
理解了 CNN 的基本组件后,下一篇我们将深入学习这些组件是如何组合成完整架构的。在下篇中,我们将系统讲解:
- LeNet-5:CNN 的开山之作
- AlexNet:深度学习复兴的里程碑
- VGGNet:深而窄网络的探索
- GoogLeNet / Inception:多尺度并行架构
- ResNet:残差连接解决深度网络训练难题
- 轻量级网络:MobileNet、DenseNet
敬请期待!
