Перейти к содержимому
ПереводыАндрей Карпатый

Zero to Hero, лекция 4: активации, градиенты и BatchNorm (makemore, часть 3)

Подробный конспект лекции Карпатого: почему стартовый loss 27 вместо 3.3, чем опасен насыщенный tanh, откуда берётся Kaiming-инициализация, как устроена батч-нормализация и по каким четырём графикам видно, здорова ли глубокая сеть.

  • Перед рекуррентными сетями Карпатый задерживается на MLP из прошлой лекции. Нужно понять, как ведут себя активации на прямом проходе и градиенты на обратном. Именно из-за них глубокие и рекуррентные сети трудно обучать, и почти вся история архитектур — попытки это исправить.
  • Две ошибки инициализации. Первая: стартовый loss 27 вместо ожидаемых −ln⁡(1/27)≈3.30-\ln(1/27) \approx 3.30 — сеть «уверенно ошибается». Вторая: насыщенный tanh, который гасит градиент. Исправив обе, val улучшается с 2.17 до 2.10 без единого изменения архитектуры.
  • Вместо подбора множителей на глаз — Kaiming-инициализацияинициализация Kaiming (He)EN Kaiming / He initializationVar(w) = 2 / n_in (He и соавт., MSRA, 2015). Двойка компенсирует то, что ReLU обнуляет половину входов. В PyTorch — nn.init.kaiming_normal_.: стандартное отклонение весов равно gain/fan_in\text{gain}/\sqrt{\text{fan\_in}}, для tanh gain равен 5/35/3.
  • Батч-нормализацияпакетная нормализация (BatchNorm)EN batch normalizationНормирует каждый признак по примерам батча, хранит скользящие статистики для инференса (Ioffe, Szegedy, 2015). Стандарт CNN; в LLM не используется. (2015): раз хочется, чтобы входы tanh были примерно нормальными, их можно просто нормировать по батчу. Цена — примеры в батче связываются друг с другом. Это даёт побочную регуляризацию и бесконечные баги. Карпатый признаётся, что не раз «стрелял себе в ногу» этим слоем.
  • Во второй части код переписывается в модули в стиле torch.nn, и появляются четыре диагностических графика: активации, их градиенты, градиенты весов и главное — отношение обновления к весам. Ориентир — около 10−310^{-3}.

Главный урок курса по этой теме — 3.4.1 «Инициализация и нормализация»: затухание сигнала через 50 слоёв, Xavier и Kaiming, BatchNorm, LayerNorm и RMSNorm, Pre-Norm и Post-Norm. Лекция и урок дополняют друг друга. Урок шире (он ведёт к трансформерам и LLM), лекция подробнее показывает диагностику на живой сети. Графики «update / data» и «loss на старте» разобраны в уроке 3.3.3 «Как отлаживать обучение», а модули, параметры и буферы — в уроке 3.3.2.

Порядок такой: урок 4.1.4 (MLP-модель), потом первая часть лекции (до 1:18), потом урок 3.4.1, потом вторая часть лекции. Новичку можно бегло просмотреть разбор ResNet (1:04:55) и документации PyTorch. Главы про стартовый loss, насыщение tanh и четыре графика пропускать не стоит.

Код лекции — ноутбук makemore_part3_bn.ipynb (MIT). Статьи: He et al., Delving Deep into Rectifiers (2015) и Ioffe, Szegedy, Batch Normalization (2015). Китайская учебная версия с переведёнными пояснениями — 004_makemore_BatchNorm.

Таймкоды — из глав в описании видео.

