¿Cómo inicializar los pesos y los sesgos (por ejemplo, con la inicialización de He o Xavier) en una red en PyTorch?
¿Cómo inicializar los pesos y los sesgos (por ejemplo, con la inicialización de He o Xavier) en una red en PyTorch?
Respuestas:
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.Module
recursiva.
apply ( fn ): se aplica de forma
fn
recursiva 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)
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)
Validation Accuracy
9.625% -- All Zeros
10.050% -- All Ones
Training Loss
2.304 -- All Zeros
1552.281 -- All Ones
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.0
y 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:
- Defina una función que asigne pesos por el tipo de capa de red, luego
- 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)
Validation Accuracy
36.667% -- Uniform Weights
Training Loss
3.208 -- Uniform Weights
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
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)
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
Validation Accuracy
85.775% -- Uniform Rule [-y, y)
84.717% -- Normal Distribution
Training Loss
0.329 -- Uniform Rule [-y, y)
0.443 -- Normal Distribution
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 conv2d
ejemplo, 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.
xavier_uniform
inicializació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. (
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)
Perdón por llegar tan tarde, espero que mi respuesta ayude.
Para inicializar pesos con un normal distribution
uso:
torch.nn.init.normal_(tensor, mean=0, std=1)
O para usar una constant distribution
escritura:
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í
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.
Si no puede usar, apply
por ejemplo, si el modelo no se implementa Sequential
directamente:
# 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.)
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))
}
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)
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.
reset_parameters
método en el código fuente de muchos módulos. ¿Debo anular el método para la inicialización del peso?