Índice
prefácio
Ao criar um modelo de aprendizado profundo, geralmente encontramos nn.Sequential
essas nn.ModuleList
três nn.ModuleDict
coisas, especialmente durante o treinamento de aprendizagem por transferência. O que são, como são usados e quais precauções devem ser tomadas ao usá-los? Dê uma olhada nesta postagem do blog.
1.nn.Módulo
Antes de apresentar esses três contêineres, precisamos saber o que é Module
. Quando criamos modelos, quase todos os modelos herdam dessa classe. Ele é a classe base de todas as redes, utilizada para gerenciar as propriedades da rede. Existem dois módulos associados a ele: nn.Parameter
e nn.functional
. Todos esses módulos vêm do torch.nn
. Abaixo apresentamos brevemente esses módulos.
1.1. nn.Parâmetro
A primeira nn.Parameter
, em Pytorch
, nn.Parameter
é uma classe especial para criar parâmetros do modelo. Em um modelo, geralmente existem muitos parâmetros e não é uma tarefa fácil gerenciar manualmente esses parâmetros. Pytorch
Geralmente, os parâmetros são nn.Parameter
usados para representar e nn.Module
gerenciar todos os parâmetros sob sua estrutura.
## nn.Parameter 具有 requires_grad = True 属性
w = nn.Parameter(torch.randn(2,2))
print(w) # tensor([[ 0.3544, -1.1643],[ 1.2302, 1.3952]], requires_grad=True)
print(w.requires_grad) # True
## nn.ParameterList 可以将多个nn.Parameter组成一个列表
params_list = nn.ParameterList([nn.Parameter(torch.rand(8,i)) for i in range(1,3)])
print(params_list)
print(params_list[0].requires_grad)
## nn.ParameterDict 可以将多个nn.Parameter组成一个字典
params_dict = nn.ParameterDict({
"a":nn.Parameter(torch.rand(2,2)),
"b":nn.Parameter(torch.zeros(2))})
print(params_dict)
print(params_dict["a"].requires_grad)
Os parâmetros definidos acima podem ser gerenciados através do Módulo:
# module.parameters()返回一个生成器,包括其结构下的所有parameters
module = nn.Module()
module.w = w
module.params_list = params_list
module.params_dict = params_dict
num_param = 0
for param in module.parameters():
print(param,"\n")
num_param = num_param + 1
print("number of Parameters =",num_param)
No uso real, nn.Module
a classe do módulo é geralmente construída por herança e todas as partes que contêm parâmetros que precisam ser aprendidos são colocadas no construtor.
#以下范例为Pytorch中nn.Linear的源码的简化版本
#可以看到它将需要学习的参数放在了__init__构造函数中,并在forward中调用F.linear函数来实现计算逻辑。
class Linear(nn.Module):
__constants__ = ['in_features', 'out_features']
def __init__(self, in_features, out_features, bias=True):
super(Linear, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = nn.Parameter(torch.Tensor(out_features, in_features))
if bias:
self.bias = nn.Parameter(torch.Tensor(out_features))
else:
self.register_parameter('bias', None)
def forward(self, input):
return F.linear(input, self.weight, self.bias)
1.2. nn.funcional
nn.functional
(Geralmente renomeado como F após a introdução) Existem implementações de função de vários componentes funcionais. por exemplo:
- Série de funções de ativação (
F.relu, F.sigmoid, F.tanh, F.softmax
)- Série de camada de modelo (
F.linear, F.conv2d, F.max_pool2d, F.dropout2d, F.embedding
)- série de funções de perda (
F.binary_cross_entropy, F.mse_loss, F.cross_entropy
)
Para facilitar o gerenciamento de parâmetros, geralmente é nn.Module
convertido em um formulário de implementação de classe por meio de herança e encapsulado diretamente no nn
módulo:
- A função de ativação torna-se (
nn.ReLu, nn.Sigmoid, nn.Tanh, nn.Softmax
)- camada de modelo (
nn.Linear, nn.Conv2d, nn.MaxPool2d, nn.Embedding
)- função de perda (
nn.BCELoss, nn.MSELoss, nn.CrossEntorpyLoss
)
nn
Portanto , as funções de ativação, camadas e funções de perda que estabelecemos na superfície são todas functional
implementadas nos bastidores. Se você continuar olhando para baixo, saberá que nn.Module
este módulo é realmente muito poderoso, pois além de gerenciar vários parâmetros referenciados por ele, ele também pode gerenciar submódulos referenciados por ele.
1.3. nn.Módulo
Nosso foco é apresentar este nn.Module
módulo. nn.Module
Existem muitos atributos de dicionário importantes em:
self.training = True
self._parameters: Dict[str, Optional[Parameter]] = OrderedDict()
self._buffers: Dict[str, Optional[Tensor]] = OrderedDict()
self._non_persistent_buffers_set: Set[str] = set()
self._backward_hooks: Dict[int, Callable] = OrderedDict()
self._is_full_backward_hook = None
self._forward_hooks: Dict[int, Callable] = OrderedDict()
self._forward_pre_hooks: Dict[int, Callable] = OrderedDict()
self._state_dict_hooks: Dict[int, Callable] = OrderedDict()
self._load_state_dict_pre_hooks: Dict[int, Callable] = OrderedDict()
self._modules: Dict[str, Optional['Module']] = OrderedDict()
Só precisamos nos concentrar em dois deles: _parameters
e_modules
_parameters
: Armazena e gerenciann.Parameter
atributos pertencentes à classe, como pesos, polarizando esses parâmetros_modules
: A classe de gerenciamento de armazenamentonn.Module
, comoLeNet
na rede clássica, criará submódulos, camadas convolucionais, camadas de agrupamento e será armazenado em _modules
Aqui está uma pergunta: Qual é a diferença entre nn.Parameter
e nn.Module
em _parameters
?
nn.Parameter
: étorch.Tensor
uma subclasse de e é usado para marcar tensores como parâmetros do modelo que podem ser aprendidos. No processo de definição de um modelo, geralmente usamosnn.Parameter
para criar parâmetros que podem ser aprendidos como atributos do modelo. A vantagem disso é quenn.Parameter
o objeto será registrado automaticamente como parâmetro do modelo e participará do cálculo do gradiente e da atualização dos parâmetros._parameters
: Énn.Module
um atributo da classe, que é um dicionário usado para armazenar os parâmetros apreensíveis do modelo. As chaves do dicionário são os nomes dos parâmetros e os valores são os tensores (nn.Parameter
tipos) dos parâmetros correspondentes._parameters
O valor do atributo extrai automaticamente os parâmetros apreensíveis dos atributos do modelo e os adiciona ao dicionário.
pode ser _parameters
pensado como um contêiner para armazenar os parâmetros que podem ser aprendidos de um modelo e nn.Parameter
é uma classe especial para criar e rotular esses parâmetros. Usando para nn.Parameter
criar parâmetros e usá-los como atributos de modelo, esses parâmetros serão adicionados automaticamente ao _parameters
dicionário para facilitar seu gerenciamento e operação unificados. Ou seja, nn.Parameter
é uma classe especial para criar parâmetros do modelo e _parameters
é um atributo de dicionário que armazena os parâmetros do modelo. nn.Parameter
Os parâmetros criados com são adicionados automaticamente _parameters
ao dicionário para facilitar o gerenciamento e o acesso aos parâmetros do modelo.
nn.Module
Como é o processo de construção de uma rede fabric? Tome a seguinte rede como exemplo:
import torch
from torch import nn
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 1, 1, 1)
self.bn = nn.BatchNorm2d(1)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(1, 1, 1, 1)
def forward(self, x):
x = self.conv1(x)
x = self.bn(x)
x = self.relu(x)
x = self.conv2(x)
return x
if __name__ == "__main__":
dat = torch.rand(1, 1, 10, 10)
net = Net().cuda()
O processo de construção é o seguinte:
Primeiro, temos um grande
Module
(criado acimaNet
) herdandonn.Module
essa classe base, como a acimaNet
, e entãoNet
pode haver muitos submódulos nela, e esses submódulos também são herdados.nn.Module
NessesModule
métodos__init__
, uma inicialização das propriedades da classe pai será realizada chamando primeiro o método de inicialização da classe pai. Então, ao construir cada submódulo, ele é realmente dividido em duas etapas: a primeira etapa é a inicialização e, em seguida, o tipo__setattr__
julgado por esse métodovalue
é salvo no dicionário de atributos correspondente e, em seguida, atribuído ao membro correspondente. Esses submódulos são construídos um a um e toda aNet
construção é finalmente concluída. Você pode depurar o processo específico sozinho.
Resumir:
- Um
module
pode conter várias subclassesmodule
(Net
incluindo camadas convolucionais,BN
camadas, funções de ativação)- Um
module
é equivalente a uma operação eforward()
a função deve ser implementada (o encaminhamento de alguns módulos precisa ser reescrito por você, você saberá quando ler)- Cada um
module
tem muitos dicionários para gerenciar seus atributos (o mais usado é_parameters
,_modules
)
Conhecendo o processo de construção da rede, podemos analisar o modelo criado por outros e extrair algumas partes dele. Para a introdução desta parte, consulte esta postagem do blog: Pytorch extrai estrutura de camada de rede neural, parâmetros de camada e inicialização personalizada .
二. nn. Sequencial
Depois de apresentar nn.Module
os módulos acima, vamos começar a apresentar o contêiner. Em primeiro lugar, vamos dar uma olhada nele nn.Sequential
, nn.Sequential
que é PyTorch
um contêiner de módulo em , que é usado para combinar vários módulos em sequência. Ele pode conectar uma série de módulos para formar uma estrutura de modelo serial. Vamos dar uma olhada em como pytorch
é implementado em , aqui olhamos apenas para o construtor e a parte de propagação direta, e o código das outras partes é omitido:
class Sequential(Module):
...
def __init__(self, *args):
super(Sequential, self).__init__()
if len(args) == 1 and isinstance(args[0], OrderedDict):
for key, module in args[0].items():
self.add_module(key, module)
else:
for idx, module in enumerate(args):
self.add_module(str(idx), module)
...
def forward(self, input):
for module in self:
input = module(input)
return input
Como você pode ver no código acima, nn.Sequential
ele é herdado de Module
, e a descrição Sequential
em si também é uma Module
, portanto, também terá esses parâmetros de dicionário. Você pode ver nn.Sequential
que implementou forward
o método. nn.Sequential
Os métodos mais comumente usados são os seguintes:
forward(input)
: Define o processo de propagação para frente do modelo. Emnn.Sequential
, esse método chamaforward
o método de cada módulo sequencialmente na ordem dos módulos, passando a saída do módulo anterior como entrada para o próximo módulo para calcular a saída final.add_module(name, module)
:nn.Sequential
Adicione um submódulo ao arquivo .name
é o nome do submódulo emodule
é o objeto do submódulo a ser adicionado. Os módulos serão propagados para frente sequencialmente na ordem em que foram adicionados.parameters()
: Retornann.Sequential
um iterador sobre todos os parâmetros que podem ser aprendidos em . Os parâmetros que podem ser aprendidos do modelo podem ser acessados e manipulados por meio de iteradores.zero_grad()
:nn.Sequential
Defina os gradientes de parâmetro de todos os módulos para zero. Este método geralmente é chamado antes de cada atualização de gradiente
add_module(name, module)
Apenas liste os métodos acima, que estão realmente corretos.Este método geralmente é mais usado para adicionar módulos. Vamos ver nn.Sequential
como usá-lo?
class Net(nn.Module):
def __init__(self, classes):
super(Net, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),)
self.classifier = nn.Sequential(
nn.Linear(16*5*5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, classes),)
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
Também pode ser criado usando o método add_module:
import torch
import torch.nn as nn
class Net(nn.Module):
def __init__(self, classes):
super(Net, self).__init__()
self.features = nn.Sequential()
self.features.add_module('conv1', nn.Conv2d(3, 6, 5))
self.features.add_module('relu1', nn.ReLU())
self.features.add_module('pool1', nn.MaxPool2d(kernel_size=2, stride=2))
self.features.add_module('conv2', nn.Conv2d(6, 16, 5))
self.features.add_module('relu2', nn.ReLU())
self.features.add_module('pool2', nn.MaxPool2d(kernel_size=2, stride=2))
self.classifier = nn.Sequential()
self.classifier.add_module('fc1', nn.Linear(16*5*5, 120))
self.classifier.add_module('relu3', nn.ReLU())
self.classifier.add_module('fc2', nn.Linear(120, 84))
self.classifier.add_module('relu4', nn.ReLU())
self.classifier.add_module('fc3', nn.Linear(84, classes))
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
Através da construção da rede acima, podemos ver que forward
apenas uma sentença é utilizada na função self.features(x)
para completar a execução de seis sentenças. A razão pela qual ele pode concluir esta operação é devido às funções nn.Sequential
do forward
programa. Durante a execução do programa, os parâmetros serão passados a ele nn.Sequential
para análise. O processo específico de implementação pode ser depurado e observado.
Resumo:
nn.Sequential
É nn.module
um contêiner, usado para empacotar um conjunto de camadas de rede em ordem, e possui as duas características a seguir:
- Sequencialidade: Cada camada de rede é construída estritamente em ordem. Neste momento, devemos prestar atenção ao relacionamento entre os dados das camadas frontal e traseira
- Self-contained
forward()
:forward
No self-contained,for
a operação de propagação direta é executada sequencialmente através do loop
3. nn.ModuleList
nn.ModuleList
É também nn.module
um contêiner, que é usado para agrupar um grupo de camadas de rede e chamar a camada de rede iterativamente . Os métodos comumente usados são os seguintes, que são muito semelhantes ao uso de lista:
append()
:ModuleList
adicionar camada de rede depoisextend()
: unindo doisModuleList
insert()
: EspecificaModuleList
para inserir a camada de rede na posição intermediária
Basta olhar para um exemplo, como usar nn.ModuleList
para construir uma rede:
class ModuleListNet(nn.Module):
def __init__(self):
super(ModuleListNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
def forward(self, x):
for i, linear in enumerate(self.linears):
x = linear(x)
return x
O exemplo acima cria 10 nn.Linear
módulos usando compreensão de lista. No geral, ainda é muito simples de usar, e amigos interessados no processo de implementação específico podem visualizá-lo depurando o código.
3. nn.ModuleDict
Vamos olhar para nn.ModuleDict
este módulo novamente. nn.ModuleDict
É também nn.module
um contêiner, que é usado para empacotar um conjunto de camadas de rede e chamar a camada de rede por índice . Os métodos comumente usados são os seguintes, que são semelhantes à operação de dicionários:
clear()
: vazioModuleDict
items()
: retorna um iterável de pares chave-valor (key-value pairs
)keys()
: retorna a chave do dicionário (key
)values()
: retorna o valor do dicionário (value)
pop()
: retorna um par chave-valor e exclui-o do dicionário
Veja um exemplo:
import torch
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.module_dict = nn.ModuleDict({
'conv1': nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
'relu1': nn.ReLU(),
'conv2': nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
'relu2': nn.ReLU(),
'flatten': nn.Flatten(),
'linear': nn.Linear(128 * 32 * 32, 10)
})
def forward(self, x):
for module in self.module_dict.values():
x = module(x)
return x
# 创建模型实例
model = MyModel()
# 随机生成输入
x = torch.randn(1, 3, 32, 32)
# 进行前向传播
output = model(x)
print(output.shape)
Ao nn.ModuleDict
criar uma rede acima, ainda é muito simples no geral, semelhante às operações do dicionário.
O uso básico de , é basicamente a introdução do vinho, se houver algum erro, por favor nn.Sequential
me corrija nn.ModuleList
!nn.ModuleDict