ВремяГлава в оригиналеО чём
0:00introзачем ещё одна лекция про MLP
1:25starter codeисходный код, torch.no_grad
4:24fixing the initial lossloss 27 вместо 3.3
13:04fixing the saturated tanhнасыщенный tanh и мёртвые нейроны
27:53calculating the init scale: “Kaiming init”масштаб весов по fan_in
40:43batch normalizationбатч-нормализация с нуля
1:03:07batch normalization: summaryсводка по слою
1:04:55real example: resnet50 walkthroughResNet и документация PyTorch
1:14:14summary of the lectureитоги первой части
1:18:40just kidding: part2: PyTorch-ifying the codeсвои Linear, BatchNorm1d, Tanh
1:26:52viz #1: forward pass activations statisticsгистограммы активаций
1:30:59viz #2: backward pass gradient statisticsгистограммы градиентов
1:32:09the fully linear case of no non-linearitiesсеть без нелинейностей
1:36:20viz #3: parameter activation and gradient statisticsградиенты весов, grad/data
1:39:55viz #4: update:data ratio over timeupdate/data во времени
1:46:05bringing back batchnorm, looking at the visualizationsте же графики с BatchNorm
1:51:34summary of the lecture for real this timeнастоящие итоги

В планах — рекуррентные сети, GRU, LSTM. Карпатый объясняет, почему пока рано. Рекуррентные сети очень выразительны, в принципе они могут реализовать любой алгоритм. Но их плохо оптимизируют методы первого порядка, то есть градиентный спуск, — других у нас нет. Чтобы понять почему, нужна интуиция о том, как в сети ведут себя активации и градиенты. Многие варианты RNN появились как раз как попытки исправить эти проблемы.

Стартовый код — MLP из прошлой лекции, очищенный от «магических чисел»: размер эмбеддинга n_embd = 10 и скрытого слоя n_hidden = 200 вынесены в переменные. Около 11 тысяч параметров, 200 тысяч шагов, батч 32. Оценка вынесена в функцию split_loss(split) с декоратором @torch.no_grad(). Внутри такой функции PyTorch не строит граф для обратного прохода, и всё работает быстрее. Исходный результат: train около 2.12, val около 2.17.

На первом шаге loss равен 27, а потом быстро падает до 2–3. Карпатый говорит, что почти всегда можно заранее прикинуть, каким должен быть loss при инициализации. На старте у сети нет причин предпочитать какой-то символ. Значит, распределение должно быть примерно равномерным, вероятность каждого символа — 1/271/27, а loss:

−ln⁡127=ln⁡27≈3.30.-\ln \frac{1}{27} = \ln 27 \approx 3.30.

27 вместо 3.3 значит, что сеть «уверенно ошибается»: выдаёт очень острые распределения, и правильный символ почти всегда получает крошечную вероятность. Это видно на игрушечном примере из четырёх классов. При нулевых логитах softmax равномерный, и loss равен ln⁡4≈1.39\ln 4 \approx 1.39 при любом правильном ответе. Если умножить случайные логиты на 10, правильный класс почти наверняка проиграет, и loss взлетит. На ещё более экстремальных логитах он доходит до бесконечности. Логиты не обязаны быть нулями — достаточно, чтобы они были равны. Но проще всего сделать их около нуля.

Логиты считаются как h @ W2 + b2. Значит, лечим так: смещение b2 умножаем на 0, веса W2 — на 0.01. Строго ноль Карпатый ставить не любит. Для выходного слоя это, может, и безопасно, но маленькие ненулевые числа оставляют немного случайности для нарушения симметрии. Стартовый loss становится 3.32.

Заодно исчезает «хоккейная клюшка» на графике loss: резкое падение в первые сотни шагов. В нём сеть ничему полезному не училась, а просто сжимала раздутые логиты. Теперь эти шаги тратятся на настоящее обучение, и итог лучше: val 2.17 → 2.13.

Логиты в порядке, но внутри сети неладно. Гистограмма скрытых активаций h показывает, что большинство значений равны −1-1 или 11. Причина видна на гистограмме входов tanh (preactivation): они разбросаны от −5-5 до 1515, и tanh почти всё сжимает к краям.

Почему это плохо, видно из обратного прохода tanh, который мы писали в micrograd:

∂L∂x=(1−t2)⋅∂L∂t,t=tanh⁡(x).\frac{\partial L}{\partial x} = (1 - t^2) \cdot \frac{\partial L}{\partial t}, \qquad t = \tanh(x).

