Introdução ao nn.Sequential, nn.ModuleList, nn.ModuleDict do contêiner pytorch

prefácio

  Ao criar um modelo de aprendizado profundo, geralmente encontramos nn.Sequentialessas nn.ModuleListtrês nn.ModuleDictcoisas, 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.Parametere 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. PytorchGeralmente, os parâmetros são nn.Parameterusados ​​para representar e nn.Modulegerenciar 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.Modulea 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.Moduleconvertido em um formulário de implementação de classe por meio de herança e encapsulado diretamente no nnmó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)

nnPortanto , as funções de ativação, camadas e funções de perda   que estabelecemos na superfície são todas functionalimplementadas nos bastidores. Se você continuar olhando para baixo, saberá que nn.Moduleeste 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.Modulemódulo. nn.ModuleExistem 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: _parameterse_modules

  • _parameters: Armazena e gerencia nn.Parameteratributos pertencentes à classe, como pesos, polarizando esses parâmetros
  • _modules: A classe de gerenciamento de armazenamento nn.Module, como LeNetna 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.Parametere nn.Moduleem _parameters?

  • nn.Parameter: é torch.Tensoruma 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 usamos nn.Parameterpara criar parâmetros que podem ser aprendidos como atributos do modelo. A vantagem disso é que nn.Parametero 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.Moduleum 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.Parametertipos) dos parâmetros correspondentes. _parametersO valor do atributo extrai automaticamente os parâmetros apreensíveis dos atributos do modelo e os adiciona ao dicionário.

  pode ser _parameterspensado 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.Parametercriar parâmetros e usá-los como atributos de modelo, esses parâmetros serão adicionados automaticamente ao _parametersdicioná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.ParameterOs parâmetros criados com são adicionados automaticamente _parametersao dicionário para facilitar o gerenciamento e o acesso aos parâmetros do modelo.

nn.ModuleComo é 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 acima Net) herdando nn.Moduleessa classe base, como a acima Net, e então Netpode haver muitos submódulos nela, e esses submódulos também são herdados. nn.ModuleNesses Modulemé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étodo valueé 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 a Netconstrução é finalmente concluída. Você pode depurar o processo específico sozinho.

Resumir:

  • Um modulepode conter várias subclasses module( Netincluindo camadas convolucionais, BNcamadas, funções de ativação)
  • Um moduleé equivalente a uma operação e forward()a função deve ser implementada (o encaminhamento de alguns módulos precisa ser reescrito por você, você saberá quando ler)
  • Cada um moduletem 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.Moduleos módulos acima, vamos começar a apresentar o contêiner. Em primeiro lugar, vamos dar uma olhada nele nn.Sequential, nn.Sequentialque é PyTorchum 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.Sequentialele é herdado de Module, e a descrição Sequentialem si também é uma Module, portanto, também terá esses parâmetros de dicionário. Você pode ver nn.Sequentialque implementou forwardo método. nn.SequentialOs métodos mais comumente usados ​​são os seguintes:

  • forward(input): Define o processo de propagação para frente do modelo. Em nn.Sequential, esse método chama forwardo 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.SequentialAdicione um submódulo ao arquivo . nameé o nome do submódulo e moduleé o objeto do submódulo a ser adicionado. Os módulos serão propagados para frente sequencialmente na ordem em que foram adicionados.
  • parameters(): Retorna nn.Sequentialum 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.SequentialDefina 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.Sequentialcomo 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 forwardapenas 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.Sequentialdo forwardprograma. Durante a execução do programa, os parâmetros serão passados ​​a ele nn.Sequentialpara análise. O processo específico de implementação pode ser depurado e observado.
Resumo:
nn.SequentialÉ nn.moduleum 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(): forwardNo self-contained, fora operação de propagação direta é executada sequencialmente através do loop

3. nn.ModuleList

  nn.ModuleListÉ também nn.moduleum 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(): ModuleListadicionar camada de rede depois
  • extend(): unindo doisModuleList
  • insert(): Especifica ModuleListpara inserir a camada de rede na posição intermediária

Basta olhar para um exemplo, como usar nn.ModuleListpara 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.Linearmó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.ModuleDicteste módulo novamente. nn.ModuleDictÉ também nn.moduleum 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.ModuleDictcriar 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.Sequentialme corrija nn.ModuleList!nn.ModuleDict

Acho que você gosta

Origin blog.csdn.net/qq_38683460/article/details/131107291
Recomendado
Clasificación