Ответ
Инициализация весов нулями (или одинаковыми константами) приводит к проблеме симметрии, которая блокирует обучение сети.
Механизм проблемы:
- Прямой проход: Если все веса в слое равны нулю, каждый нейрон получит одинаковый взвешенный вход (ноль). После применения функции активации (например, ReLU) все нейроны выдадут одинаковый выход (например, ноль для ReLU).
- Обратное распространение (Backpropagation): Поскольку выходы нейронов одинаковы, градиенты ошибки по весам, пришедшие к ним, также будут идентичными.
- Обновление весов: Все веса в слое обновятся на одну и ту же величину. После первой же итерации они перестанут быть нулями, но останутся равными друг другу.
В результате все нейроны в слое будут вычислять одну и ту же функцию, что эквивалентно наличию в слое всего одного нейрона. Сеть теряет способность изучать разнообразные признаки, и ее емкость резко падает.
Код, иллюстрирующий проблему:
import numpy as np
# ПЛОХО: Инициализация нулями
def zero_init(size):
return np.zeros(size)
# ХОРОШО: Случайная инициализация (например, He для ReLU)
def he_init(size):
# size = (n_neurons_current, n_neurons_previous)
fan_in = size[1]
return np.random.randn(*size) * np.sqrt(2.0 / fan_in)
# Сравнение
layer_size = (5, 10) # 5 нейронов, 10 входов
weights_zero = zero_init(layer_size)
weights_he = he_init(layer_size)
print("Zero init (первые два нейрона идентичны):n", weights_zero[0], "n", weights_zero[1])
print("nHe init (нейроны различны):n", weights_he[0][:3], "...n", weights_he[1][:3], "...")
Решение: Использовать случайную инициализацию, которая нарушает симметрию:
- Xavier/Glorot:
W = np.random.randn(n_in, n_out) * np.sqrt(1 / n_in)— хорошо для сигмоидных/гиперболических активаций. - He:
W = np.random.randn(n_in, n_out) * np.sqrt(2 / n_in)— стандарт для слоев с ReLU и его вариациями.
Ответ 18+ 🔞
Да ты посмотри, какая классика подъехала! Прямо как в том анекдоте про двух одинаковых близнецов — нихуя не разберёшь, кто есть кто. Так и тут: если все веса в слое занулить или сделать одинаковыми, получается ёперный театр.
Смотри, как это работает, на пальцах. Допустим, ты такой умный, взял и всем нейронам в слое выставил веса в ноль. Что происходит?
- Прямой проход: Каждому нейрону на вход прилетает сумма (входы * веса). А если все веса — нули, то и сумма у каждого — ноль, блядь, один в один. Кидаешь это дело в ReLU — на выходе у всех опять ноль. Получается хор из пяти одинаковых мудаков, которые орут одну и ту же ноту.
- Обратка (Backpropagation): А когда ошибку назад распространяешь, градиенты, которые приходят к этим весам, тоже, блять, получаются идентичными. Потому что вышли-то они из одного места!
- Обновление: И вот ты обновляешь веса. Они все сдвинутся на одну и ту же величину. Да, они перестанут быть нулями, но станут как братья-близнецы — не отличишь. После каждой эпохи они будут дружно шагать строем, как солдаты.
И что в итоге? А в итоге весь твой слой из, условно, ста нейронов работает как один-единственный, самый тупой нейрон. Вся мощь сети накрывается медным тазом. Она не может выучить нихуя разнообразного, только одну, блять, простейшую фичу. Пиздец, а не модель.
Вот, глянь код, чтобы совсем понятно стало. Видишь разницу?
import numpy as np
# ПЛОХО, НЕ ДЕЛАЙ ТАК: Сделал всех одинаковыми
def zero_init(size):
return np.zeros(size)
# НОРМ, ДЕЛАЙ ТАК: Разбросал как попало (He для ReLU)
def he_init(size):
# size = (n_neurons_current, n_neurons_previous)
fan_in = size[1]
return np.random.randn(*size) * np.sqrt(2.0 / fan_in)
# Сравниваем, ёпта
layer_size = (5, 10) # 5 нейронов, 10 входов
weights_zero = zero_init(layer_size)
weights_he = he_init(layer_size)
print("Zero init (первые два нейрона — клоны):n", weights_zero[0], "n", weights_zero[1])
print("nHe init (нейроны — разные личности):n", weights_he[0][:3], "...n", weights_he[1][:3], "...")
Так что же делать, спросишь ты? Да хуй с горы, всё просто! Надо эту ебаную симметрию сломать в самом начале. Берёшь и инициализируешь веса случайно, чтобы они все были разные, как снежинки, блядь.
Есть проверенные рецепты, ядрёна вошь:
- Xavier/Glorot:
W = np.random.randn(n_in, n_out) * np.sqrt(1 / n_in)— старый добрый способ, хорошо для всяких сигмоид и гиперболических тангенсов. - He:
W = np.random.randn(n_in, n_out) * np.sqrt(2 / n_in)— вот это сейчас царь и бог для всех, кто использует ReLU и его родственников. Бери — не прогадаешь.
Главное — не делай из своих нейронов мартышлюшек, повторяющих одно и то же. Дай им свободу в начале пути, а там уж они разберутся, кто на что способен.