Если t=±1t = \pm 1, множитель 1−t21 - t^2 равен нулю, и градиент через этот нейрон не проходит. Если t=0t = 0, градиент проходит без изменений. Градиент через tanh может только уменьшаться, и тем сильнее, чем ближе нейрон к краю. Интуиция Карпатого: на плоском «хвосте» tanh изменение входа почти не меняет выход, значит, веса этого нейрона на loss не влияют.

Карпатый рисует картинку 32×20032 \times 200 (примеры × нейроны): белое — где ∣h∣>0.99|h| > 0.99. Белого очень много. Если бы какой-то столбец был целиком белым, получился бы мёртвый нейрон: ни один пример не выводит его из насыщения, градиента нет, нейрон не учится никогда. Здесь таких столбцов нет, поэтому сеть всё-таки учится, но хуже, чем могла бы.

Та же беда у других нелинейностей:

  • sigmoid — тоже «сжимающая» функция с плоскими хвостами;
  • ReLU — при отрицательном входе ровно ноль, и градиент обнуляется полностью. Мёртвый ReLU-нейрон может появиться при инициализации или во время обучения. Слишком большой шаг «выбивает» нейрон с многообразия данных, и его больше ничто не активирует. Карпатый называет это «необратимым повреждением мозга» сети;
  • у Leaky ReLU плоских участков нет, а у ELU они есть.

Лечение то же, что для логитов: входы tanh должны быть ближе к нулю. b1 умножаем на 0.01 (немного разнообразия не помешает), W1 — сначала на 0.1, потом на 0.2. Белое на картинке исчезает, val 2.13 → 2.10. Сеть мелкая, поэтому прощает плохую инициализацию. При 50 слоях ошибки накапливаются, и сеть может не обучиться совсем.

4. Kaiming-инициализация: откуда брать множитель

Заголовок раздела «4. Kaiming-инициализация: откуда брать множитель»

Множитель 0.2 подобран на глаз. Для сети из многих слоёв так не годится. Карпатый показывает, откуда берётся правильный масштаб:

import torch
x = torch.randn(1000, 10) # 1000 примеров, 10 признаков, std = 1
w = torch.randn(10, 200) # веса тоже из N(0, 1)
print(x.std(), (x @ w).std()) # ≈ 1.0 и ≈ 3.16: разброс вырос в √10 раз
print((x @ (w / 10**0.5)).std()) # ≈ 1.0: делим на √fan_in — разброс сохраняется

Каждый выход — сумма десяти произведений, и дисперсии складываются. Чтобы стандартное отклонение осталось единичным, веса делят на fan_in\sqrt{\text{fan\_in}}, где fan_in — число входов слоя. Подробно, с выводом через дисперсию, это разбирает урок 3.4.1.

Систематически вопрос изучала статья Кайминга Хэ и соавторов «Delving Deep into Rectifiers» (2015), про свёрточные сети с ReLU. ReLU обнуляет половину распределения, и это нужно компенсировать множителем: стандартное отклонение весов 2/fan_in\sqrt{2/\text{fan\_in}}. Статья проверяет и обратный проход. Если прямой проход нормирован правильно, обратный тоже нормирован с точностью до постоянного множителя, и на практике это почти не важно.

В PyTorch это torch.nn.init.kaiming_normal_. Аргумент mode чаще оставляют по умолчанию (fan_in), а nonlinearity задаёт поправку gain:

НелинейностьgainПочему
нет (линейный слой)11ничего не сжимается
ReLU2\sqrt 2обнуляет половину распределения
tanh5/35/3сжимает хвосты, и это нужно компенсировать

Итоговая формула: std=gain/fan_in\text{std} = \text{gain} / \sqrt{\text{fan\_in}}. У нас fan_in равен 30 (три символа по 10 чисел), поэтому std=5/330≈0.30\text{std} = \frac{5/3}{\sqrt{30}} \approx 0.30 — вместо подобранных на глаз 0.2. Результат тот же, val 2.10, но без магических чисел.

