1 . nn 中的大多数 layer,在 nn.functional 中都有一个与之相对应的函数,nn.functional 中的函数和 nn.Module 的主要区别在于,用 nn.Module 实现的 layers 是一个特殊的类,都是由 class layer(nn.Module) 定义,会自动提取可学习的参数,而 nn.functional 中的函数更像是纯函数,由 def function(input) 定义。

Input:

1
2
3
4
5
6
7
8
9
10
11
12
import torch as t
from torch import nn

input = t.randn(2, 3)
model = nn.Linear(3, 4)
output1 = model(input)
output2 = nn.functional.linear(input, model.weight, model.bias)
print(output1 == output2)

b = nn.functional.relu(input)
b2 = nn.ReLU()(input)
print(b == b2)

Output:

如果模型有可学习的参数,最好用 nn.Module,否则既可以用 nn.Module 也可以使用 nn.functional,二者在性能上没有太大差异。但 dropout 操作虽然没有可学习的参数,但还是建议使用 nn.Dropout 而不是 nn.functional.dropout,因为 dropout 在训练和测试两个阶段的行为有所差异,使用 nn.Module 对象能够通过 model.eval 操作加以区分。

在模型中搭配使用 nn.Modulenn.functional

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


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
x = F.pool(F.relu(self.conv1(x)), 2)
x = F.pool(F.relu(self.conv2(x)), 2)
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x

对于不具备可学习参数的层(激活层、池化层等),将它们用函数代替,这样则可以不用放置在构造函数 __init__ 中。

2 . 在深度学习中参数的初始化非常重要,良好的初始化能让模型更快收敛,并达到更高水平,而糟糕的初始化则可能使模型迅速瘫痪。nn.Module 模块的参数都采取了较为合理的初始化策略,因此一般不需要我们考虑。而当我们使用 Parameter 时,自定义初始化尤其重要,PyTorch 中 nn.init 模块就是专门为初始化而设计的,如果某种初始化策略 nn.init 不提供,用户也可以自己直接初始化。

1
2
3
4
5
6
# 利用 nn.init 初始化
from torch.nn import init
linear = nn.Linear(3, 4)

t.manual_seed(1)
init.xavier_normal_(linear.weight) # 等价于 linear.weight.data.normal_(0, std)
1
2
3
4
5
6
7
# 直接初始化
import math
t.manual_seed(1)

# xavier 初始化的计算公式
std = math.sqrt(2)/math.sqrt(7.)
linear.weight.data.normal_(0, std)
1
2
3
4
5
6
7
8
9
10
# 对模型的所有参数进行初始化
for name, params in net.named_parameters():
if name.find('linear') != -1:
# init linear
params[0] # weight
params[1] # bias
elif name.find('conv') != -1:
pass
elif name.find('norm') != -1:
pass

3 . nn.Module 基类的构造函数:

1
2
3
4
5
6
7
def __init__(self):
self._parameters = OrderedDict()
self._modules = OrderedDict()
self._buffers = OrderedDict()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self.training = True
  • _parameters:保存用户直接设置的 Parameter;
  • _modules:指定的子 module 会保存于此;
  • _buffers:缓存,如 batchnorm 使用 momentum 机制,每次前向传播需用到上一次前向传播的结果;
  • _backward_hooks_forward_hooks:钩子技术,用来提取中间变量,类似 variable 的 hook;
  • training:BatchNorm 与 Dropout 层在训练阶段和测试阶段会分别采取不同的策略,通过判断 training 的值来决定前向传播策略。

上述几个属性中,_parameters_modules_buffers 这三个字典中的值,都可以通过 self.key 方式获得,效果等价于 self._parameters[key]

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import torch as t
from torch import nn


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 等价于 self.register_parameter('param1', nn.Patameter(t.rand(3, 3)))
self.param1 = nn.Parameter(t.rand(3, 3))
self.submodel1 = nn.Linear(3, 4)

def forward(self, input):
x = self.param1.mm(input)
x = self.submodel1(x)
return x


net = Net()
print('-------- Net 结构 --------')
print(net)
print('-------- Net 结构 --------')
print('\n')

print('-------- Net 中的子 module --------')
print(net._modules)
print('-------- Net 中的子 module --------')
print('\n')

print('-------- Net 中的自定义参数 --------')
print(net._parameters)
print('-------- Net 中的自定义参数 --------')
print('\n')

print('-------- Net 中 para1 的参数 --------')
print(net.param1) # 等价于 net._parameters['param1']
print('-------- Net 中 para1 的参数 --------')
print('\n')

print('-------- Net 中的参数大小 --------')
for name, parameter in net.named_parameters():
print(name, parameter.size())
print('-------- Net 中的参数大小 --------')
print('\n')

print('-------- Net 中的子 module 及其名称 --------')
for name, submodel in net.named_modules():
print(name, submodel)
print('-------- Net 中的子 module 及其名称 --------')
print('\n')

bn = nn.BatchNorm1d(2)
input = t.rand(3, 2)
output = bn(input)
print(bn._buffers)

nn.Module 在实际使用中可能层层嵌套,一个 module 包含若干个子 module,每一个子 module 又包含更多的子 module,children 方法可以查看直接子 module,module 函数可以查看所有的子 module(包含当前 module)。与之相对应的还有函数 named_childrennamed_modules,其能够在返回 module 列表的同时返回它们的名字。

dropout 在训练和测试阶段采取不同策略举例:

1
2
3
4
5
6
7
8
input = t.arange(0, 12).view(3, 4)
model = nn.Dropout()
# 在训练阶段,会有一半左右的数被随机置为 0
print(model(input))

# 在测试阶段,dropout 什么都不做
model.training = False
model(input)

如果一个模型具有多个 dropout 层,不需要为每个 dropout 层指定 training 属性,更为推荐的做法是调用 model.train() 函数,它会将当前 module 及其子 module 中的所有 training 属性都设置为 True,相应的,model.eval() 函数会把 training 属性都设为 False。

register_forward_hookregister_backward_hook,这两个函数的功能类似于 variable 函数的 register_hook,可在 module 前向传播或反向传播时注册钩子。每次前向传播执行结束后会执行钩子函数(hook)。前向传播的钩子函数具有如下形式:hook(module, input, output) -> None,而反向传播则具有如下形式:hook(module, grad_input, grad_output) -> Tensor or None。钩子函数不应修改输入和输出,并且在使用后应及时删除,以避免每次都运行钩子增加运行负载。钩子函数主要用在获取某些中间结果的情景,如中间某一层的输出或某一层的梯度。这些结果本应写在 forward 函数中,但如果在 forward 函数中专门加上这些处理,可能会使处理逻辑比较复杂,这时候使用钩子技术就更合适一些。

下面考虑一种场景,有一个预训练好的模型,需要提取模型的某一层(不是最后一层)的输出作为特征进行分类,但又不希望修改其原有的模型定义文件,这时就可以利用钩子函数。下面给出实现的伪代码。

1
2
3
4
5
6
7
8
9
10
11
model = VGG()
features = t.Tensor()

def hook(module, input, output):
'''把这层的输出拷贝到 features 中'''
features.copy_(output.data)

handle = model.layer8.register_forward_hook(hook)
_ = model(input)
# 用完 hook 后删除
handle.remove()

笔记来源:《pytorch-book》