Comment initialiser les poids dans PyTorch?

Réponses:

150

Une seule couche

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.Modulemanière récursive.

apply ( fn ): S'applique de fnmaniè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)
Fábio Perez
la source
6
J'ai trouvé une reset_parametersméthode dans le code source de nombreux modules. Dois-je remplacer la méthode d'initialisation du poids?
Yang Bo
1
et si je veux utiliser une distribution normale avec une moyenne et une std?
Charlie Parker
12
Quelle est l'initialisation par défaut si je n'en spécifie pas?
xjcl
l'initialisation par défaut au moins pour les couches linéaires est la suivante: pytorch.org/docs/stable/nn.html#linear-layers
arash javan
40

Nous comparons différents modes d'initialisation de poids en utilisant la même architecture de réseau neuronal (NN).

Tous les zéros ou les uns

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)
  • Après 2 époques:

graphique de la perte d'entraînement avec l'initialisation du poids à constante

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

Initialisation uniforme

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.0et 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:

  1. Définissez une fonction qui attribue des pondérations par type de couche réseau, puis
  2. 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)
  • Après 2 époques:

entrez la description de l'image ici

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

Règle générale pour le réglage des poids

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

  • Après 2 époques:

graphique montrant les performances d'initialisation uniforme du poids par rapport à la règle générale d'initialisation

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)

distribution normale pour initialiser les poids

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

  • Après 2 époques:

performance de l'initialisation du poids en utilisant une distribution uniforme par rapport à la 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
ashunigion
la source
7
Quelle est la tâche que vous optimisez? Et comment une solution entièrement à zéro peut-elle ne donner aucune perte?
dédObed le
19

Pour initialiser les couches, vous n'avez généralement rien à faire.

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 conv2dexemple, 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.

prosti
la source
Cependant, l'initialisation par défaut ne donne pas toujours les meilleurs résultats. J'ai récemment implémenté l'architecture VGG16 dans Pytorch et l'ai formée sur le jeu de données CIFAR-10, et j'ai trouvé cela simplement en passant à l' xavier_uniforminitialisation 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.)
littleO
C'est parce qu'ils n'ont pas utilisé les normes Batch dans VGG16. Il est vrai qu'une bonne initialisation est importante et que pour certaines architectures, vous y prêtez attention. Par exemple, si vous utilisez (nn.conv2d (), ReLU () sequence), vous lancerez l'initialisation de Kaiming He conçue pour relu votre couche conv. PyTorch ne peut pas prédire votre fonction d'activation après le conv2d. Cela a du sens si vous évaluez les valeurs eigne, mais en général, vous n'avez pas à faire grand-chose si vous utilisez les normes Batch, elles normaliseront les sorties pour vous. Si vous prévoyez de gagner au concours SotaBench, cela compte.
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
la source
5

Désolé d'être si tard, j'espère que ma réponse vous aidera.

Pour initialiser les poids avec une normal distributionutilisation:

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

Luca Di Liello
la source
2

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.

Nicolas Gervais
la source
1

Itérer sur les paramètres

Si vous ne pouvez pas utiliser applypar exemple si le modèle n'implémente pas Sequentialdirectement:

Pareil pour tous

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

Selon la forme

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))
}
ted
la source
0

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)
Joseph Konan
la source
1
Vous pouvez commenter la réponse de Fábio Perez pour garder les réponses claires.
Phani Rithvij
0

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.

Gloire Chen
la source