Важная оговорка Карпатого. Семь лет назад, когда вышла статья, к инициализации приходилось относиться очень бережно: всё было хрупким. С тех пор появились средства, которые делают обучение намного устойчивее. Это остаточные связи, слои нормализации (batch, layer, group) и оптимизаторы лучше обычного SGD, например RMSProp и особенно Adam. Сам он на практике просто делит веса на fan_in\sqrt{\text{fan\_in}}.

BatchNorm предложили в Google в 2015 году. Статья была очень влиятельной: с этим слоем стало возможно надёжно обучать глубокие сети. Идея, которая когда-то, по словам Карпатого, «взорвала ему мозг»: мы хотим, чтобы входы tanh были примерно стандартными нормальными. Так давайте просто нормируем их. Вычитание среднего и деление на стандартное отклонение — дифференцируемые операции, через них можно делать backprop.

Для предактиваций hpreact формы 32×20032 \times 200 среднее и стандартное отклонение считаются по батчу, то есть по оси 0 с keepdim=True. Получаются строки 1×2001 \times 200, по числу на нейрон. После нормировки каждый нейрон на этом батче имеет нулевое среднее и единичный разброс. Отсюда название.

Но жёстко держать распределение нормальным нельзя: сети нужно уметь делать нейроны острее или площе. Поэтому сверху добавляются обучаемые масштаб (gain, γ\gamma) и сдвиг (bias, β\beta). На старте это единицы и нули, а дальше backprop двигает их как обычные параметры:

x^=x−μBσB2+ε,y=γx^+β.\hat x = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \varepsilon}}, \qquad y = \gamma \hat x + \beta.

Здесь μB\mu_B и σB2\sigma_B^2 — среднее и дисперсия по батчу, ε\varepsilon (обычно 10−510^{-5}) защищает от деления на ноль. Код из ноутбука лекции (MIT), комментарии наши:

# параметры BatchNorm: учатся backprop'ом
bngain = torch.ones((1, n_hidden))
bnbias = torch.zeros((1, n_hidden))
# буферы: скользящие оценки для инференса, градиентом не учатся
bnmean_running = torch.zeros((1, n_hidden))
bnstd_running = torch.ones((1, n_hidden))
# ...внутри цикла обучения:
hpreact = embcat @ W1 # смещение b1 убрано: BatchNorm его всё равно вычтет
bnmeani = hpreact.mean(0, keepdim=True) # среднее по батчу для каждого нейрона
bnstdi = hpreact.std(0, keepdim=True) # разброс по батчу
hpreact = bngain * (hpreact - bnmeani) / bnstdi + bnbias
with torch.no_grad(): # обновление буферов не участвует в backprop
bnmean_running = 0.999 * bnmean_running + 0.001 * bnmeani
bnstd_running = 0.999 * bnstd_running + 0.001 * bnstdi

В сети с одним скрытым слоем BatchNorm почти ничего не даёт: val около 2.10. Масштаб весов здесь легко посчитать и руками. Но в глубокой сети с разными типами слоёв подобрать масштаб каждой матрицы невозможно, а вставить BatchNorm после каждого линейного или свёрточного слоя — легко. Поэтому слой и стал популярным.

Раньше каждый пример обрабатывался независимо, а батч был лишь способом считать быстрее. С BatchNorm выход сети на примере зависит от того, какие ещё примеры случайно попали в батч: от них зависят среднее и разброс. Активации и логиты немного «дрожат». Казалось бы, это баг, но дрожание работает как регуляризация, что-то вроде аугментации данных: сети труднее подогнаться под конкретные примеры. Из-за этого побочного эффекта от BatchNorm трудно отказаться. Хотя никому не нравится, что примеры связаны, и поэтому придумали нормализации без этой связи: layer, instance, group normalization.

После обучения сеть хочется применять к одному примеру, а для одного примера статистику батча не посчитать. Статья предлагает после обучения один раз прогнать весь train и зафиксировать среднее и разброс. Это «калибровка», и Карпатый её показывает. Но второй этап никто делать не хочет, поэтому оценки копят прямо во время обучения скользящим средним, как в коде выше. В конце они почти совпадают с честно посчитанными, и loss на валидации тот же. Так устроен и nn.BatchNorm1d в PyTorch.

