¿Cómo inicializar pesos en PyTorch?

Respuestas:

150

Una sola capa

Para inicializar los pesos de una sola capa, use una función de torch.nn.init. Por ejemplo:

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

Alternativamente, puede modificar los parámetros escribiendo en conv1.weight.data(que es a torch.Tensor). Ejemplo:

conv1.weight.data.fill_(0.01)

Lo mismo se aplica a los sesgos:

conv1.bias.data.fill_(0.01)

nn.Sequential o personalizado nn.Module

Pasar una función de inicialización a torch.nn.Module.apply. Inicializará los pesos en todo de forma nn.Modulerecursiva.

apply ( fn ): se aplica de forma fnrecursiva a cada submódulo (como lo devuelve .children()), así como a self. El uso típico incluye inicializar los parámetros de un modelo (ver también torch-nn-init).

Ejemplo:

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 Pérez
fuente
6
Encontré un reset_parametersmétodo en el código fuente de muchos módulos. ¿Debo anular el método para la inicialización del peso?
Yang Bo
1
¿Qué pasa si quiero usar una distribución Normal con alguna media y estándar?
Charlie Parker
12
¿Cuál es la inicialización predeterminada si no especifico una?
xjcl
La inicialización predeterminada al menos para capas lineales es ella: pytorch.org/docs/stable/nn.html#linear-layers
arash javan
40

Comparamos diferentes modos de inicialización de peso utilizando la misma arquitectura de red neuronal (NN).

Todos ceros o unos

Si sigue el principio de la navaja de Occam , podría pensar que establecer todos los pesos en 0 o 1 sería la mejor solución. Este no es el caso.

Con todos los pesos iguales, todas las neuronas de cada capa producen la misma salida. Esto hace que sea difícil decidir qué pesos ajustar.

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

gráfico de pérdida de entrenamiento con inicialización de peso a constante

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

Inicialización uniforme

Una distribución uniforme tiene la misma probabilidad de elegir cualquier número de un conjunto de números.

Veamos qué tan bien se entrena la red neuronal usando una inicialización de peso uniforme, dónde low=0.0y high=1.0.

A continuación, veremos otra forma (además del código de la clase Net) para inicializar los pesos de una red. Para definir pesos fuera de la definición del modelo, podemos:

  1. Defina una función que asigne pesos por el tipo de capa de red, luego
  2. Aplique esos pesos a un modelo inicializado usando model.apply(fn), que aplica una función a cada capa del 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)
  • Después de 2 épocas:

ingrese la descripción de la imagen aquí

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

Regla general para configurar pesos

La regla general para establecer los pesos en una red neuronal es establecerlos cerca de cero sin que sean demasiado pequeños.

Una buena práctica es comenzar sus pesos en el rango de [-y, y] donde y=1/sqrt(n)
(n es el número de entradas a una neurona dada).

    # 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)

a continuación, comparamos el rendimiento de NN, pesos inicializados con distribución uniforme [-0.5,0.5) frente a aquel cuyo peso se inicializa usando la regla general

  • Después de 2 épocas:

Gráfico que muestra el rendimiento de la inicialización uniforme del peso frente a la regla general de inicialización

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)

distribución normal para inicializar los pesos

La distribución normal debe tener una media de 0 y una desviación estándar de y=1/sqrt(n), donde n es el número de entradas a 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)

A continuación mostramos el desempeño de dos NN, uno inicializado usando distribución uniforme y el otro usando distribución normal

  • Después de 2 épocas:

Rendimiento de la inicialización del peso utilizando una distribución uniforme frente a la distribución 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
fuente
7
¿Cuál es la tarea para la que optimizas? ¿Y cómo puede una solución de todos ceros dar cero pérdidas?
dedObed
19

Para inicializar capas, normalmente no es necesario hacer nada.

PyTorch lo hará por ti. Si lo piensas, esto tiene mucho sentido. ¿Por qué deberíamos inicializar capas, cuando PyTorch puede hacerlo siguiendo las últimas tendencias?

Compruebe, por ejemplo, la capa Lineal .

