学习卷积操作
卷积层
1(第一个参数) → in_channels = 1
表示输入数据的通道数为 1,比如一张灰度图(只有亮度通道)。
1(第二个参数) → out_channels = 1
表示经过卷积后输出的通道数也是 1,也就是说只用一个卷积核对输入进行滤波,得到一张特征图。
kernel_size=(1, 2) → 卷积核的尺寸是 高=1,宽=2
即一个扁平的 1×2 小窗口,它会在输入上滑动,每次覆盖相邻的 2 个像素(水平方向),竖直方向不滑动(因为高度为 1)。
bias=False → 不使用偏置项
即卷积结果不再加一个可学习的常数,相当于 y = weight * x而不是 y = weight * x + bias
在 PyTorch 的nn.Conv2d中,偏置项默认是存在的,即默认bias=True。
print(conv2d.bias) # 打印整个 bias 参数
print(conv2d.bias.item()) # 如果只有一个输出通道,取出标量值
bias是随机的
卷积层和卷积核:
卷积核的权重形状是(out_channels, in_channels, H, W)。和写的nn.conv2d参数位置有点区别
这里的“深度” 指的就是形状中的第二个数字:in_channels(输入通道数)。
比如
nn.Conv2d(3, 16, ...),输入通道数是 3,那么每个卷积核的深度就是 3
padding和stride:
在 PyTorch 的nn.Conv2d中,如果你没有显式指定stride和padding,它们的默认值是:
stride = 1:卷积核每次移动 1 步(水平和垂直方向都是 1)。padding = 0:不在输入张量的四周填充额外的零。
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2) comp_conv2d(conv2d, X).shape conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4)) comp_conv2d(conv2d, X).shape输入通道: 1 (灰度图)
输出通道: 1 (1个卷积核)
卷积核尺寸: 3×5 (高度3,宽度5)
填充:
padding=(0, 1)→ 高度不填充,宽度每边填充1步长:
stride=(3, 4)→ 高度方向每次移动3,宽度方向每次移动4
卷积结果形状计算公式:
特征图通道数概念:(重要)
输出特征图的空间尺寸(高和宽)确实与输入输出通道数没有关系。公式里只涉及输入尺寸、卷积核尺寸、填充和步长这些空间参数。
不过,你需要区分两个概念:
输出特征图的通道数:等于你设置的
out_channels(即卷积核的组数),这个是由Conv2d的第2个参数决定的。输出特征图的高和宽:只由空间参数决定,与通道数无关。
一组卷积核对应一个输出(通道)
(一组卷积核有多少个卷积核)卷积核自身有几个通道(深度)对应输入通道in_channels(Conv2d的第1个参数决定的。)
例子:
需要确保X的形状与nn.Conv2d的第一个参数in_channels相匹配,即X创建 输入的第二维(通道维)必须是in_channels大小。
# 输入 X: shape = (batch, 1, 8, 8) # 1个输入通道 # (batch, channels, height, width) X = torch.rand(size=(1,1,8, 8)) conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1, stride=2) # 输出16个通道 out1 = conv1(X) print(out1.shape) # (batch, 16, 4, 4) 空间尺寸(4,4)与通道数无关 # 如果换成输入32个通道,输出64个通道 X = torch.rand(size=(32,32,8, 8)) conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1, stride=2) out2 = conv2(X) print(out2.shape) # 只要输入的空间尺寸还是(8,8),输出的空间尺寸依然是(4,4)结果:
torch.Size([1, 16, 4, 4]) torch.Size([32, 64, 4, 4]) 例子2:
unsqueeze是 PyTorch 张量方法,作用就一句话:
在指定的「维度位置(用索引表示 0 1 2 3 -1 -2 -3之类)」上,插入一个大小为 1 的新维度(dim)。
squeeze():把大小为 1 的维度“挤掉”
unsqueeze(dim):在 dim 处“造一个大小为 1 的维度”
实际里更推荐:别连着 unsqueeze,用reshape / view 但view(要求内存连续,更快也更严)
y = torch.zeros(1, 1, 8, 8)
y.squeeze().shape # (8, 8) ← 所有 1 被挤掉
y.squeeze(0).shape # (1, 8, 8) ← 只挤 dim=0(如果它==1)
卷积细节:
多通道变成单通道:
1个输出通道,那么就一组卷积核,个数等于输入通道数
输入通道1 → [卷积核1] → 特征图1
输入通道2 → [卷积核2] → 特征图2 → (逐元素相加) → 输出特征图
输入通道3 → [卷积核3] → 特征图3
每个输出通道都有一组卷积核
输入通道1 ──→ [卷积核1_1] ──→ 特征图1_1
输入通道2 ──→ [卷积核1_2] ──→ 特征图1_2 → 相加 → 输出通道1
输入通道3 ──→ [卷积核1_3] ──→ 特征图1_3
输入通道1 ──→ [卷积核2_1] ──→ 特征图2_1
输入通道2 ──→ [卷积核2_2] ──→ 特征图2_2 → 相加 → 输出通道2
输入通道3 ──→ [卷积核2_3] ──→ 特征图2_3
单通道变多通道:
有多组卷积核(因为输出是多通道),但每组卷积核就一个卷积核(因为是单通道输入)
当输入是单通道(如灰度图),输出是多通道(如 16 个通道)时,每个输出通道都由一个独立的 2D 卷积核与输入通道做卷积得到。
具体过程
输入:1 个通道,尺寸
H×W输出:16 个通道,每个通道尺寸由空间参数决定
步骤:
准备 16 个卷积核,每个卷积核的大小为
kernel_size × kernel_size(因为是单通道输入,所以每个卷积核是 2D 的)。第 1 个卷积核与输入通道做卷积 → 输出第 1 个通道的特征图。
第 2 个卷积核与输入通道做卷积 → 输出第 2 个通道的特征图。
……
第 16 个卷积核与输入通道做卷积 → 输出第 16 个通道的特征图。
每个输出通道可以加上各自的偏置(如果有)。
┌── 卷积核1 ──→ 输出通道1
├── 卷积核2 ──→ 输出通道2
输入通道 (1) ────┤ ...
├── 卷积核15 ─→ 输出通道15
└── 卷积核16 ─→ 输出通道16
区别
情况 | 权重形状 | 每个输出通道所需的卷积核数量 |
|---|---|---|
单通道→多通道 |
| 1 个 2D 卷积核 |
多通道→多通道 |
|
|
多变单内部原理代码:
def corr2d_multi_in(X, K): return sum(d2l.corr2d(x, k) for x, k in zip(X, K)) X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]], [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]]) K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]]) corr2d_multi_in(X, K) 参数含义 X:输入张量,形状为 (C_in, H, W) C_in:输入通道数 H:高度 W:宽度 K:卷积核张量,形状为 (C_in, k_h, k_w) 必须有相同的通道数 C_in k_h:卷积核高度 k_w:卷积核宽度 执行过程 zip(X, K):将输入 X和卷积核 K按通道配对 假设 X是 3 个通道,X[0]、X[1]、X[2]是每个通道的 2D 矩阵 假设 K也是 3 个通道,K[0]、K[1]、K[2]是每个通道的 2D 卷积核 zip(X, K)产生对:(X[0], K[0])、(X[1], K[1])、(X[2], K[2]) d2l.corr2d(x, k):对每个通道单独做 2D 互相关计算 用 K[0]卷积核在 X[0]输入通道上做互相关 → 得到一个 2D 输出 用 K[1]卷积核在 X[1]输入通道上做互相关 → 得到一个 2D 输出 用 K[2]卷积核在 X[2]输入通道上做互相关 → 得到一个 2D 输出 sum(...):将所有通道的 2D 输出逐元素相加 最后得到一个单通道的 2D 输出等价代码:
# 假设 X 形状 (3, 5, 5), K 形状 (3, 3, 3) result = torch.zeros((3, 3)) # 假设无填充,步长=1,输出是3×3 # 用循环实现 for i in range(3): # 遍历每个通道 x_i = X[i] # 第i个输入通道 k_i = K[i] # 第i个卷积核 result += d2l.corr2d(x_i, k_i) # 累积求和相当于pytorch的:
# 创建一个单输出通道的卷积层 conv = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3, bias=False) # 设置权重 conv.weight.data = K.unsqueeze(0) # 形状从(3,3,3)变成(1,3,3,3) # 前向传播 output = conv(X.unsqueeze(0)) # 添加批次维度多变多:
def corr2d_multi_in_out(X, K): return torch.stack([corr2d_multi_in(X, k) for k in K], dim=0)#或者不写dim 直接写0 #corr2d_multi_in(X, k)函数在上面有写 #调用 通过将核张量K与K+1(K中每个元素加1)和K+2连接起来,构造了一个具有3个输出通道的卷积核。 K = torch.stack((K, K + 1, K + 2), 0) corr2d_multi_in_out(X, K) 参数含义 X:输入张量,形状为 (C_in, H, W) C_in:输入通道数 H:高度 W:宽度 K:卷积核张量,形状为 (C_out, C_in, k_h, k_w) C_out:输出通道数 C_in:输入通道数(必须与 X的通道数匹配) k_h:卷积核高度 k_w:卷积核宽度 执行过程 [corr2d_multi_in(X, k) for k in K] 遍历 K的第一个维度(C_out个输出通道) 对于每个输出通道 i,取 K[i],形状为 (C_in, k_h, k_w) 将这个 K[i]与输入 X传给 corr2d_multi_in函数 得到这个输出通道的计算结果(一个 2D 张量) torch.stack(..., dim=0) 将 C_out个 2D 输出张量沿着新的第 0 个维度堆叠 最终得到形状为 (C_out, H_out, W_out)的输出torch.stack是 PyTorch 中用于沿新维度拼接多个张量的函数。它会将所有输入张量堆叠成一个更高维度的张量
torch.stack(tensors, dim=0)
tensors:一个由多个形状完全相同的张量组成的序列(列表或元组)。
dim:指定新维度插入的位置(默认为0)。
工作原理
假设你有三个形状均为 (a, b)的张量 T1, T2, T3:
torch.stack([T1, T2, T3], dim=0)→ 新张量形状 (3, a, b)
torch.stack([T1, T2, T3], dim=1)→ 新张量形状 (a, 3, b)
torch.stack([T1, T2, T3], dim=2)→ 新张量形状 (a, b, 3)
输入X: (3, 5, 5) # 3个输入通道
卷积核K: (4, 3, 3, 3) # 4个输出通道,每个有3个卷积核
计算:
K[0] (3, 3, 3) + X → 通过corr2d_multi_in → 输出通道0 (3×3矩阵)
K[1] (3, 3, 3) + X → 通过corr2d_multi_in → 输出通道1 (3×3矩阵)
K[2] (3, 3, 3) + X → 通过corr2d_multi_in → 输出通道2 (3×3矩阵)
K[3] (3, 3, 3) + X → 通过corr2d_multi_in → 输出通道3 (3×3矩阵)
将4个(3×3)矩阵堆叠 → 输出(4, 3, 3)
pytorch代码:
# 用 PyTorch 实现相同的功能 conv = nn.Conv2d(in_channels=3, out_channels=4, kernel_size=3, bias=False) conv.weight.data = K # 设置卷积核权重 output = conv(X.unsqueeze(0)).squeeze(0) # 添加批次维度再移除 # output 形状: (4, 3, 3)等价代码:
# 假设 X 形状 (3, 5, 5) # 假设 K 形状 (4, 3, 3, 3) outputs = [] # 存储每个输出通道的结果 # 对每个输出通道 for i in range(4): # 4个输出通道 # 取出第i个输出通道的卷积核组 k_i = K[i] # 形状 (3, 3, 3),3个卷积核分别对应3个输入通道 # 计算第i个输出通道 channel_i = corr2d_multi_in(X, k_i) # 形状 (3, 3) outputs.append(channel_i) # 将所有输出通道堆叠 result = torch.stack(outputs, dim=0) # 形状 (4, 3, 3)1x1卷积层==全连接层:(重点)唯一作用是改变通道数量
池化/汇聚层:
对于给定输入元素,最大汇聚层会输出该窗口内的最大值,平均汇聚层会输出该窗口内的平均值。
*汇聚层的主要优点之一是减轻卷积层对位置的过度敏感。
* 我们可以指定汇聚层的填充和步幅。
*使用最大汇聚层以及大于1的步幅,可减少空间维度(如高度和宽度)。
* 汇聚层的输出通道数与输入通道数相同。
最大汇聚层(maximum pooling)和平均汇聚层(average pooling)。
torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)
默认情况下,(深度学习框架中的步幅与汇聚窗口的大小相同)。 因此,如果我们使用形状为(3, 3)的汇聚窗口,那么默认情况下,我们得到的步幅形状为(3, 3)
pool2d = nn.MaxPool2d(3)
pool2d(X)
在处理多通道输入数据时,[汇聚层在每个输入通道上单独运算],而不是像卷积层一样在通道上对输入进行汇总。这意味着汇聚层的输出通道数与输入通道数相同。
维度定义(重要) troch.cat连接
在 PyTorch 中,多维数据的维度(dim)通常这样定义(以图像数据为例):
dim=0:代表“批量大小” (Batch Size) 或者“样本个数”。
dim=1:代表“通道数” (Channels)。
dim=2:代表“高度” (Height)。
dim=3:代表“宽度” (Width)。
所以,代码里的, 1明确表示:把这两个数据在“通道”这个维度上拼起来。
池化函数参数细节
如何控制?
默认(ceil_mode=False):丢弃不足的部分,输出尺寸较小。
ceil_mode=True:保留不足的部分(使用部分数据池化),输出尺寸较大。
另外,也可以通过padding 来人为补足,使输入尺寸能被步长整除,避免丢弃
池化层结果计算:
卷积池化层参数对比
特性 | 卷积层 | 池化层 |
|---|---|---|
默认步长 | 1 | kernel_size |
填充 | 可指定 | 默认0 |
膨胀 | 可指定 | 默认1(通常不变) |
边界处理 | 无ceil选项 | 有ceil_mode选项 |