Ещё две детали:

  • смещение перед BatchNorm бесполезно. Что бы ни прибавил b1, BatchNorm вычтет это вместе со средним, и градиент b1 будет нулём. Поэтому у линейного слоя перед BatchNorm смещение отключают, а сдвиг берёт на себя β\beta;
  • momentum. В лекции коэффициент обновления буферов 0.001, а в PyTorch по умолчанию 0.1. При большом батче статистики стабильны, и 0.1 подходит. При батче 32 они сильно скачут, и с 0.1 буферы могут не успокоиться.

6. Настоящий пример: ResNet и документация PyTorch

Заголовок раздела «6. Настоящий пример: ResNet и документация PyTorch»

Карпатый открывает реализацию ResNet в PyTorch. Это остаточная сеть для классификации изображений, сама архитектура будет позже. Её повторяющийся блок (bottleneck) устроен по тому же мотиву, что и наша сеть: слой с весами (свёртка — по сути тот же Wx+bWx + b, только на участках изображения), потом нормализация (BatchNorm2d), потом нелинейность (ReLU). У свёрток там стоит bias=False — ровно по той причине, что выше.

Документация PyTorch подтверждает остальное:

  • nn.Linear по умолчанию берёт веса из равномерного распределения на [−1/fan_in, 1/fan_in][-1/\sqrt{\text{fan\_in}},\ 1/\sqrt{\text{fan\_in}}]. Тот же масштаб 1/fan_in1/\sqrt{\text{fan\_in}}, только с gain 1 и без нормального распределения;
  • у nn.BatchNorm1d есть eps (по умолчанию 10−510^{-5}), momentum (0.1), affine (обучаемые γ\gamma и β\beta, почти всегда включено) и track_running_stats. Последний можно выключить, если статистику считаете отдельно после обучения.

Сводка улучшений из ноутбука:

Шагtrainval
исходная сеть2.1252.168
исправлен «уверенно неправый» softmax2.072.13
исправлен насыщенный tanh2.0362.103
Kaiming вместо подбора на глаз2.0382.107
добавлен BatchNorm2.0672.105

«Шутка, лекция не закончилась» — и Карпатый переписывает сеть в модули с тем же интерфейсом, что в PyTorch:

  • Linear(fan_in, fan_out, bias=True) — веса из нормального распределения, делённые на fan_in\sqrt{\text{fan\_in}}, смещение нулями;
  • BatchNorm1d(dim, eps=1e-5, momentum=0.1) — с флагом training. В режиме обучения слой берёт статистику батча и обновляет буферы, в режиме оценки — накопленные running_mean и running_var. Здесь копится уже дисперсия, как в статье, а не стандартное отклонение. Параметры (gamma, beta) возвращаются в parameters(), буферы — нет: их обновляет экспоненциальное скользящее среднее под torch.no_grad();
  • Tanh — без параметров.

Каждый модуль сохраняет свой выход в self.out. В PyTorch такого нет, это нужно только для графиков. Если приписать спереди nn., код будет работать с настоящими слоями PyTorch. Сеть из шести линейных слоёв по 100 нейронов — около 46–47 тысяч параметров. Курс разбирает настоящие nn.Module и nn.Sequential в уроке 3.3.2, а модульную версию этого кода — в шестой лекции.

Сеть сначала без BatchNorm: «бутерброд» из Linear и Tanh. Перед backward Карпатый вызывает layer.out.retain_grad(), чтобы PyTorch сохранил градиенты промежуточных тензоров.

1. Активации прямого прохода. Гистограммы выходов каждого tanh-слоя, их среднее, стандартное отклонение и доля насыщения (сколько значений по модулю больше 0.97). С gain 5/35/3 первый слой насыщен процентов на 20, дальше всё стабилизируется: разброс около 0.65, насыщение около 5%. С gain 1 активации от слоя к слою сжимаются к нулю, потому что tanh каждый раз немного сжимает распределение. С gain 3 — сильное насыщение. Карпатый честно говорит, что не знает, откуда в PyTorch взялось именно 5/35/3. Но эмпирически оно хорошо держит этот бутерброд.

