1 . 图像的卷积操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch as t
from torch import nn
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage

to_tensor = ToTensor()
to_pil = ToPILImage()
lena = Image.open('path to your image')

# 输入是一个 batch,batch_size=1
input = to_tensor(lena).unsqueeze(0)

# 锐化卷积核
kernel = t.ones(3, 3)/-9
kernel[1][1] = 1
conv = nn.Conv2d(1, 1, (3, 3), 1, bias=False)
conv.weight.data = kernel.view(1, 1, 3, 3)

out = conv(input)
to_pil(out.data.squeeze(0))

2 . 池化层可以看作是一种特殊的卷积层,用来下采样,但池化层没有可学习的参数,其 weight 是固定的。

1
2
3
4
5
pool = nn.AvgPool2d(2, 2)
print(list(pool.parameters())) # []

out = pool(input)
to_pil(out.data.squeeze(0))

3 . 除了卷积层和池化层,深度学习中还将常用到以下几层:

  • Linear:全连接层;
  • BatchNorm:批规范化层,分为 1D、2D 和 3D。除了标准的 BatchNorm 之外,还有在风格迁移中常用到的 InstanceNorm 层;
  • Dropout:dropout 层用来防止过拟合,同样分为 1D、2D 和 3D。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 输入 batch_size = 2,维度3
input = t.randn(2, 3)
linear = nn.Linear(3, 4)
h = linear(input)
print(h)

# 4 channel,初始化标准差为 4,均值为 0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)

bn_out = bn(h)
# 注意输出的均值和方差
# 方差是标准差的平方,计算无偏方差分母会减 1
# 使用 unbiased=False,分母不减 1
print(bn_out.mean(0), bn_out.var(0, unbiased=False))

# 每个元素以 0.5 的概率舍弃
dropout = nn.Dropout(0.5)
o = dropout(bn_out)
print(o) # 有一半的数变为0

4 . PyTorch 实现了常见的激活函数,这些激活函数可作为独立的 layer 使用,这里介绍一下常用的激活函数 ReLU,其数学表达式为 $ReLU(x)=max(0, x)$。

1
2
3
4
5
relu = nn.ReLU(inplace=True)
input = t.randn(2, 3)
print(input)
output = relu(input) # 等价于 input.clamp(min=0)
print(output) # 小于 0 的被截断为 0

ReLU 函数有个 inplace 参数,如果设置为 True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。之所以可以覆盖是因为在计算 ReLU 的反向传播时,只需根据输出就能够推算反向传播的梯度。但是只有少数的 autograd 操作支持 inplace 操作(如 tensor.sigmoid_()),除非你明确地知道自己在做什么,否则一般不要使用 inplace 操作。

5 . 在以上例子中,基本上都是将每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络,对于此类网络如果每次都写复杂的 forward 函数会有些麻烦,有两种简化方式,ModuleListSequential,其中 Sequential 是一个特殊的 Module,它包含几个子 Module,前向传播时会将输入一层接一层地传递下去,ModuleList 也是一个极其特殊的 Module,可以包含几个子 Module,可以像 list 一样使用它,但不能直接把输入传递给 ModuleList。

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
33
34
35
from collections import OrderedDict

import torch as t
from torch import nn

# Sequential 的三种写法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())

net2 = nn.Sequential(
nn.Conv2d(3, 3, 3),
nn.BatchNorm2d(3),
nn.ReLU()
)

net3 = nn.Sequential(OrderedDict[
('conv1', nn.Conv2d(3, 3, 3)),
('bn1', nn.BatchNorm2d(3)),
('relu1', nn.ReLU())
])

print('net1:', net1)
print('net2:', net2)
print('net3:', net3)

# 可根据名字或序号取出子 Module
print(net1.conv, net2[0], net3.conv1)

input = t.rand(1, 3, 4, 4)
output = net1(input)
output = net2(input)
output = net3(input)
output = net3.relu1(net1.batchnorm(net1.conv(input)))
1
2
3
4
5
6
7
8
9
10
import torch as t
from torch import nn

modellist = nn.ModuleList([nn.Linear(3, 4), nn.ReLU(), nn.Linear(4, 2)])
input = t.randn(1, 3)
for model in modellist:
input = model(input)

# 下面会报错,因为 modellist 没有实现 forward 方法
# output = modellist(input)

ModuleListModule 的子类,当在 Module 中使用它的时候,就能自动识别为子 module,而 python 自带的 list 则不行。

Input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from torch import nn


class MyModule(nn.Module):
def __init__(self):
super(MyModule, self).__init__()
self.list = [nn.Linear(3, 4), nn.ReLU()]
self.module_list = nn.ModuleList([nn.Conv2d(3, 3, 3), nn.ReLU()])

def forward(self):
pass


model = MyModule()
print(model)

for name, param in model.named_parameters():
print(name, param)

Output:

可见,list 中的子 Module 并不能被主 Module 所识别,而 ModuleList 中的子 Module 能够被主 Module 所识别,这意味着如果用 list 保存子 Module,将无法调整其参数,因其未加入到主 Module 的参数中。

在实际应用中,如果在构造函数 __init__ 中用到 list、tuple、dict 等对象时,一定要思考是否应该用 ModuleList 或 ParameterList 代替。


笔记来源:《pytorch-book》