Як ініціалізувати ваги в PyTorch?


Відповіді:


150

Одношаровий

Щоб ініціалізувати ваги одного шару, використовуйте функцію від torch.nn.init. Наприклад:

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

Крім того, ви можете змінити параметри, написавши на conv1.weight.data(що є a torch.Tensor). Приклад:

conv1.weight.data.fill_(0.01)

Те саме стосується упереджень:

conv1.bias.data.fill_(0.01)

nn.Sequential або на замовлення nn.Module

Передайте функцію ініціалізації torch.nn.Module.apply. Він ініціалізує ваги в цілому nn.Moduleрекурсивно.

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

Приклад:

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
Я знайшов reset_parametersметод у вихідному коді багатьох модулів. Чи слід замінювати метод ініціалізації ваги?
Ян Бо

1
що робити, якщо я хочу використовувати звичайний розподіл з деяким середнім значенням і стандартним значенням?
Чарлі Паркер,

12
Що таке ініціалізація за замовчуванням, якщо я її не вказав?
xjcl

типовою ініціалізацією принаймні для лінійних шарів є її: pytorch.org/docs/stable/nn.html#linear-layers
arash javan

40

Ми порівнюємо різні режими вагової ініціалізації, використовуючи ту саму архітектуру нейронної мережі (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 епохи:

графік втрати тренувань з ініціалізацією ваги до постійної

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

Рівномірна ініціалізація

Рівномірний розподіл має рівну ймовірність вибору будь-якого числа з набору чисел.

Давайте подивимося, наскільки нейронна мережа тренується з використанням рівномірної ініціалізації ваги, де 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 епохи:

введіть тут опис зображення

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

Загальне правило встановлення ваг

Загальне правило встановлення ваг у нейронній мережі полягає в тому, щоб встановити їх так, щоб вони були близькими до нуля, не будучи занадто малими.

Хороша практика - починати вагові коефіцієнти з діапазону [-y, y], де y=1/sqrt(n)
(n - кількість входів до даного нейрона).

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

нижче ми порівнюємо продуктивність NN, ваги, ініціалізовані з рівномірним розподілом [-0,5,0,5), з вагою, вага якої ініціалізується за загальним правилом

  • Через 2 епохи:

Діаграма, що показує ефективність рівномірної ініціалізації ваги проти загального правила ініціалізації

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)

нормальний розподіл для ініціалізації ваг

Нормальний розподіл повинен мати середнє значення 0 і стандартне відхилення y=1/sqrt(n), де n - кількість входів в 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)

нижче ми показуємо ефективність двох NN, один ініціалізований за допомогою рівномірного розподілу, а інший - за допомогою нормального розподілу

  • Через 2 епохи:

виконання ініціалізації ваги з використанням рівномірного розподілу проти нормального розподілу

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
Для якого завдання ви оптимізуєте? І як рішення з усіма нулями може дати нульові втрати?
dedObed

19

Для ініціалізації шарів зазвичай не потрібно нічого робити.

PyTorch зробить це за вас. Якщо задуматися, це має багато сенсу. Навіщо нам ініціалізувати шари, коли PyTorch може це робити, слідуючи останнім тенденціям.

Перевірте, наприклад, Лінійний шар .

У __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, наприклад , перевірити тут .

Зверніть увагу: Виграш правильної ініціалізації полягає у вищій швидкості тренування. Якщо ваша проблема заслуговує на особливу ініціалізацію, ви можете зробити це післямови.


Однак ініціалізація за замовчуванням не завжди дає найкращі результати. Нещодавно я впровадив архітектуру VGG16 у Pytorch і навчив її набору даних CIFAR-10, і я виявив, що просто переключившись на xavier_uniformініціалізацію для ваг (з упередженнями, ініціалізованими до 0), а не використовуючи ініціалізацію за замовчуванням, моя точність перевірки після 30 епохи RMSprop зросли з 82% до 86%. Я також отримав 86% точності перевірки при використанні вбудованої моделі VGG16 від Pytorch (не попередньо навченої), тому, думаю, я її правильно застосував. (Я використовував рівень навчання 0,00001.)
littleO

Це тому, що вони не використовували пакетні норми у VGG16. Це правда, що правильна ініціалізація має значення, і на деякі архітектури ви звертаєте увагу. Наприклад, якщо ви використовуєте послідовність (nn.conv2d (), ReLU ()), ви ініціюєте ініціалізацію Kaiming He, призначену для relu вашого рівня conv. PyTorch не може передбачити вашу функцію активації після conv2d. Це має сенс, якщо ви оцінюєте власні значення, але зазвичай вам не потрібно багато робити, якщо ви використовуєте пакетні норми, вони нормалізують результати для вас. Якщо ви плануєте перемогти у змаганні SotaBench, це має значення.
прості

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

Вибачте за запізнення, сподіваюся, моя відповідь допоможе.

Для ініціалізації ваг за normal distributionдопомогою:

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

Або використовувати constant distributionзапис:

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

Або використовувати uniform distribution:

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

Ви можете перевірити інші методи ініціалізації тензорів тут


2

Якщо вам потрібна додаткова гнучкість, ви також можете встановити ваги вручну .

Скажімо, ви ввели всі:

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

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


1

Ітерація над параметрами

Якщо ви не можете використовувати, applyнаприклад, якщо модель не реалізується Sequentialбезпосередньо:

Для всіх однаково

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

0

Якщо ви бачите попередження про зневажання (@ 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
Ви можете прокоментувати там відповідь Фабіо Переза, щоб відповіді були чистими.
Phani Rithvij

0

Тому що у мене поки що недостатньо репутації, я не можу додати коментар під

відповідь Написав PrOSTi в 26 '19 червня о 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)

Але я хочу зазначити, що насправді ми знаємо деякі припущення в роботі Kaiming He , Deving Deep into Rectifiers: Supering Performance на людському рівні щодо класифікації ImageNet , не є доречними, хоча, схоже, свідомо розроблений метод ініціалізації робить хіт на практиці .

Наприклад, у підрозділі Справа про зворотне поширення про зворотне вони припускають, що $ w_l $ і $ \ delta y_l $ не залежать один від одного. Але, як ми всі знаємо, візьмемо оціночну карту $ \ delta y ^ L_i $ як примірник, часто це $ y_i-softmax (y ^ L_i) = y_i-softmax (w ^ L_ix ^ L_i) $, якщо ми використовуємо типовий ціль функції перехресної ентропії.

Тому я думаю, що справжня основна причина того, чому Він ініціалізує добре, залишається розгадати. Тому що кожен пересвідчився в його силі щодо підвищення рівня глибокого навчання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.