2. Градиенты обратного прохода. Те же гистограммы для градиентов по выходам tanh. В здоровой сети они примерно одинаковы во всех слоях. При gain 0.5 активации гаснут, а градиенты расползаются. При gain 3 появляется асимметрия между слоями.

Отступление: сеть совсем без нелинейностей. Если убрать все tanh, правильный gain — ровно 1. При 5/35/3 активации от слоя к слою разбухают, а градиенты сжимаются. Так выглядело обучение глубоких сетей до нормализаций, Adam и остаточных связей: «эквилибристика», где всё должно быть точно выверено. Зачем вообще tanh, если без него так просто? Стопка линейных слоёв схлопывается в один линейный слой и представляет только линейные функции. При этом динамика обучения у такой стопки другая, чем у одного слоя, и её изучают целые статьи. Нелинейности превращают бутерброд в сеть, которая может приблизить любую функцию.

3. Градиенты весов. Для каждой матрицы весов — среднее и разброс градиента и отношение разброса градиента к разбросу самих весов (grad:data). Почти везде градиент примерно в 1000 раз меньше весов. Выбивается последний слой: его градиенты примерно в 10 раз больше, потому что веса этого слоя искусственно уменьшили. При простом SGD последний слой на старте учится раз в 10 быстрее остальных. Со временем это выравнивается.

4. Отношение обновления к весам во времени. Важно не отношение градиента к весам, а то, насколько шаг реально меняет веса. Для каждой матрицы на каждом шаге считается log⁡10std(lr⋅grad)std(data)\log_{10} \dfrac{\text{std}(\text{lr} \cdot \text{grad})}{\text{std}(\text{data})}, и строится график.

ud = [] # update:data для каждого параметра на каждом шаге
# ...в цикле обучения, после шага:
with torch.no_grad():
ud.append([((lr * p.grad).std() / p.data.std()).log10().item() for p in parameters])

Ориентир Карпатого — около −3-3: за шаг веса меняются примерно на тысячную долю. Сильно ниже значит, что шаг слишком мал. С learning rate 0.001 обновления примерно в 10 000 раз меньше весов (около −4-4 на графике), и сеть учится слишком медленно. Сильно выше значит, что веса меняются слишком резко. Если забыть деление на fan_in\sqrt{\text{fan\_in}}, все четыре графика сразу показывают беду: насыщение, разнобой градиентов, отношения около −1-1 и −1.5-1.5. Курс применяет то же правило к обучению с Adam в уроке 3.3.3.

Теперь BatchNorm ставится после каждого линейного слоя, перед tanh. Можно и после нелинейности, и после последнего линейного слоя перед loss. Тогда «уверенность» выхода на старте уменьшают через γ\gamma последнего BatchNorm (умножают на 0.1), а не через веса.

Активации, естественно, выглядят идеально: перед каждым tanh стоит нормировка. Разброс около 0.65, насыщение около 2%, все слои одинаковые. Главное — сеть стала намного менее чувствительна к gain. При gain 0.2 активации не меняются вовсе. Но меняется масштаб обновлений: обратный проход BatchNorm зависит от масштаба входа. Поэтому при сильной смене масштаба весов приходится перенастраивать learning rate. Можно даже вообще не делить веса на fan_in\sqrt{\text{fan\_in}} — сеть будет вести себя прилично. Только обновления окажутся заметно ниже 10−310^{-3}, и learning rate нужно поднять раз в 10, до 1.0.

Карпатый называет три цели второй части. Первая — познакомить с BatchNorm, одним из первых современных приёмов, стабилизирующих глубокие сети. Вторая — переписать код в модули, которые складываются как кубики лего. Третья — показать диагностику: смотреть не на один снимок, а на динамику активаций, градиентов и отношений update/data.

