blog

Welcome to my blog!

Pytorch:nn.Conv2d()

ab's Avatar 2023-11-24 Accumulate

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

参考链接

1.pytorch官方文档

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

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

out(Ni,Coutj)=bias(Coutj)+k=0Cin1weight(Coutj,k)input(Ni,k)out(N_i, C_{out_j}) = bias(C_{out_j}) + \sum_{k=0}^{C_{in}-1} weight(C_{out_j}, k) * input(N_i, k)

  • *是有效的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 时,每个输入通道都与其自己的一组滤波器(尺寸为 (outchannelsinchannels)(\frac{out_-channels}{in_-channels}) 进行卷积。

参数 kernel_sizestridepaddingdilation 可以是:

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

1.1 注意

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

    • 换句话说,对于输入大小为(N,Cin,Lin)(N, C_{in}, L_{in}) 的情况,可以使用深度乘数K进行深度可分卷积,其参数为 (Cin=Cin,Cout=Cin×K,...,groups=Cin)(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


1.2 参数

  • 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

1.3 形状

  • 输入:(N,Cin,Hin,Win)(N, C_{in}, H_{in}, W_{in})(Cin,Hin,Win)(C_{in}, H_{in}, W_{in})
  • 输出:(N,Cout,Hout,Wout)(N, C_{out}, H_{out}, W_{out})(Cout,Hout,Wout)(C_{out}, H_{out}, W_{out}),其中

Hout=Hin+2×padding[0]dilation[0]×(kernel_size[0]1)1stride[0]+1H_{out} = \frac{H_{in} + 2 \times padding[0] - dilation[0] \times (kernel\_size[0] - 1) - 1}{stride[0]} + 1

Wout=Win+2×padding[1]dilation[1]×(kernel_size[1]1)1stride[1]+1W_{out} = \frac{W_{in} + 2 \times padding[1] - dilation[1] \times (kernel\_size[1] - 1) - 1}{stride[1]} + 1


1.4 变量

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

1.5 示例

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)


2.测试

2.1 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])


2.2 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])
本文最后更新于 天前,文中所描述的信息可能已发生改变