Como inicializar pesos no PyTorch?

106

Como inicializar os pesos e vieses (por exemplo, com inicialização de He ou Xavier) em uma rede em PyTorch?

Fábio Perez
fonte

Respostas:

150

Camada única

Para inicializar os pesos de uma única camada, use uma função de torch.nn.init. Por exemplo:

conv1 = torch.nn.Conv2d(...)
torch.nn.init.xavier_uniform(conv1.weight)

Como alternativa, você pode modificar os parâmetros gravando em conv1.weight.data(que é a torch.Tensor). Exemplo:

conv1.weight.data.fill_(0.01)

O mesmo se aplica a vieses:

conv1.bias.data.fill_(0.01)

nn.Sequential ou costume nn.Module

Passe uma função de inicialização para torch.nn.Module.apply. Ele irá inicializar os pesos em todo nn.Modulerecursivamente.

aplicar ( fn ): Aplica - se fnrecursivamente a cada submódulo (conforme retornado por .children()), bem como a si mesmo. O uso típico inclui a inicialização dos parâmetros de um modelo (consulte também torch-nn-init).

Exemplo:

def init_weights(m):
    if type(m) == nn.Linear:
        torch.nn.init.xavier_uniform(m.weight)
        m.bias.data.fill_(0.01)

net = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2))
net.apply(init_weights)
Fábio Perez
fonte
6
Encontrei um reset_parametersmétodo no código-fonte de muitos módulos. Devo substituir o método de inicialização de peso?
Yang Bo
1
e se eu quiser usar uma distribuição normal com alguma média e padrão?
Charlie Parker
12
Qual é a inicialização padrão se eu não especificar uma?
xjcl
a inicialização padrão pelo menos para camadas lineares é ela: pytorch.org/docs/stable/nn.html#linear-layers
arash javan
40

Comparamos diferentes modos de inicialização de peso usando a mesma arquitetura de rede neural (NN).

Todos os zeros ou uns

Se você seguir o princípio da navalha de Occam , pode pensar que definir todos os pesos como 0 ou 1 seria a melhor solução. Este não é o caso.

Com todo peso igual, todos os neurônios em cada camada estão produzindo a mesma saída. Isso torna difícil decidir quais pesos ajustar.

    # initialize two NN's with 0 and 1 constant weights
    model_0 = Net(constant_weight=0)
    model_1 = Net(constant_weight=1)
  • Após 2 épocas:

gráfico de perda de treinamento com inicialização de peso para constante

Validation Accuracy
9.625% -- All Zeros
10.050% -- All Ones
Training Loss
2.304  -- All Zeros
1552.281  -- All Ones

Inicialização Uniforme

Uma distribuição uniforme tem a mesma probabilidade de escolher qualquer número de um conjunto de números.

Vamos ver o quão bem a rede neural treina usando uma inicialização de peso uniforme, onde low=0.0e high=1.0.

A seguir, veremos outra maneira (além do código da classe Net) para inicializar os pesos de uma rede. Para definir pesos fora da definição do modelo, podemos:

  1. Defina uma função que atribua pesos pelo tipo de camada de rede, então
  2. Aplique essas ponderações a um modelo inicializado usando model.apply(fn), que aplica uma função a cada camada do modelo.
    # takes in a module and applies the specified weight initialization
    def weights_init_uniform(m):
        classname = m.__class__.__name__
        # for every Linear layer in a model..
        if classname.find('Linear') != -1:
            # apply a uniform distribution to the weights and a bias=0
            m.weight.data.uniform_(0.0, 1.0)
            m.bias.data.fill_(0)

    model_uniform = Net()
    model_uniform.apply(weights_init_uniform)
  • Após 2 épocas:

insira a descrição da imagem aqui

Validation Accuracy
36.667% -- Uniform Weights
Training Loss
3.208  -- Uniform Weights

Regra geral para definir pesos

A regra geral para definir os pesos em uma rede neural é defini-los como próximos a zero sem serem muito pequenos.

A boa prática é iniciar seus pesos no intervalo de [-y, y], onde y=1/sqrt(n)
(n é o número de entradas para um determinado neurônio).

    # takes in a module and applies the specified weight initialization
    def weights_init_uniform_rule(m):
        classname = m.__class__.__name__
        # for every Linear layer in a model..
        if classname.find('Linear') != -1:
            # get the number of the inputs
            n = m.in_features
            y = 1.0/np.sqrt(n)
            m.weight.data.uniform_(-y, y)
            m.bias.data.fill_(0)

    # create a new model with these weights
    model_rule = Net()
    model_rule.apply(weights_init_uniform_rule)

