blog

Welcome to my blog!

Pytorch:nn.Conv2d()

ab's Avatar 2023-11-24 Accumulate

  1. 参考链接
  2. pytorch官方文档
    1. 注意
    2. 参数
  3. 形状
  4. 变量
  5. 示例
  6. 测试
    1. nn.Conv2d(类式接口)
    2. F.conv2d(函数式接口)

参考链接

pytorch官方文档

应用一个2D卷积在由多个输入平面组成的输入信号上。

在最简单的情况下,具有输入大小 $(N, C_{in}, H, W)$ 和输出 $(N, C_{out}, H_{out}, W_{out})$ 的层的输出值可以精确描述为:

  • *是有效的2D互相关运算符
  • N 是批大小
  • C 表示通道数
  • H 是像素中输入平面的高度
  • W 是像素中的宽度。


1
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • stride: 控制交叉相关的步长,可以是一个数字或一个元组。
  • padding: 控制应用于输入的填充量。可以是字符串 {'valid', 'same'} 或一个整数/整数元组,指定在两侧应用的隐式填充量。
  • dilation: 控制核心点之间的间距;也称为à trous算法。这个比较难描述,但是这个链接有一个很好的视觉化示例,展示了dilation的作用。
  • groups: 控制输入和输出之间的连接。in_channelsout_channels 必须都能被 groups 整除。例如:
    • groups=1 时,所有输入都与所有输出进行卷积。
    • groups=2 时,操作相当于并行地拥有两个卷积层,每个层看到一半的输入通道并产出一半的输出通道,然后将两者的输出连接起来。
    • groups=in_channels 时,每个输入通道都与其自己的一组滤波器(尺寸为 $(\frac{out_-channels}{in_-channels})$ 进行卷积。


参数 kernel_sizestridepaddingdilation 可以是:

  • 一个 int 单个整数 - 在这种情况下,相同的值用于高度和宽度维度
  • 一个由两个整数组成的 tuple 元组 - 在这种情况下,第一个 int 用于高度维度,第二个 int 用于宽度维度


注意

  • 注意:当 groups == in_channelsout_channels == K * in_channels,其中K是一个正整数时,该操作也被称为“深度可分卷积”。

    • 换句话说,对于输入大小为$(N, C_{in}, L_{in})$ 的情况,可以使用深度乘数K进行深度可分卷积,其参数为 $(C_{in} = C_{in}, C_{out} = C_{in} \times K, …, groups = C_{in})$。
  • 注意:在某些情况下,当在CUDA设备上给定张量并使用CuDNN时,此操作可能选择一个非确定性算法以提高性能。如果这不可取,你可以尝试通过设置 torch.backends.cudnn.deterministic = True 来使操作变为确定性(可能以牺牲性能为代价)。更多信息请参见可重现性

  • 注意:padding='valid' 相当于无填充。padding='same' 会填充输入,使输出具有与输入相同的形状。然而,这种模式不支持步长值不为1的任何情况。

  • 注意:该模块支持复杂的数据类型,即 complex32complex64complex128


参数

  • in_channels (int) - 输入图像的通道数。
  • out_channels (int) - 卷积产生的通道数。
  • kernel_size (int 或 tuple) - 卷积核的大小。
  • stride (int 或 tuple, 可选) - 卷积的步长。默认值:1。
  • padding (int, tuple 或 str, 可选) - 填充到输入的所有四边。默认值:0。
  • padding_mode (str, 可选) - 'zeros', 'reflect', 'replicate''circular'。默认:'zeros'
  • dilation (int 或 tuple, 可选) - 核元素之间的间距。默认值:1。
  • groups (int, 可选) - 从输入通道到输出通道的阻塞连接数。默认值:1。
  • bias (bool, 可选) - 如果为 True,则向输出添加可学习的偏置。默认值:True


形状

  • 输入:$(N, C_{in}, H_{in}, W_{in})$ 或 $(C_{in}, H_{in}, W_{in})$
  • 输出:$(N, C_{out}, H_{out}, W_{out})$ 或 $(C_{out}, H_{out}, W_{out})$,其中


变量

  • weight (Tensor): 模块可学习的权重,其形状为 (out_channels, in_channels / groups, kernel_size[0], kernel_size[1])。这些权重的值是从均匀分布 U(-√k, √k) 中采样的,其中 k = $\frac{1}{C_{in} * \prod_{i=0}^{groups} kernel_size[i]}$。
  • bias (Tensor): 如果 biasTrue,则模块形状为 (out_channels) 的可学习偏置。这些权重的值是从 U(-√k, √k) 中采样的,其中 k = $\frac{1}{C_{in} * \prod_{i=0}^{groups} kernel_size[i]}$。


示例

1
2
3
4
5
6
7
8
9
10
# 使用正方形核和相同步长
m = nn.Conv2d(16, 33, 3, stride=2)
# 使用非正方形核、不同步长和填充
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# 使用非正方形核、不同步长、填充和扩张
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
# 输入张量
input = torch.randn(20, 16, 50, 100)
# 输出张量
output = m(input)




测试

nn.Conv2d(类式接口)

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import torch
from torch import nn

# 2维的卷积层,用于图片的卷积
# 输入图像的通道数=1(灰度图像),卷积核的种类数=3
# 卷积核的shape是3乘3的,扫描步长为1,不加padding
layer = nn.Conv2d(1, 3, kernel_size=3, stride=1, padding=0)

# 要输入的原始图像
# 样本数=1,通道数=1,图像的shape是28乘28的
x = torch.rand(1, 1, 28, 28)

# 使用上面定义的卷积层layer和输入x,完成一次卷积的前向运算
out = layer.forward(x)
# 得到的还是1张图片,因为用了3种kernel所以输出的通道数变成3了
# 因为没加padding,原来28乘28的图像在3乘3卷积下得到的边长是28-3+1=26
print(out.shape) # torch.Size([1, 3, 26, 26])

# 添加padding看看
# 这次使用padding为1.所以原始图像上下左右都加了一层0
layer = nn.Conv2d(1, 3, kernel_size=3, stride=1, padding=1)
print(layer.forward(x).shape) # torch.Size([1, 3, 28, 28])

# 步长设置为2看看
# stride设置为2,也就是每次移动2格子(向上或者向右)
layer = nn.Conv2d(1, 3, kernel_size=3, stride=2, padding=1)
# 相当于每次跳1个像素地扫描,输出的Feature Map直接小了一半
print(layer.forward(x).shape) # torch.Size([1, 3, 14, 14])

# 实际使用时,应该这样用!
out = layer(x)
print(out.shape) # torch.Size([1, 3, 14, 14])

运行结果:

1
2
3
4
torch.Size([1, 3, 26, 26])
torch.Size([1, 3, 28, 28])
torch.Size([1, 3, 14, 14])
torch.Size([1, 3, 14, 14])


特别注意,在使用时应该直接使用layer(x)而不是layer.forward(x),因为前者实际是调用了__call__(),而PyTorch在这个函数中定义了一些hooks,如果要使用这些钩子的功能就只能用前者了!它会先运行hooks再运行.forward()函数。



在前面定义的卷积层的基础上,查看一下卷积层的权重(即卷积核)信息和偏置信息:

1
2
print(layer.weight)
print(layer.bias)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tensor([[[[ 0.1277, -0.1672,  0.1102],
[ 0.3176, 0.0236, 0.2537],
[ 0.0737, 0.0904, 0.0261]]],


[[[ 0.0349, -0.2042, 0.1766],
[-0.0938, -0.0470, 0.2011],
[-0.2460, 0.0876, 0.3124]]],


[[[-0.2361, -0.0971, -0.1031],
[-0.0756, -0.3073, 0.3227],
[-0.1951, -0.2395, -0.0769]]]], requires_grad=True)
Parameter containing:
tensor([ 0.0790, -0.3261, 0.0697], requires_grad=True)




查看一下shape:

1
2
print(layer.weight.shape)
print(layer.bias.shape)

运行结果:

1
2
torch.Size([3, 1, 3, 3])
torch.Size([3])



F.conv2d(函数式接口)

PyTorch里一般小写的都是函数式的接口,相应的大写的是类式接口。函数式的更加low-level一些,如果不需要做特别复杂的配置只要用类式接口就够了。

可以这样理解:nn.Conv2d是[2D卷积层],而F.conv2d是[2D卷积操作]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
from torch.nn import functional as F

# 手动定义卷积核(weight)和偏置
w = torch.rand(16, 3, 5, 5) # 16种3通道的5乘5卷积核
b = torch.rand(16) # 和卷积核种类数保持一致(不同通道共用一个bias)

# 定义输入样本
x = torch.randn(1, 3, 28, 28) # 1张3通道的28乘28的图像

# 2D卷积得到输出
out = F.conv2d(x, w, b, stride=1, padding=1) # 步长为1,外加1圈padding
print(out.shape)

out = F.conv2d(x, w, b, stride=2, padding=2) # 步长为2,外加2圈padding
print(out.shape)

运行结果:

1
2
torch.Size([1, 16, 26, 26])
torch.Size([1, 16, 14, 14])
本文最后更新于 天前,文中所描述的信息可能已发生改变
Perfect Night
LE SSERAFIM