En el __init__método llamará a la función 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)

Lo mismo ocurre con otros tipos de capas. Por conv2dejemplo, consulte aquí .

Nota: La ganancia de una inicialización adecuada es la velocidad de entrenamiento más rápida. Si su problema merece una inicialización especial, puede hacerlo posteriormente.

prosti
fuente
Sin embargo, la inicialización predeterminada no siempre da los mejores resultados. Recientemente implementé la arquitectura VGG16 en Pytorch y la entrené en el conjunto de datos CIFAR-10, y descubrí que simplemente cambiando a xavier_uniforminicialización para los pesos (con sesgos inicializados a 0), en lugar de usar la inicialización predeterminada, mi precisión de validación después de 30 las épocas de RMSprop aumentaron del 82% al 86%. También obtuve una precisión de validación del 86% al usar el modelo VGG16 incorporado de Pytorch (no entrenado previamente), así que creo que lo implementé correctamente. (
Usé
Esto se debe a que no han usado Batch Norms en VGG16. Es cierto que una correcta inicialización es importante y que para algunas arquitecturas debes prestar atención. Por ejemplo, si usa (nn.conv2d (), secuencia ReLU ()), iniciará la inicialización de Kaiming He diseñada para relu su capa de conv. PyTorch no puede predecir su función de activación después de conv2d. Esto tiene sentido si evalúa los valores eigne, pero normalmente no tiene que hacer mucho si usa Batch Norms, ellos normalizarán los resultados por usted. Si planea ganar la competencia de SotaBench, es importante.
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
fuente
5

Perdón por llegar tan tarde, espero que mi respuesta ayude.

Para inicializar pesos con un normal distributionuso:

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

O para usar una constant distributionescritura:

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

O para usar un uniform distribution:

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

Puede consultar otros métodos para inicializar tensores aquí

Luca Di Liello
fuente
2

Si desea un poco de flexibilidad adicional, también puede configurar los pesos manualmente .

Digamos que tiene información 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.]])

Y desea hacer una capa densa sin sesgo (para que podamos visualizar):

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

Establezca todos los pesos en 0.5 (o cualquier otra cosa):

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

Los 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 sus pesos ahora son 0.5. Pasar los datos a través de:

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>)

Recuerde que cada neurona recibe 8 entradas, todas las cuales tienen un peso de 0.5 y un valor de 1 (y sin sesgo), por lo que suma 4 para cada una.

Nicolás Gervais
fuente
1

Iterar sobre los parámetros

Si no puede usar, applypor ejemplo, si el modelo no se implementa Sequentialdirectamente:

Igual 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.) 

Dependiendo de la 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)

Puede probar con torch.nn.init.constant_(x, len(x.shape))para comprobar que estén correctamente inicializados:

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

Si ve una advertencia de desaprobación (@ 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
fuente
1
Puede comentar allí en la respuesta de Fábio Pérez para mantener las respuestas limpias.
Phani Rithvij
0

Porque no he tenido suficiente reputación hasta ahora, no puedo agregar un comentario debajo

la respuesta Publicado por prosti en 26 '19 Jun a las 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)

Pero quiero señalar que en realidad conocemos algunas suposiciones en el artículo de Kaiming He , Profundizando en los rectificadores: superando el desempeño a nivel humano en la clasificación de ImageNet. , no son apropiadas, aunque parece que el método de inicialización diseñado deliberadamente es un éxito en la práctica. .

Por ejemplo, dentro de la subsección de Caso de propagación hacia atrás , asumen que $ w_l $ y $ \ delta y_l $ son independientes entre sí. Pero como todos sabemos, tomemos el mapa de puntuación $ \ delta y ^ L_i $ como ejemplo, a menudo es $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $ si usamos un objetivo de función de pérdida de entropía cruzada.

Así que creo que la verdadera razón subyacente por la que la inicialización de He funciona bien sigue sin resolverse. Porque todos han sido testigos de su poder para impulsar el entrenamiento de aprendizaje profundo.

Gloria Chen
fuente