Come inizializzare i pesi in PyTorch?


Risposte:


150

Singolo strato

Per inizializzare i pesi di un singolo livello, utilizzare una funzione da torch.nn.init. Per esempio:

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

In alternativa, puoi modificare i parametri scrivendo in conv1.weight.data(che è una torch.Tensor). Esempio:

conv1.weight.data.fill_(0.01)

Lo stesso vale per i pregiudizi:

conv1.bias.data.fill_(0.01)

nn.Sequential o personalizzato nn.Module

Passa una funzione di inizializzazione a torch.nn.Module.apply. Inizializzerà i pesi nell'intero in nn.Modulemodo ricorsivo.

apply ( fn ): si applica fnricorsivamente a ogni sottomodulo (come restituito da .children()) oltre che a self. L'utilizzo tipico include l'inizializzazione dei parametri di un modello (vedere anche torch-nn-init).

Esempio:

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)

6
Ho trovato un reset_parametersmetodo nel codice sorgente di molti moduli. Devo sostituire il metodo per l'inizializzazione del peso?
Yang Bo

1
cosa succede se voglio usare una distribuzione normale con un po 'di media e std?
Charlie Parker

12
Qual è l'inizializzazione predefinita se non ne specifico una?
xjcl

l'inizializzazione predefinita almeno per i livelli lineari è lei: pytorch.org/docs/stable/nn.html#linear-layers
arash javan

40

Confrontiamo diverse modalità di inizializzazione del peso utilizzando la stessa architettura di rete neurale (NN).

Tutti zero o uno

Se segui il principio del rasoio di Occam , potresti pensare che impostare tutti i pesi su 0 o 1 sarebbe la soluzione migliore. Questo non è il caso.

A parità di peso, tutti i neuroni di ogni strato producono lo stesso output. Ciò rende difficile decidere quali pesi regolare.

    # initialize two NN's with 0 and 1 constant weights
    model_0 = Net(constant_weight=0)
    model_1 = Net(constant_weight=1)
  • Dopo 2 epoche:

trama della perdita di allenamento con inizializzazione del peso costante

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

Inizializzazione uniforme

Una distribuzione uniforme ha la stessa probabilità di scegliere qualsiasi numero da un insieme di numeri.

Vediamo come si allena la rete neurale utilizzando un'inizializzazione del peso uniforme, dove low=0.0e high=1.0.

Di seguito vedremo un altro modo (oltre al codice della classe Net) per inizializzare i pesi di una rete. Per definire i pesi al di fuori della definizione del modello, possiamo:

  1. Definisci una funzione che assegni i pesi in base al tipo di livello di rete, quindi
  2. Applicare questi pesi a un modello inizializzato utilizzando model.apply(fn), che applica una funzione a ogni livello del modello.
    # 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)
  • Dopo 2 epoche:

inserisci qui la descrizione dell'immagine

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

Regola generale per l'impostazione dei pesi

La regola generale per impostare i pesi in una rete neurale è impostarli in modo che siano vicini allo zero senza essere troppo piccoli.

Una buona pratica è iniziare i pesi nell'intervallo [-y, y] dove y=1/sqrt(n)
(n è il numero di input per un dato neurone).

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

di seguito confrontiamo le prestazioni di NN, pesi inizializzati con distribuzione uniforme [-0.5,0.5) rispetto a quello il cui peso è inizializzato usando la regola generale

  • Dopo 2 epoche:

grafico che mostra le prestazioni dell'inizializzazione uniforme del peso rispetto alla regola generale di inizializzazione

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)

distribuzione normale per inizializzare i pesi

La distribuzione normale dovrebbe avere una media di 0 e una deviazione standard di y=1/sqrt(n), dove n è il numero di input per 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)

di seguito mostriamo le prestazioni di due NN, uno inizializzato utilizzando la distribuzione uniforme e l'altro utilizzando la distribuzione normale

  • Dopo 2 epoche:

prestazioni di inizializzazione del peso utilizzando la distribuzione uniforme rispetto alla distribuzione 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

7
Qual è l'attività per cui ottimizzi? E come può una soluzione di tutti zeri dare zero perdite?
dedObed

19

Per inizializzare i livelli in genere non è necessario fare nulla.

