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 tfrom torch import nninput = 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.Module
和 nn.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 nnfrom torch.nn import functional as Fclass 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 from torch.nn import initlinear = nn.Linear(3 , 4 ) t.manual_seed(1 ) init.xavier_normal_(linear.weight)
1 2 3 4 5 6 7 import matht.manual_seed(1 ) 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 : params[0 ] params[1 ] 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 tfrom torch import nnclass Net (nn.Module) : def __init__ (self) : super(Net, self).__init__() 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) 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_children
和 named_modules
,其能够在返回 module 列表的同时返回它们的名字。
dropout 在训练和测试阶段采取不同策略举例:
1 2 3 4 5 6 7 8 input = t.arange(0 , 12 ).view(3 , 4 ) model = nn.Dropout() print(model(input)) model.training = False model(input)
如果一个模型具有多个 dropout 层,不需要为每个 dropout 层指定 training 属性,更为推荐的做法是调用 model.train()
函数,它会将当前 module 及其子 module 中的所有 training 属性都设置为 True,相应的,model.eval()
函数会把 training
属性都设为 False。
register_forward_hook
与 register_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) handle.remove()
笔记来源:《pytorch-book》