上一篇笔记我们从宏观地学习了如何“搭建”模型,这次笔记深入剖析网络内部的具体网络层,从微观拆解。
一、卷积层
1.直观理解
卷积运算本质上是特征提取的过程。
- 卷积核: 又称为过滤器、滤波器,可以把它想象成一个“手电筒”或“探测器”。它包含了一组权重,代表某种特定的纹理或特征(如边缘、圆弧)。
- 卷积运算: 这个“手电筒”在输入(信号)图像上滑动,相应位置上进行乘加。
- 卷积维度:一般,卷积核在几个维度上滑动,就是几维卷积,如下图所示。

💡 关键理解:多通道卷积
上图有个小问题,没有考虑通道数,图中所示的都是一个通道的情况,比如二维灰度图像,通道数为1,如果是三通道的RGB二维图像呢?
- 输入: 如果输入是 RGB 图像(3通道),那么每一个卷积核也必须有 3 个通道(深度)。
- 运算: 3个通道分别进行卷积运算,产生 3 个中间矩阵,然后将它们相加,再加上一个偏置 (Bias),最终得到一张单通道的特征图 (Feature Map)。
- 输出: 如果想要输出 N N N 个通道的特征图,就需要 N N N 个这样的卷积核。
2.nn.Conv2d核心参数详解
在 PyTorch 中,Conv2d 是实现二维卷积的类,其参数决定了卷积的行为:
| 参数名 | 含义 | 详细说明 |
|---|---|---|
in_channels | 输入通道数 | 必须与输入数据的通道数一致(如 RGB 图像为 3) |
out_channels | 输出通道数 | 也就是卷积核的个数,决定了提取多少种特征 |
kernel_size | 卷积核尺寸 | 如 3 代表
3
×
3
3\times3
3×3,(3,5) 代表高3宽5 |
stride | 步长 | 滑动的跨度。默认为 1。步长越大,输出尺寸越小 |
padding | 填充 | 在输入边缘填充像素(通常填0)。用于保持输出尺寸或处理边缘信息 |
padding_mode | 填充模式 | 有四种:‘zeros’、‘reflect’、‘replicate’、‘circular’,默认为‘zeros’ |
dilation | 空洞/扩张 | 卷积核元素之间的间距。用于扩大感受野(Receptive Field) |
groups | 分组卷积 | 默认为 1。设为 in_channels 时即为深度可分离卷积 |
bias | 偏置 | 默认为True |
有一个需要注意的地方:

对于kernel_size,stride,padding和dilation四个参数,如果是一个值(int类),则对H和W两个维度都是这个值;如果是两个int组成的元组,则第一个数对应H维度,第二个数对应W维度,举例理解:
conv1 = nn.Conv2d(16, 33, 3, stride=2)
# 这里kernel_size等于一个值3,即H维和W维的尺寸都是3,等价于(3, 3);stride等于一个值2,即滑动时在H和W两个维度上步长都是2
conv2 = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# 这个卷积层,kernel_size为(3, 5),即H维是3,W维是5,即代表大小为3×5的卷积核;步长为(2, 1),即H维上滑动步长为2,W维上滑动步长为1;padding为(4, 2),即H维(上下各)填充4个像素,W维(左右各)填充2个像素。
3.卷积参数对输出尺寸的影响
- kernel_size、stride、padding等参数会改变特征图的尺寸
- out_channels会改变通道数
直观上看看:

一般来说,H=W,那么 输出尺寸 = f l o o r [ (输入长度 − 核大小 + 2 × p a d d i n g ) / s t r i d e + 1 ] , f l o o r 代表向下取整 输出尺寸=floor[(输入长度 - 核大小 + 2 × padding) / stride + 1],floor代表向下取整 输出尺寸=floor[(输入长度−核大小+2×padding)/stride+1],floor代表向下取整,那H≠W,输出尺寸见如下公式:

import torch
import torch.nn as nn
# 模拟一张图片:Batch=256, Channel=3, H=64, W=64
x = torch.randn(256, 3, 64, 64)
# 【情况1】改变通道数
# 输入3通道 -> 输出6通道 (需要6个卷积核)
conv_ch = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, padding=1)
print(f"{conv_ch(x).shape}") # torch.Size([256, 6, 64, 64])
# 【情况2】下采样 (改变特征图大小)
# stride=2, 特征图尺寸减半
conv_stride = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=2, padding=1)
print(f"{conv_stride(x).shape}") # torch.Size([256, 6, 32, 32])
# stride=2,padding=2
conv_sp = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=2, padding=2)
print(f"{conv_sp(x).shape}") # torch.Size([256, 6, 33, 33])
4.直观再理解
用一个例子来形象地体现卷积层的作用:
image_path = r"D:/Photos\desktop photos/t01662406687cf7219b.jpg"
img = Image.open(image_path).convert('RGB')
transform = transforms.Compose([transforms.ToTensor()])
img_tensor = transform(img)
x = img_tensor.unsqueeze_(dim=0)
conv = nn.Conv2d(3, 1, 3)
nn.init.xavier_normal_(conv_layer.weight.data)
img_conv = conv(img_tensor)
with torch.no_grad():
out = conv_layer(x) # 形状 (1,1,H,W)
# 取出并归一化到 0~1
out = out.squeeze().cpu().numpy()
out = (out - out.min()) / (out.max() - out.min() + 1e-8)
plt.figure(figsize=(10,6))
plt.subplot(1,2,1)
plt.imshow(img)
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(out)
plt.axis('off')
plt.show()
左边是原图,右边是卷积之后的结果。卷积后的结果,其实就是某一种模式或者说是特征图,我们将卷积核内的权重换一下,就可以得到另一种不同的特征图:


