Jak zainicjować wagi i odchylenia (na przykład z inicjalizacją He lub Xavier) w sieci w PyTorch?
Jak zainicjować wagi i odchylenia (na przykład z inicjalizacją He lub Xavier) w sieci w PyTorch?
Odpowiedzi:
Aby zainicjować wagi pojedynczej warstwy, użyj funkcji from torch.nn.init
. Na przykład:
conv1 = torch.nn.Conv2d(...)
torch.nn.init.xavier_uniform(conv1.weight)
Alternatywnie możesz zmodyfikować parametry, pisząc do conv1.weight.data
(czyli a torch.Tensor
). Przykład:
conv1.weight.data.fill_(0.01)
To samo dotyczy uprzedzeń:
conv1.bias.data.fill_(0.01)
nn.Sequential
lub niestandardowe nn.Module
Przekaż funkcję inicjalizacyjną do torch.nn.Module.apply
. To zainicjuje wagi w całości nn.Module
rekurencyjnie.
Apply ( fn ): Stosuje się
fn
rekurencyjnie do każdego modułu podrzędnego (zwróconego przez.children()
), a także do siebie. Typowe użycie obejmuje inicjalizację parametrów modelu (patrz także torch-nn-init).
Przykład:
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)
Jeśli zastosujesz się do zasady brzytwy Ockhama , możesz pomyśleć, że najlepszym rozwiązaniem byłoby ustawienie wszystkich ciężarów na 0 lub 1. Nie o to chodzi.
Przy takiej samej wadze wszystkie neurony w każdej warstwie wytwarzają ten sam wynik. To sprawia, że trudno jest zdecydować, które ciężary dostosować.
# 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
Dystrybucja uniform ma równe prawdopodobieństwo wytypowania dowolnej liczby ze zbioru liczb.
Zobaczmy, jak dobrze sieć neuronowa trenuje przy użyciu inicjalizacji o jednakowej masie, gdzie low=0.0
i high=1.0
.
Poniżej zobaczymy inny sposób (poza kodem klasy Net) zainicjowania wag sieci. Aby zdefiniować wagi poza definicją modelu, możemy:
- Następnie zdefiniuj funkcję przypisującą wagi według typu warstwy sieciowej
- Zastosuj te wagi do zainicjowanego modelu za pomocą
model.apply(fn)
, który stosuje funkcję do każdej warstwy modelu.
# 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
Ogólna zasada ustalania wag w sieci neuronowej polega na ustawianiu ich wartości bliskich zeru, ale nie są zbyt małe.
Dobrą praktyką jest rozpoczęcie wagi w zakresie [-y, y], gdzie
y=1/sqrt(n)
(n to liczba wejść do danego neuronu).
# 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)
poniżej porównujemy wydajność NN, wagi inicjowane z rozkładem równomiernym [-0,5,0,5) z wagą inicjowaną według reguły ogólnej
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)
Rozkład normalny powinien mieć średnią równą 0 i odchylenie standardowe wynoszące
y=1/sqrt(n)
, gdzie n jest liczbą wejść do 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)
poniżej przedstawiamy działanie dwóch NN, z których jeden został zainicjowany przy użyciu rozkładu równomiernego, a drugi przy użyciu rozkładu normalnego
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 zrobi to za Ciebie. Jeśli się nad tym zastanowić, ma to sens. Po co inicjalizować warstwy, skoro PyTorch może to zrobić zgodnie z najnowszymi trendami.
Sprawdź na przykład warstwę Linear .
W __init__
metodzie wywoła funkcję init 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)
Podobnie jest z innymi typami warstw. Na conv2d
przykład sprawdź tutaj .
Uwaga: Uzyskanie odpowiedniej inicjalizacji to większa prędkość treningu. Jeśli twój problem zasługuje na specjalną inicjalizację, możesz to zrobić później.
xavier_uniform
inicjalizację wag (z obciążeniami początkowymi na 0), zamiast używania domyślnej inicjalizacji, moja dokładność walidacji po 30 epoki RMSprop wzrosły z 82% do 86%. Uzyskałem również 86% dokładności walidacji, gdy używam wbudowanego modelu VGG16 Pytorcha (nie został wstępnie wyszkolony), więc myślę, że zaimplementowałem go poprawnie. (Użyłem wskaźnika uczenia się 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)
Przepraszam za spóźnienie, mam nadzieję, że moja odpowiedź okaże się pomocna.
Aby zainicjować odważniki za normal distribution
pomocą:
torch.nn.init.normal_(tensor, mean=0, std=1)
Lub constant distribution
napisać:
torch.nn.init.constant_(tensor, value)
Lub użyj uniform distribution
:
torch.nn.init.uniform_(tensor, a=0, b=1) # a: lower_bound, b: upper_bound
Można sprawdzić inne sposoby, aby zainicjować tensory tutaj
Jeśli potrzebujesz dodatkowej elastyczności, możesz również ustawić wagi ręcznie .
Powiedzmy, że masz wszystkie dane wejściowe:
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.]])
I chcesz utworzyć gęstą warstwę bez uprzedzeń (abyśmy mogli to wizualizować):
d = nn.Linear(8, 8, bias=False)
Ustaw wszystkie wagi na 0,5 (lub cokolwiek innego):
d.weight.data = torch.full((8, 8), 0.5)
print(d.weight.data)
Wagi:
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]])
Wszystkie twoje wagi wynoszą teraz 0,5. Przekaż dane przez:
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>)
Pamiętaj, że każdy neuron otrzymuje 8 wejść, z których wszystkie mają wagę 0,5 i wartość 1 (bez odchylenia), więc sumuje się do 4 dla każdego.
Jeśli nie możesz użyć, apply
na przykład, jeśli model nie jest implementowany Sequential
bezpośrednio:
# 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)
Możesz spróbować torch.nn.init.constant_(x, len(x.shape))
sprawdzić, czy są odpowiednio zainicjowane:
init_funcs = {
"default": lambda x: torch.nn.init.constant_(x, len(x.shape))
}
Jeśli zobaczysz ostrzeżenie o wycofaniu (@ 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)
Ponieważ dotychczas nie miałem wystarczającej reputacji, nie mogę dodać komentarza pod
odpowiedź wysłana przez prosti w czerwcu 26 '19 o 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)
Ale chcę zaznaczyć, że tak naprawdę znamy pewne założenia z artykułu Kaiming He , Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification , nie są właściwe, chociaż wygląda na to, że celowo zaprojektowana metoda inicjalizacji odniosła sukces w praktyce .
Np. W podrozdziale Przypadku propagacji wstecznej zakładają, że $ w_l $ i $ \ delta y_l $ są od siebie niezależne. Ale jak wszyscy wiemy, weźmy mapę wyników $ \ delta y ^ L_i $ jako przykład, często jest to $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $, jeśli używamy typowego krzyżowa funkcja utraty entropii cel.
Więc myślę, że prawdziwym powodem, dla którego Inicjalizacja He działa dobrze, pozostaje do wyjaśnienia. Ponieważ wszyscy byli świadkami jego mocy w zwiększaniu treningu głębokiego uczenia się.
reset_parameters
metodę w kodzie źródłowym wielu modułów. Czy powinienem zastąpić metodę inicjalizacji wagi?