Jak zainicjować wagi w PyTorch?

Odpowiedzi:

160

Pojedyncza warstwa

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.Modulerekurencyjnie.

Apply ( fn ): Stosuje się fnrekurencyjnie 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)
Fábio Perez
źródło
6
Znalazłem reset_parametersmetodę w kodzie źródłowym wielu modułów. Czy powinienem zastąpić metodę inicjalizacji wagi?
Yang Bo
1
co jeśli chcę użyć rozkładu normalnego z pewną średnią i standardową?
Charlie Parker
13
Jaka jest domyślna inicjalizacja, jeśli jej nie określę?
xjcl
domyślna inicjalizacja przynajmniej dla warstw liniowych to ona: pytorch.org/docs/stable/nn.html#linear-layers
arash javan
41

Porównujemy różne sposoby inicjalizacji wagi przy użyciu tej samej architektury sieci neuronowej (NN).

All Zeros or Onees

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)
  • Po 2 epokach:

wykres utraty treningu z początkiem wagi do stałej

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

Jednolita inicjalizacja

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.0i high=1.0.

Poniżej zobaczymy inny sposób (poza kodem klasy Net) zainicjowania wag sieci. Aby zdefiniować wagi poza definicją modelu, możemy:

  1. Następnie zdefiniuj funkcję przypisującą wagi według typu warstwy sieciowej
  2. 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)
  • Po 2 epokach:

wprowadź opis obrazu tutaj

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

Ogólna zasada ustawiania wag

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

  • Po 2 epokach:

wykres przedstawiający wydajność jednakowej inicjalizacji wagi w porównaniu z ogólną zasadą inicjalizacji

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, aby zainicjować wagi

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

  • Po 2 epokach:

wykonanie inicjalizacji wagi przy użyciu rozkładu równomiernego w porównaniu z rozkładem normalnym

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
źródło
8
Do jakiego zadania optymalizujesz? I w jaki sposób rozwiązanie zawierające wszystkie zera może dać zero strat?
dedObed
19

Aby zainicjować warstwy, zazwyczaj nie musisz nic robić.

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 conv2dprzykł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.

prosti
źródło
Jednak domyślna inicjalizacja nie zawsze daje najlepsze wyniki. Niedawno zaimplementowałem architekturę VGG16 w Pytorch i wyszkoliłem ją na zestawie danych CIFAR-10 i odkryłem, że po przełączeniu się na xavier_uniforminicjalizację 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.)
littleO
Dzieje się tak, ponieważ nie stosowali oni norm partii w VGG16. Prawdą jest, że właściwa inicjalizacja ma znaczenie i że w przypadku niektórych architektur należy zwrócić uwagę. Na przykład, jeśli użyjesz (nn.conv2d (), ReLU () sekwencji), zainicjujesz inicjalizację Kaiming He zaprojektowaną do relu twojej warstwy conv. PyTorch nie może przewidzieć Twojej funkcji aktywacji po conv2d. Ma to sens, jeśli oceniasz wartości własne, ale zazwyczaj nie musisz dużo robić, jeśli używasz norm wsadowych, normalizują one wyniki. Jeśli planujesz wygrać w konkursie SotaBench, ma to znaczenie.
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
źródło
5

Przepraszam za spóźnienie, mam nadzieję, że moja odpowiedź okaże się pomocna.

Aby zainicjować odważniki za normal distributionpomocą:

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

Lub constant distributionnapisać:

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

Luca Di Liello
źródło
3

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.

Nicolas Gervais
źródło
1

Iteruj po parametrach

Jeśli nie możesz użyć, applyna przykład, jeśli model nie jest implementowany Sequentialbezpośrednio:

To samo dla wszystkich

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

W zależności od kształtu

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))
}
przetrząsać
źródło
0

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)
Joseph Konan
źródło
1
Możesz tam skomentować odpowiedź Fábio Pereza, aby odpowiedzi były czyste.
Phani Rithvij
0

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ę.

Glory Chen
źródło