Come inizializzare i pesi e i bias (ad esempio, con l'inizializzazione He o Xavier) in una rete in PyTorch?
Come inizializzare i pesi e i bias (ad esempio, con l'inizializzazione He o Xavier) in una rete in PyTorch?
Risposte:
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.Module
modo ricorsivo.
apply ( fn ): si applica
fn
ricorsivamente 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)
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)
Validation Accuracy
9.625% -- All Zeros
10.050% -- All Ones
Training Loss
2.304 -- All Zeros
1552.281 -- All Ones
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.0
e 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:
- Definisci una funzione che assegni i pesi in base al tipo di livello di rete, quindi
- 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)
Validation Accuracy
36.667% -- Uniform Weights
Training Loss
3.208 -- Uniform Weights
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
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 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
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 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 conv2d
esempio, 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.
xavier_uniform
all'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.)
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)
Scusa per il ritardo, spero che la mia risposta possa essere d'aiuto.
Per inizializzare i pesi con un normal distribution
utilizzo:
torch.nn.init.normal_(tensor, mean=0, std=1)
O per usare una constant distribution
scrittura:
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
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.
Se non è possibile utilizzare, apply
ad esempio, se il modello non viene implementato Sequential
direttamente:
# 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)
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))
}
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)
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.
reset_parameters
metodo nel codice sorgente di molti moduli. Devo sostituire il metodo per l'inizializzazione del peso?