Loading...

参考链接

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