二、转置卷积
1.直观理解
如果说卷积层是“特征提取”与“尺寸压缩”,那么转置卷积层(Transposed Convolution)本质上是尺寸恢复或上采样的过程。
- 功能定位: 常用于语义分割(恢复原图大小)或生成对抗网络(GAN,从噪音生成图片)。可以把它想象成一个“投影仪”或“幻灯机”,将小尺寸的图像放大投射成大尺寸的图像。
- 转置运算: 它并不是卷积的数学逆运算(即无法恢复原始数值),而是尺寸上的逆变换。其内部通过在输入元素之间插入零(Zero Padding) 来扩大尺寸,然后再进行普通的卷积操作。

💡 关键理解:棋盘效应
由于转置卷积是通过插入零并通过卷积核滑动生成的,如果步长(stride)和卷积核大小(kernel_size)配合不好,输出图像上会出现明显的网格状伪影,就像棋盘一样。这是转置卷积特有的常见问题。
2.nn.ConvTranspose2d参数详解
在 PyTorch 中,ConvTranspose2d 是实现二维转置卷积的类。它的参数名与 Conv2d 几乎一样,但作用逻辑往往是相反的:

有一个需要注意的:
参数的反向理解:在普通卷积中,stride > 1 会导致尺寸缩小;而在转置卷积中,stride > 1 会导致尺寸变大。在普通卷积中,padding > 0 会导致“输入”变大(从而维持输出尺寸);而在转置卷积中,padding 参数参与公式运算的效果是让“输出”变小(相当于Crop操作)。
3.卷积参数对输出尺寸的影响
- stride、kernel_size、padding会改变尺寸大小。
- output_padding 用于填补计算带来的微小空缺。
直观上看看:

对一个 4×4 的输入进行 3×3 的转置卷积操作
尺寸变换: [ 4 × 4 ] ── T r a n s C o n v ( k = 3 ) ── > [ 6 × 6 ] [4×4] ──TransConv(k=3)──> [6×6] [4×4]──TransConv(k=3)──>[6×6]
等同于对一个 2×2 的输入(在两侧添加 2×2 的零填充)进行 3×3 核心卷积
import torch
from torch import nn
# 模拟特征图:Batch=1, Channel=3, H=4, W=4
x = torch.randn(1, 3, 4, 4)
ConvTrans2d = nn.ConvTranspose2d(3, 3, kernel_size=3) # torch.Size([1, 3, 6, 6])
print(f"{ConvTrans2d(x).shape}")
下图是公式,它的输入输出尺寸的关系与卷积层正好相反,可以发现:stride是放大倍数的主导因素,padding用于微调(缩小)边缘

import torch
from torch import nn
# 模拟特征图:Batch=1, Channel=3, H=32, W=32
x = torch.randn(1, 3, 32, 32)
# 【情况1】尺寸翻倍 (最经典用法)
# kernel_size=2, stride=2
# (32-1) × 2 + (2-1) + 1
ConvTrans2d = nn.ConvTranspose2d(3, 5, kernel_size=2, stride=2) # torch.Size([1, 5, 64, 64])
print(f"{ConvTrans2d(x).shape}")
# 【情况2】尺寸增加
# stride=1,kernel_size大小改变尺寸大小
# (32-1) × 1 + (3-1) + 1
ConvTrans2d = nn.ConvTranspose2d(3, 3, kernel_size=3, stride=1) # torch.Size([1, 3, 34, 34])
print(f"{ConvTrans2d(x).shape}")
# (32-1) × 1 + (5-1) + 1
ConvTrans2d = nn.ConvTranspose2d(3, 3, kernel_size=5, stride=1) # torch.Size([1, 3, 36, 36])
print(f"{ConvTrans2d(x).shape}")
# 【情况3】有填充
# kernel_size=2, stride=1,padding多1,尺寸小2
# (32-1) × 1 - 2 × 1 + (2-1) + 1
ConvTrans2d = nn.ConvTranspose2d(3, 3, kernel_size=2, stride=1, padding=1) # torch.Size([1, 3, 31, 31])
print(f"{ConvTrans2d(x).shape}")
# (32-1) × 1 - 2 × 4 + (2-1) + 1
ConvTrans2d = nn.ConvTranspose2d(3, 3, kernel_size=2, stride=1, padding=4) # torch.Size([1, 3, 25, 25])
print(f"{ConvTrans2d(x).shape}")
4.直观再理解
最后来看一个转置卷积的例子,直观再理解
image_path = r"D:/Photos\desktop photos/t01662406687cf7219b.jpg"
img = Image.open(image_path).convert('RGB')
img_transform = transforms.Compose([transforms.ToTensor()])
img_tensor = img_transform(img)
x = img_tensor.unsqueeze_(dim=0)
conv_layer = nn.ConvTranspose2d(3, 1, 3, stride=2)
nn.init.xavier_normal_(conv_layer.weight.data)
img_conv = conv_layer(img_tensor)
with torch.no_grad():
out = conv_layer(x)
out = out.squeeze().cpu().numpy()
out = (out - out.min()) / (out.max() - out.min() + 1e-8)
plt.figure(figsize=(10,6))
plt.subplot(1,2,1)
plt.imshow(img)
plt.subplot(1,2,2)
plt.imshow(out)
plt.show()
可以得到尺寸更大的特征图

2088

被折叠的 条评论
为什么被折叠?



