Как инициализировать веса и смещения (например, с инициализацией He или Xavier) в сети в PyTorch?

Ответы (10)

Однослойный

Чтобы инициализировать веса одного слоя, используйте функцию из torch.nn.init. Например:

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

В качестве альтернативы вы можете изменить параметры, записав в conv1.weight.data (который является torch.Tensor). Пример:

conv1.weight.data.fill_(0.01)

То же самое для смещений:

conv1.bias.data.fill_(0.01)

nn.Последовательный или пользовательский nn.Module

Передайте функцию инициализации в torch.nn.Module.apply. Он будет инициализировать веса всего модуля nn.Module рекурсивно.

apply (fn): Применяет fn рекурсивно к каждому подмодулю (как возвращается .children ()), а также себя. Типичное использование включает инициализацию параметров модели (см. Также torch-nn-init).

Пример:

def init_weights(m):
    if isinstance(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)

Мы сравниваем разные режимы инициализации веса, используя ту же архитектуру нейронной сети (NN).

Все нули или единицы

Если вы будете следовать принципу бритвы Оккама, вы можете подумать, что установка всех весов на 0 или 1 будет лучшим решением. Это не так.

При одинаковом весе все нейроны на каждом слое производят одинаковый результат. Это затрудняет решение, какие веса корректировать.

    # initialize two NN's with 0 and 1 constant weights
    model_0 = Net(constant_weight=0)
    model_1 = Net(constant_weight=1)
  • Через 2 эпохи:

plot of training loss with weight initialization to constant

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

Единая инициализация

A равномерное распределение имеет равную вероятность выбора любого числа из набора чисел.

Давайте посмотрим, насколько хорошо нейронная сеть обучается с использованием инициализации с единым весом, где low = 0,0 и high = 1,0.

Ниже мы увидим другой способ (помимо кода класса Net) инициализировать веса сети. Чтобы определить веса вне определения модели, мы можем:

  1. Определите функцию, которая назначает веса по типу сетевого уровня, затем
  2. Примените эти веса к инициализированной модели, используя model.apply (fn), который применяет функцию к каждому слою модели.
    # 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)
  • Через 2 эпохи:

enter image description here

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

Общее правило установки весов

Общее правило для установки весов в нейронной сети - установить их близкими к нулю, но не слишком маленькими.

Good practice is to start your weights in the range of [-y, y] where y=1/sqrt(n)
(n is the number of inputs to a given neuron).

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

below we compare performance of NN, weights initialized with uniform distribution [-0.5,0.5) versus the one whose weight is initialized using general rule

  • After 2 epochs:

plot showing performance of uniform initialization of weight versus general rule of initialization

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)

normal distribution to initialize the weights

The normal distribution should have a mean of 0 and a standard deviation of y=1/sqrt(n), where n is the number of inputs to 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)

below we show the performance of two NN one initialized using uniform-distribution and the other using normal-distribution

  • After 2 epochs:

performance of weight initialization using uniform-distribution versus the normal distribution

Validation Accuracy
85.775% -- Uniform Rule [-y, y)
84.717% -- Normal Distribution
Training Loss
0.329  -- Uniform Rule [-y, y)
0.443  -- Normal Distribution

Потому что у меня пока недостаточно репутации, я не могу добавить комментарий под

ответ, отправленный prosti в 26 июня '19 в 13:16.

 def reset_parameters (self):
        init.kaiming_uniform_ (собственный вес, a = math.sqrt (3))
        если self.bias не равен None:
            fan_in, _ = init._calculate_fan_in_and_fan_out (собственный вес)
            граница = 1 / math.sqrt (fan_in)
            init.uniform_ (self.bias, -bound, привязанный)

Но я хочу отметить, что на самом деле мы знаем некоторые предположения в статье Kaiming He, Углубляясь в выпрямители: Превосходя человеческий уровень по классификации ImageNet, не подходят , хотя похоже, что специально разработанный метод инициализации дает успех на практике.

Например, в подразделе Случай обратного распространенияпредполагается, что $ w_l $ и $ \ delta y_l $ независимы друг от друга. Но, как все мы знаем, возьмем карту очков $ \ delta y ^ L_i $ в качестве примера, часто это $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $, если мы используем типичный Цель функции кросс-энтропийных потерь.

Итак, я думаю, что истинная основная причина, по которой He's Initialization работает хорошо, еще предстоит разгадать. Потому что каждый был свидетелем его силы в ускорении обучения глубокому обучению.

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)

Вот способ получше, просто передайте всю свою модель

import torch.nn as nn
def initialize_weights(model):
    # Initializes weights according to the DCGAN paper
    for m in model.modules():
        if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d, nn.BatchNorm2d)):
            nn.init.normal_(m.weight.data, 0.0, 0.02)
        # if you also want for linear layers ,add one more elif condition 

Перебрать параметры

Если вы не можете использовать , примените, например, если модель не реализует Последовательный напрямую:

Одинаково для всех

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

Вы можете попробовать с torch.nn.init.constant_ (x, len (x.shape)), чтобы проверить правильность их инициализации:

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

Для инициализации слоев обычно ничего делать не нужно. PyTorch сделает это за вас. Если задуматься, в этом есть большой смысл. Зачем нам инициализировать слои, если PyTorch может это делать в соответствии с последними тенденциями.

Проверьте, например, Линейный слой.

В методе __ init __ вызывается функция 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)

Аналогично для других типов слоев. Для conv2d например проверьте здесь.

Примечание: выигрыш от правильной инициализации - это более высокая скорость обучения. Если ваша проблема заслуживает особой инициализации, вы можете сделать это позже.

Извините за опоздание, надеюсь, мой ответ поможет.

Для инициализации весов нормальным распределением используйте:

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

Или, чтобы использовать постоянное распределение напишите:

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

Или использовать равномерное распределение:

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

Вы можете проверить другие методы инициализации тензоров здесь

Если вы видите предупреждение об устаревании (@ 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)

Если вам нужна дополнительная гибкость, вы также можете установить веса вручную.

Допустим, вы ввели все:

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

И вы хотите сделать плотный слой без смещения (чтобы мы могли визуализировать):

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

Установите все веса на 0,5 (или что-нибудь еще):

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

Вес:

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

Все ваши веса теперь 0,5. Передайте данные через:

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

Помните, что каждый нейрон получает 8 входов, каждый из которых имеет вес 0,5 и значение 1 (без смещения), поэтому в сумме получается 4 для каждого.

2022 WebDevInsider