abaixo, comparamos o desempenho de NN, pesos inicializados com distribuição uniforme [-0,5,0,5) versus aquele cujo peso é inicializado usando a regra geral

  • Após 2 épocas:

gráfico mostrando o desempenho de inicialização uniforme de peso versus regra geral de inicialização

Validation Accuracy
75.817% -- Centered Weights [-0.5, 0.5)
85.208% -- General Rule [-y, y)
Training Loss
0.705  -- Centered Weights [-0.5, 0.5)
0.469  -- General Rule [-y, y)

distribuição normal para inicializar os pesos

A distribuição normal deve ter uma média de 0 e um desvio padrão de y=1/sqrt(n), onde n é o número de entradas para NN

    ## takes in a module and applies the specified weight initialization
    def weights_init_normal(m):
        '''Takes in a module and initializes all linear layers with weight
           values taken from a normal distribution.'''

        classname = m.__class__.__name__
        # for every Linear layer in a model
        if classname.find('Linear') != -1:
            y = m.in_features
        # m.weight.data shoud be taken from a normal distribution
            m.weight.data.normal_(0.0,1/np.sqrt(y))
        # m.bias.data should be 0
            m.bias.data.fill_(0)

abaixo, mostramos o desempenho de dois NN, um inicializado usando distribuição uniforme e o outro usando distribuição normal

  • Após 2 épocas:

desempenho de inicialização de peso usando distribuição uniforme versus distribuição normal

Validation Accuracy
85.775% -- Uniform Rule [-y, y)
84.717% -- Normal Distribution
Training Loss
0.329  -- Uniform Rule [-y, y)
0.443  -- Normal Distribution
Ashunigion
fonte
7
Qual é a tarefa que você otimiza? E como pode uma solução totalmente zeros dar perda zero?
dedobed
19

Para inicializar camadas, você normalmente não precisa fazer nada.

PyTorch fará isso por você. Se você pensar bem, isso faz muito sentido. Por que devemos inicializar camadas, quando PyTorch pode fazer isso seguindo as últimas tendências.

Verifique, por exemplo, a camada Linear .

No __init__método, ele chamará a função init de Kaiming He .

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight, a=math.sqrt(3))
        if self.bias is not None:
            fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
            bound = 1 / math.sqrt(fan_in)
            init.uniform_(self.bias, -bound, bound)

O semelhante é para outros tipos de camadas. Por conv2dexemplo, verifique aqui .

Observação: o ganho da inicialização adequada é a velocidade de treinamento mais rápida. Se o seu problema merece inicialização especial, você pode fazê-lo posteriormente.

Prosti
fonte
A inicialização padrão nem sempre dá os melhores resultados, no entanto. Recentemente, implementei a arquitetura VGG16 no Pytorch e a treinei no conjunto de dados CIFAR-10, e descobri que apenas alternando para a xavier_uniforminicialização para os pesos (com vieses inicializados em 0), em vez de usar a inicialização padrão, minha precisão de validação após 30 épocas de RMSprop aumentaram de 82% para 86%. Também obtive 86% de precisão de validação ao usar o modelo VGG16 integrado do Pytorch (não pré-treinado), então acho que o implementei corretamente. (Eu usei uma taxa de aprendizado de 0,00001.)
pequeno O
Isso ocorre porque eles não usaram as normas de lote no VGG16. É verdade que a inicialização adequada é importante e que para algumas arquiteturas você deve prestar atenção. Por exemplo, se você usar (nn.conv2d (), sequência ReLU ()), você iniciará a inicialização do Kaiming He projetada para relu sua camada conv. PyTorch não pode prever sua função de ativação após o conv2d. Isso faz sentido se você avaliar os eignevalues, mas normalmente você não precisa fazer muito se usar as normas em lote; elas normalizarão as saídas para você. Se você planeja vencer a competição SotaBench, isso importa.
prosti
7
    import torch.nn as nn        

    # a simple network
    rand_net = nn.Sequential(nn.Linear(in_features, h_size),
                             nn.BatchNorm1d(h_size),
                             nn.ReLU(),
                             nn.Linear(h_size, h_size),
                             nn.BatchNorm1d(h_size),
                             nn.ReLU(),
                             nn.Linear(h_size, 1),
                             nn.ReLU())

    # initialization function, first checks the module type,
    # then applies the desired changes to the weights
    def init_normal(m):
        if type(m) == nn.Linear:
            nn.init.uniform_(m.weight)

    # use the modules apply function to recursively apply the initialization
    rand_net.apply(init_normal)
Duane
fonte
5

Desculpe pelo atraso, espero que minha resposta ajude.

Para inicializar pesos com um normal distributionuso:

torch.nn.init.normal_(tensor, mean=0, std=1)

Ou para usar uma constant distributionescrita:

torch.nn.init.constant_(tensor, value)

Ou para usar um uniform distribution:

torch.nn.init.uniform_(tensor, a=0, b=1) # a: lower_bound, b: upper_bound

Você pode verificar outros métodos para inicializar tensores aqui

Luca Di Liello
fonte
2

Se você quiser alguma flexibilidade extra, também pode definir os pesos manualmente .

Digamos que você tenha a opinião de todos:

import torch
import torch.nn as nn

input = torch.ones((8, 8))
print(input)
tensor([[1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1.]])

E você quer fazer uma camada densa sem viés (para que possamos visualizar):

d = nn.Linear(8, 8, bias=False)

Defina todos os pesos para 0,5 (ou qualquer outra coisa):

d.weight.data = torch.full((8, 8), 0.5)
print(d.weight.data)

Os pesos:

Out[14]: 
tensor([[0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
        [0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000]])

Todos os seus pesos agora são 0,5. Passe os dados por:

d(input)
Out[13]: 
tensor([[4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.],
        [4., 4., 4., 4., 4., 4., 4., 4.]], grad_fn=<MmBackward>)

Lembre-se de que cada neurônio recebe 8 entradas, todas com peso 0,5 e valor 1 (e sem viés), portanto, soma 4 para cada.

Nicolas Gervais
fonte
1

Iterar sobre os parâmetros

Se você não puder usar, applypor exemplo, se o modelo não implementar Sequentialdiretamente:

O mesmo para todos

# see UNet at https://github.com/milesial/Pytorch-UNet/tree/master/unet


def init_all(model, init_func, *params, **kwargs):
    for p in model.parameters():
        init_func(p, *params, **kwargs)

model = UNet(3, 10)
init_all(model, torch.nn.init.normal_, mean=0., std=1) 
# or
init_all(model, torch.nn.init.constant_, 1.) 

Dependendo da forma

def init_all(model, init_funcs):
    for p in model.parameters():
        init_func = init_funcs.get(len(p.shape), init_funcs["default"])
        init_func(p)

model = UNet(3, 10)
init_funcs = {
    1: lambda x: torch.nn.init.normal_(x, mean=0., std=1.), # can be bias
    2: lambda x: torch.nn.init.xavier_normal_(x, gain=1.), # can be weight
    3: lambda x: torch.nn.init.xavier_uniform_(x, gain=1.), # can be conv1D filter
    4: lambda x: torch.nn.init.xavier_uniform_(x, gain=1.), # can be conv2D filter
    "default": lambda x: torch.nn.init.constant(x, 1.), # everything else
}

init_all(model, init_funcs)

Você pode tentar torch.nn.init.constant_(x, len(x.shape))verificar se eles foram inicializados corretamente:

init_funcs = {
    "default": lambda x: torch.nn.init.constant_(x, len(x.shape))
}
ted
fonte
0

Se você vir um aviso de suspensão de uso (@ Fábio Perez) ...

def init_weights(m):
    if type(m) == nn.Linear:
        torch.nn.init.xavier_uniform_(m.weight)
        m.bias.data.fill_(0.01)

net = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2))
net.apply(init_weights)
Joseph Konan
fonte
1
Você pode comentar aí na resposta do Fábio Perez para manter as respostas limpas.
Phani Rithvij
0

Porque eu não tive reputação suficiente até agora, não posso adicionar um comentário em

a resposta postada por prosti em 26 de junho de 1919 às 13:16 .

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight, a=math.sqrt(3))
        if self.bias is not None:
            fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
            bound = 1 / math.sqrt(fan_in)
            init.uniform_(self.bias, -bound, bound)

Mas gostaria de salientar que na verdade sabemos que algumas suposições no artigo de Kaiming He , Investigando profundamente os retificadores: superando o desempenho de nível humano na classificação ImageNet , não são adequadas, embora pareça que o método de inicialização deliberadamente projetado faz sucesso na prática .

Por exemplo, dentro da subseção de Caso de propagação para trás , eles assumem que $ w_l $ e $ \ delta y_l $ são independentes um do outro. Mas como todos nós sabemos, tome o mapa de pontuação $ \ delta y ^ L_i $ como uma instância, muitas vezes é $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $ se usarmos um típico objetivo da função de perda de entropia cruzada.

Portanto, acho que a verdadeira razão subjacente pela qual a inicialização de Ele funciona bem ainda está para ser desvendada. Porque todo mundo testemunhou seu poder em impulsionar o treinamento de aprendizagem profunda.

Glory Chen
fonte