Чего он не добился и не пытался. BatchNorm не улучшил результат: Карпатый проверял, в том числе с поиском learning rate. Сеть упирается уже не в оптимизацию, а в длину контекста — три символа. Дальше нужны более мощные архитектуры: рекуррентные сети и трансформеры. Полного объяснения, почему смена gain требует другого learning rate, тоже нет: для этого нужно разбирать обратный проход каждого слоя. Его финальная мысль: инициализация и обратный проход до сих пор не «решены», это открытая область исследований, но инструменты, которые показывают, на верном ли мы пути, у нас уже есть.

Где курс объясняет то же самое и что есть сверх курса

Заголовок раздела «Где курс объясняет то же самое и что есть сверх курса»
Тема лекцииГде в курсеЧто в лекции сверх курса
Ожидаемый loss на старте (ln⁡C\ln C)3.3.3как «уверенно неправый» выход рождает «хоккейную клюшку»
Насыщение tanh, мёртвые нейроны3.4.1, 3.3.3картинка насыщения «примеры × нейроны»
Xavier и Kaiming, gain3.4.1gain 5/35/3 для tanh и его проверка на шестислойной сети
BatchNorm: формулы, режимы train / eval3.4.1калибровка после обучения против скользящих средних, momentum при маленьком батче, смещение перед BN
LayerNorm, RMSNorm, Pre-Norm3.4.1в лекции только упомянуты
Гистограммы, update / data ≈ 10−310^{-3}3.3.3четыре графика подряд и как они ломаются при ошибках
Модули, параметры и буферы3.3.2свой BatchNorm1d с нуля
Остаточные связи3.4.2в лекции только анонс

Курс идёт дальше лекции в главном: объясняет, почему в трансформерах вместо BatchNorm используют LayerNorm и RMSNorm. Карпатый к этому придёт в лекции 7.

Упражнения из описания видео (копия с пояснениями на китайском — 004_makemore_BatchNorm/BatchNorm_exercises.ipynb в репозитории karpathy-zero-to-hero-zh-transcripts):

  1. Инициализируйте все веса и смещения нулями и обучите сеть. Обычно ждут, что сеть либо учится, либо нет. На деле она учится, но лишь частично, и результат плохой. Посмотрите гистограммы активаций и градиентов и выясните, какие параметры учатся, а какие нет, и почему.
  2. Обучите небольшую трёхслойную MLP с BatchNorm, затем «вклейте» γ\gamma, β\beta и буферы в веса предыдущих линейных слоёв и уберите BatchNorm. Проверьте, что loss на инференсе не изменился (см. нашу проверку выше). В этом преимущество BatchNorm перед LayerNorm: после обучения слой можно выбросить.

И ещё:

  1. Повторите «журнал» улучшений из раздела 6 на своём компьютере. Совпадают ли числа до второго знака? Если нет, почему?
  2. Постройте четыре графика для сети с ReLU вместо tanh и gain 2\sqrt 2. Найдите мёртвые нейроны после обучения с завышенным learning rate.
  3. Замените свой BatchNorm1d на nn.BatchNorm1d(n_hidden, momentum=0.001) и сравните буферы после обучения.
ENRU
activations, pre-activationsактивации, предактивации (входы нелинейности)
confidently wrongуверенно неправ
hockey stick loss«хоккейная клюшка» на графике loss
saturated tanh, flat tailsнасыщенный tanh, плоские хвосты
dead neuron, dead ReLUмёртвый нейрон, мёртвый ReLU
fan-in / fan-outчисло входов / выходов слоя
gainпоправочный множитель (gain)
Kaiming (He) initializationинициализация Kaiming (He)
batch normalizationбатч-нормализация
scale and shift (gain, bias)масштаб и сдвиг
running mean / variance, bufferскользящее среднее / дисперсия, буфер
momentum (BatchNorm)коэффициент скользящего среднего
calibrationкалибровка
train / eval modeрежим обучения / режим оценки
grad:data ratio, update:data ratioотношение градиента к весам, отношение обновления к весам
residual connectionостаточная связь