Comment initialiser les poids et biais (par exemple, avec l'initialisation He ou Xavier) dans un réseau dans PyTorch?
Comment initialiser les poids et biais (par exemple, avec l'initialisation He ou Xavier) dans un réseau dans PyTorch?
Réponses:
Pour initialiser les poids d'une seule couche, utilisez une fonction de torch.nn.init
. Par exemple:
conv1 = torch.nn.Conv2d(...)
torch.nn.init.xavier_uniform(conv1.weight)
Vous pouvez également modifier les paramètres en écrivant dans conv1.weight.data
(qui est a torch.Tensor
). Exemple:
conv1.weight.data.fill_(0.01)
Il en va de même pour les biais:
conv1.bias.data.fill_(0.01)
nn.Sequential
ou personnalisé nn.Module
Passez une fonction d'initialisation à torch.nn.Module.apply
. Il initialisera les poids dans l'ensemble de nn.Module
manière récursive.
apply ( fn ): S'applique de
fn
manière récursive à chaque sous-module (tel que renvoyé par.children()
) ainsi qu'à self. L'utilisation typique comprend l'initialisation des paramètres d'un modèle (voir aussi torch-nn-init).
Exemple:
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 vous suivez le principe du rasoir d'Occam , vous pourriez penser que régler tous les poids à 0 ou 1 serait la meilleure solution. Ce n'est pas le cas.
Avec chaque poids identique, tous les neurones de chaque couche produisent la même sortie. Cela rend difficile le choix des poids à ajuster.
# 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
Une distribution uniforme a la même probabilité de choisir n'importe quel nombre parmi un ensemble de nombres.
Voyons à quel point le réseau de neurones s'entraîne en utilisant une initialisation de poids uniforme, où low=0.0
et high=1.0
.
Ci-dessous, nous verrons une autre façon (en plus du code de classe Net) d'initialiser les poids d'un réseau. Pour définir des poids en dehors de la définition du modèle, nous pouvons:
- Définissez une fonction qui attribue des pondérations par type de couche réseau, puis
- Appliquez ces pondérations à un modèle initialisé à l'aide de
model.apply(fn)
, qui applique une fonction à chaque couche de modèle.
# 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 règle générale pour définir les poids dans un réseau de neurones est de les définir pour qu'ils soient proches de zéro sans être trop petits.
Une bonne pratique consiste à commencer vos poids dans la plage de [-y, y] où
y=1/sqrt(n)
(n est le nombre d'entrées d'un neurone donné).
# 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)
ci-dessous nous comparons les performances de NN, les poids initialisés avec une distribution uniforme [-0,5,0,5) par rapport à celui dont le poids est initialisé en utilisant la règle générale
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 distribution normale doit avoir une moyenne de 0 et un écart type de
y=1/sqrt(n)
, où n est le nombre d'entrées de 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)
ci-dessous, nous montrons les performances de deux NN, l'un initialisé avec une distribution uniforme et l'autre avec une distribution normale
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 le fera pour vous. Si vous y réfléchissez, cela a beaucoup de sens. Pourquoi devrions-nous initialiser les couches, alors que PyTorch peut le faire en suivant les dernières tendances.
Vérifiez par exemple le calque linéaire .
Dans la __init__
méthode, il appellera la fonction d'initialisation 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)
La même chose est pour les autres types de couches. Par conv2d
exemple, vérifiez ici .
A noter: le gain d'une bonne initialisation est la vitesse d'entraînement plus rapide. Si votre problème mérite une initialisation spéciale, vous pouvez le faire après.
xavier_uniform
initialisation pour les poids (avec des biais initialisés à 0), plutôt que d'utiliser l'initialisation par défaut, ma précision de validation après 30 les époques de RMSprop sont passées de 82% à 86%. J'ai également obtenu une précision de validation de 86% lors de l'utilisation du modèle VGG16 intégré de Pytorch (non pré-formé), donc je pense que je l'ai implémenté correctement. (J'ai utilisé un taux d'apprentissage de 0,00001.)
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)
Désolé d'être si tard, j'espère que ma réponse vous aidera.
Pour initialiser les poids avec une normal distribution
utilisation:
torch.nn.init.normal_(tensor, mean=0, std=1)
Ou pour utiliser une constant distribution
écriture:
torch.nn.init.constant_(tensor, value)
Ou pour utiliser un uniform distribution
:
torch.nn.init.uniform_(tensor, a=0, b=1) # a: lower_bound, b: upper_bound
Vous pouvez vérifier d'autres méthodes pour initialiser les tenseurs ici
Si vous souhaitez une flexibilité supplémentaire, vous pouvez également définir les poids manuellement .
Disons que vous avez une entrée pour tous:
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.]])
Et vous voulez créer une couche dense sans biais (afin que nous puissions visualiser):
d = nn.Linear(8, 8, bias=False)
Définissez tous les poids sur 0,5 (ou autre chose):
d.weight.data = torch.full((8, 8), 0.5)
print(d.weight.data)
Les poids:
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]])
Tous vos poids sont maintenant de 0,5. Transmettez les données via:
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>)
Rappelez-vous que chaque neurone reçoit 8 entrées, qui ont toutes un poids de 0,5 et une valeur de 1 (et aucun biais), donc cela fait jusqu'à 4 pour chacune.
Si vous ne pouvez pas utiliser apply
par exemple si le modèle n'implémente pas Sequential
directement:
# 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)
Vous pouvez essayer avec torch.nn.init.constant_(x, len(x.shape))
pour vérifier qu'ils sont correctement initialisés:
init_funcs = {
"default": lambda x: torch.nn.init.constant_(x, len(x.shape))
}
Si vous voyez un avertissement d'obsolescence (@ 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)
Parce que je n'ai pas eu assez de réputation jusqu'à présent, je ne peux pas ajouter de commentaire sous
la réponse publiée par prosti en juin 26 '19 à 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)
Mais je tiens à souligner qu'en fait, nous connaissons certaines hypothèses dans l'article de Kaiming He , Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification , ne sont pas appropriées, bien qu'il semble que la méthode d'initialisation délibérément conçue fasse un succès dans la pratique .
Par exemple, dans la sous-section Cas de propagation vers l'arrière , ils supposent que $ w_l $ et $ \ delta y_l $ sont indépendants l'un de l'autre. Mais comme nous le savons tous, prenons la carte des scores $ \ delta y ^ L_i $ comme instance, c'est souvent $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $ si nous utilisons un Objectif de la fonction de perte d'entropie croisée.
Je pense donc que la véritable raison sous-jacente pour laquelle He Initialization fonctionne bien reste à démêler. Parce que tout le monde a été témoin de son pouvoir de stimuler la formation en deep learning.
reset_parameters
méthode dans le code source de nombreux modules. Dois-je remplacer la méthode d'initialisation du poids?