PyTorch lo farà per te. Se ci pensi, ha molto senso. Perché dovremmo inizializzare i livelli, quando PyTorch può farlo seguendo le ultime tendenze.

Controlla ad esempio il livello lineare .

Nel __init__metodo chiamerà la funzione Kaiming He init.

    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 stesso vale per altri tipi di livelli. Ad conv2desempio, controlla qui .

Da notare: il vantaggio di una corretta inizializzazione è la maggiore velocità di addestramento. Se il tuo problema merita un'inizializzazione speciale, puoi farlo in seguito.


Tuttavia, l'inizializzazione predefinita non dà sempre i migliori risultati. Recentemente ho implementato l'architettura VGG16 in Pytorch e l'ho addestrata sul set di dati CIFAR-10, e ho scoperto che semplicemente passando xavier_uniformall'inizializzazione per i pesi (con i bias inizializzati a 0), piuttosto che usando l'inizializzazione predefinita, la mia precisione di convalida dopo 30 epoche di RMSprop sono aumentate dall'82% all'86%. Ho anche ottenuto un'accuratezza di convalida dell'86% utilizzando il modello VGG16 integrato di Pytorch (non pre-addestrato), quindi penso di averlo implementato correttamente. (Ho usato un tasso di apprendimento di 0,00001.)
littleO

Questo perché non hanno utilizzato Batch Norms in VGG16. È vero che la corretta inizializzazione è importante e che per alcune architetture si presta attenzione. Ad esempio, se usi (nn.conv2d (), sequenza ReLU ()) inizierai l'inizializzazione Kaiming He progettata per relu il tuo livello di conv. PyTorch non può prevedere la tua funzione di attivazione dopo conv2d. Questo ha senso se valuti gli eignevalues, ma in genere non devi fare molto se usi Batch Norms, normalizzeranno gli output per te. Se hai intenzione di vincere alla competizione SotaBench, è 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)

5

Scusa per il ritardo, spero che la mia risposta possa essere d'aiuto.

Per inizializzare i pesi con un normal distributionutilizzo:

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

O per usare una constant distributionscrittura:

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

O per usare un uniform distribution:

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

Puoi controllare altri metodi per inizializzare i tensori qui


2

Se desideri una maggiore flessibilità, puoi anche impostare i pesi manualmente .

Supponi di avere input di tutti quelli:

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 vuoi creare uno strato denso senza pregiudizi (così possiamo visualizzare):

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

Imposta tutti i pesi su 0,5 (o qualsiasi altra cosa):

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

I pesi:

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

Tutti i tuoi pesi ora sono 0,5. Passa i dati attraverso:

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

Ricorda che ogni neurone riceve 8 input, tutti con peso 0,5 e valore 1 (e nessun bias), quindi somma fino a 4 per ciascuno.


1

Itera sui parametri

Se non è possibile utilizzare, applyad esempio, se il modello non viene implementato Sequentialdirettamente:

Lo stesso per tutti

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

A seconda della 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)

Puoi provare con torch.nn.init.constant_(x, len(x.shape))per verificare che siano inizializzati correttamente:

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

0

Se vedi un avviso di ritiro (@ 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)

1
Puoi commentare laggiù alla risposta di Fábio Perez per mantenere pulite le risposte.
Phani Rithvij

0

Perché finora non ho avuto abbastanza reputazione, non posso aggiungere un commento sotto

la risposta postato da prosti in 26 '19 Jun alle 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)

Ma voglio sottolineare che in realtà sappiamo che alcune ipotesi nel documento di Kaiming He , Approfondire i raddrizzatori: superare le prestazioni a livello umano sulla classificazione ImageNet , non sono appropriate, anche se sembra che il metodo di inizializzazione deliberatamente progettato abbia un successo nella pratica .

Ad esempio, nella sottosezione del caso di propagazione all'indietro , assumono che $ w_l $ e $ \ delta y_l $ siano indipendenti l'uno dall'altro. Ma come tutti sappiamo, prendi la mappa del punteggio $ \ delta y ^ L_i $ come istanza, spesso è $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $ se usiamo un tipico obiettivo della funzione di perdita di entropia incrociata.

Quindi penso che il vero motivo di fondo per cui He's Initialization funziona bene resta da svelare. Perché tutti hanno visto il suo potere nel potenziare la formazione di deep learning.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.