Заставить сеть учиться — и обобщать
Шестислойная сеть застряла на loss ln 2. Исправляем по одному измерению, затем double descent: 5 000 параметров на 40 точках.
На этой странице
Сеть из главы 5 работает. У неё девять параметров, она учит XOR, а её градиенты совпадают с PyTorch до шестнадцати знаков после запятой.
Сделайте её глубиной в шесть слоёв — и она полностью перестанет учиться. Не медленно — полностью. Вот шестислойная сеть на задаче классификации двух спиралей, обученная 5000 шагов:
step 1: loss 0.693147
step 5000: loss 0.693147
accuracy: 50.0 %Это число не случайно. — это binary cross-entropy модели, которая для всего выдаёт вероятность , а 50 % на сбалансированном наборе данных — это подбрасывание монеты. После пяти тысяч шагов сеть не сдвинулась ни на один знак. Ничего не упало, предупреждений не было, а градиенты всё ещё абсолютно правильные.
Эта глава — о разрыве между сетью, которая запускается, и сетью, которая работает. У неё две половины, которые выглядят как разные темы, но на деле являются одной задачей: заставить loss идти вниз и заставить его идти вниз на данных, которых модель никогда не видела.
Почему шестислойная сеть мертва
Ссылка на раздел: Почему шестислойная сеть мертваНачните с наблюдения, а не с догадок. Прогоните batch входов через сеть и выведите стандартное отклонение активаций на каждом слое, а затем стандартное отклонение градиентов весов:
def profile(model, x):
h = x
for layer in model:
h = layer(h)
if isinstance(layer, (nn.Tanh, nn.ReLU)):
print(f"activation std: {h.std().item():.4f}")
model(x).sum().backward()
for p in model.parameters():
if p.dim() == 2:
print(f"gradient std: {p.grad.std().item():.2e}")Три initialisation, одна архитектура, шесть слоёв :
| initialisation | std активаций, слои 1→6 |
|---|---|
| normal, std | 0.0145 · 0.0016 · 0.0002 · 0.0000 · 0.0000 · 0.0000 |
| normal, std | 0.6573 · 0.9296 · 0.9585 · 0.9634 · 0.9637 · 0.9625 |
| Xavier | 0.1579 · 0.1493 · 0.1353 · 0.1333 · 0.1325 · 0.1403 |
| initialisation | std градиента, первый слой → последний |
|---|---|
| normal, std | 3.20e-06 · 4.97e-07 · … · 6.40e-06 |
| normal, std | 1.94e+03 · 2.28e+02 · 1.22e+02 · 4.43e+01 · 1.85e+01 · 7.30e+00 |
| Xavier | 2.31e+00 · 4.50e-01 · 4.26e-01 · 3.89e-01 · 4.39e-01 · 4.73e-01 |
Первая строка — сеть выше, и она не учится медленно: в ней не осталось сигнала. К четвёртому слою стандартное отклонение активаций ушло в ноль при четырёх знаках после запятой. Любой вход даёт один и тот же выход, выход является константой, а градиент константы — ничто. Веса инициализировали маленькими «для безопасности», и эта малость оказалась смертельной.
Вторая строка — противоположный сбой, и его стоит понять, потому что он контринтуитивен. Активации выглядят здоровыми — около 0.96, — но это в насыщении, прижатый к своему пределу, ровно тот режим, который глава 5 измерила как потерю почти десяти тысяч раз в градиенте. И всё же градиенты огромны: 1940 на первом слое. Оба факта верны одновременно. Каждый обратный шаг умножается на , и при 128 входах с единичной дисперсией этот множитель даёт усиление примерно , которое перекрывает сжатие от насыщенного . Градиенты геометрически растут на обратном проходе. Это exploding gradient, и в любом реальном запуске обучения он за несколько шагов даёт значения loss nan.
Третья строка — то, что вам нужно: активации примерно постоянного масштаба по глубине, градиенты примерно постоянного масштаба по глубине. Ничто не умирает, ничто не взрывается.
Нормализация и та, которая выжила
Ссылка на раздел: Нормализация и та, которая выжилаХорошая initialisation фиксирует масштаб на нулевом шаге. Но она не удерживает его фиксированным: веса меняются, и к пятитысячному шагу аккуратный аргумент про дисперсию больше не применим.
Слои нормализации поддерживают масштаб постоянно. Берём вектор активаций, вычитаем среднее, делим на стандартное отклонение, затем применяем обучаемый масштаб и сдвиг , чтобы слой мог отменить нормализацию, если именно этого он захочет:
Единственный настоящий вопрос — по чему усреднять. Batch normalisation3 берёт и по batch-измерению, по одной статистике на feature. Layer normalisation4 берёт их по features, по одной статистике на example.
Этот выбор выглядит мелким, но определяет почти всё дальше:
BatchNorm делает выход каждого example зависимым от других examples, которые случайно оказались в его batch. Во время обучения это мягкий regulariser. Во время inference batch нет, поэтому приходится хранить running average статистик, собранных при обучении, — а значит слой ведёт себя по-разному в режимах training и evaluation, и забыть переключить режимы — один из самых частых багов в этой области. Кроме того, BatchNorm деградирует на маленьких batches и неудобен для последовательностей переменной длины, потому что «среднее по batch в позиции 40» считается по тому числу последовательностей, которые случайно оказались такой длины.
LayerNorm нормализует каждый example отдельно. Нет зависимости от batch, нет running statistics, одинаковое поведение при training и inference, безразличие к размеру batch, безразличие к длине последовательности. Каждое из этих свойств становится не приятным бонусом, а требованием, когда вы генерируете один token за раз для одного пользователя, — именно туда приходит глава 13.
Вот почему LayerNorm снова появится в главе 9 без изменений: transformer block использует его, и использует по причинам из правой колонки, а не потому, что он абстрактно лучше.
Исправлять по одной вещи за раз — вот настоящий skill
Ссылка на раздел: Исправлять по одной вещи за раз — вот настоящий skillЧетыре возможных исправления для мёртвой сети: Xavier initialisation, LayerNorm, residual connections и Adam вместо SGD. Есть искушение применить все четыре и идти дальше. Сделайте так — и вы никогда не узнаете, что именно имело значение, а в следующий раз у вас не будет метода, только ритуал.
Поэтому применяйте их по одному. Тот же seed, те же данные, та же архитектура, 800 шагов:
| что добавили | итоговый loss | accuracy |
|---|---|---|
| ничего | 0.6931 | 50.0 % |
| Xavier initialisation | 0.5692 | 60.4 % |
| LayerNorm | 0.6230 | 61.5 % |
| residual connections | 0.6651 | 56.6 % |
| Adam | 0.6787 | 58.7 % |
| все четыре | 0.0000 | 100.0 % |
Читайте эту таблицу так, как читали бы её в 2 часа ночи, и вывод будет таким: по отдельности ничего не работает, вместе работает всё, значит deep learning — алхимия. Этот вывод неверен, и выяснить почему — самое полезное в этой главе.
Дайте каждому запуску в шесть раз больше бюджета — 5000 шагов вместо 800, — и картина полностью изменится:
| что добавили | итоговый loss @ 5000 | accuracy |
|---|---|---|
| ничего | 0.6931 | 50.0 % |
| Xavier initialisation | 0.0007 | 100.0 % |
| LayerNorm | 0.0002 | 100.0 % |
| residual connections | 0.6653 | 56.7 % |
| Adam | 0.6908 | 53.4 % |
| Xavier + Adam | 0.0000 | 100.0 % |
| Xavier + LayerNorm | 0.0001 | 100.0 % |
Теперь картина резкая, и это диагноз, а не ритуал.
Одна initialisation исправляет проблему. Одна normalisation исправляет проблему. Каждая бьёт по настоящей болезни — forward signal схлопывается к нулю — и любой из них достаточно. На 800 шагах они лишь выглядели как частичный успех, потому что уже решили проблему, но ещё только выбирались наружу.
Residual connections и Adam не исправляют это ни при каком бюджете. Не потому, что они плохие, а потому, что лечат другую болезнь. Residual connection даёт градиенту путь вокруг блокирующего слоя; это очень ценно, когда проблема в градиенте, и бесполезно, когда forward signal уже равен нулю, потому что shortcut вокруг мёртвого слоя всё равно несёт мёртвое значение. Adam масштабирует шаг каждого параметра по его собственной истории градиентов; это помогает, когда градиенты имеют сильно разные величины, и не может воскресить сеть, выход которой не зависит от входа.
А «ничего» всё ещё ровно 0.6931 после пяти тысяч шагов. Не 0.6929. Это не медленно; это мёртво, и теперь это различие видно так, как раньше не было видно, потому что у вас есть строка с работающим исправлением для сравнения.
Заслужить PyTorch
Ссылка на раздел: Заслужить PyTorchС этого места курс использует PyTorch. Это нужно заслужить, а не просто объявить, поэтому вот ровно то, что он делает из уже знакомого вам.
Optimiser — это правило превращения градиентов в обновления параметров. Обычный gradient descent использует градиент. Momentum использует его running average, что сглаживает шум и накапливает скорость вдоль направлений, которые остаются согласованными:
v = beta * v + p.grad
p -= lr * v Adam5 хранит два running averages — градиента и градиента в квадрате — и делит одно на квадратный корень из другого, так что каждый параметр получает шаг, масштабированный по его собственной недавней величине градиента:
m = b1 * m + (1 - b1) * g # mean of the gradient
v = b2 * v + (1 - b2) * g * g # mean of the squared gradient
m_hat = m / (1 - b1 ** t) # bias correction: both averages start at zero
v_hat = v / (1 - b2 ** t)
p -= lr * m_hat / (v_hat.sqrt() + eps) Десять строк. Запустите оба против torch.optim на той же задаче на 50 шагов:
SGD+momentum by hand [2.7781870365142822, -1.0304985046386719]
torch [2.7781870365142822, -1.0304983854293823] max |diff| = 1.19e-07
Adam by hand [0.4893140196800232, -0.46317872405052185]
torch [0.48931416869163513, -0.46317875385284424] max |diff| = 1.49e-07Идентично с точностью float32. torch.optim.Adam — это те пять строк плюс десятилетия заботы о крайних случаях и C++ kernel. Вот сделка, которую вы заключаете дальше: не магия вместо понимания, а скорость вместо строк, которые вы уже написали.
Почему существует Adam: кривизна
Ссылка на раздел: Почему существует Adam: кривизнаОбычное объяснение Adam — «адаптивные learning rates для каждого параметра», но это описание, а не причина. Причина — геометрия, и её можно измерить.
Возьмите loss, у которого кривизна различается по направлениям: крутая в одном, пологая в другом. У SGD один global learning rate, поэтому он должен выбрать значение достаточно маленькое, чтобы быть стабильным в самом крутом направлении, — и это же значение оказывается слишком маленьким для пологого направления, где прогресс ползёт. Именно это создаёт классическую картинку, где gradient descent зигзагами спускается по узкой долине.
Два соотношения кривизны, три optimiser, 300 шагов, и каждому optimiser дан лучший learning rate из sweep, чтобы никто не был поставлен в худшие условия:
| соотношение кривизны | SGD | SGD + momentum | Adam |
|---|---|---|---|
| 10 : 1 | error 0.000002 | error 0.000000 | error 0.000000 |
| 1000 : 1 | error 1.925485 | error 0.001432 | error 0.000000 |
| diverged при (1000:1) | 4 из 8 rates | 4 из 8 rates | 0 из 6 rates |
При соотношении десять работает всё, и обсуждать нечего. При тысяче обычный SGD не может прийти к ответу ни при одном испробованном learning rate — его лучший результат всё ещё error 1.93, — и он outright diverges на половине rates. Adam попадает точно в target и не diverges ни на одном.
Последняя колонка — практическая причина, по которой Adam является default. Не в том дело, что Adam находит лучшие решения; на хорошо conditioned задачах настроенный SGD часто совпадает с ним или превосходит его. Дело в том, что Adam гораздо менее чувствителен к выбранному learning rate, а у реальных сетей соотношения кривизны намного хуже тысячи по их миллионам параметров.
Сюда относятся ещё две вещи, и обе — одна строка. Gradient clipping перемасштабирует вектор градиента всякий раз, когда его norm превышает threshold, что превращает строку «loss внезапно прыгает к огромному значению» из диагностической таблицы в не-событие. И learning rate schedules: короткий warmup от почти нуля за первые несколько сотен шагов, потому что оценки дисперсии Adam мусорные, пока они не видели немного градиентов, а полноразмерный шаг, сделанный на мусоре, может разрушить initialisation; затем cosine decay к нулю, потому что завершать запуск с тем же размером шага, с которого вы начали, означает дрожать вокруг минимума, а не оседать в нём.
Вторая половина: модель, которая идеально подгоняется и ничего не предсказывает
Ссылка на раздел: Вторая половина: модель, которая идеально подгоняется и ничего не предсказываетДо сих пор всё было о том, как заставить loss снижаться. Теперь более трудная половина, потому что снижение loss не является целью — это proxy для цели, и этот proxy ломается вполне конкретным и известным образом.
Двенадцать точек из гладкой функции с небольшим шумом. Подгоним полиномы растущей степени:
| степень | train RMSE | test RMSE |
|---|---|---|
| 1 | 0.764499 | 0.6985 |
| 3 | 0.252605 | 0.3031 |
| 5 | 0.164437 | 0.1568 |
| 9 | 0.088960 | 0.2347 |
| 11 | 0.000000 | 1.2094 |
Степень 11 через 12 точек проходит через каждую из них точно — train error ноль до шести знаков после запятой — и на данных, которых она не видела, оказывается в восемь раз хуже степени 5. Попросите степень 3 и степень 11 предсказать значение при , чуть за пределами training range:
degree 3: predicts -1.053 (truth -0.012)
degree 11: predicts +61.224 (truth -0.012)Шестьдесят один, когда ответ примерно равен нулю. Модель не выучила функцию; она выучила двенадцать точек, а между ними делает всё, чего требует арифметика.
Это overfitting, а его противоположность — степень 1, которая вообще не может представить кривую и плоха везде, — это underfitting. Классическое объяснение делит ожидаемую ошибку модели на три части: bias, ошибку от того, что модель слишком жёсткая, чтобы представить истину; variance, ошибку от того, что модель настолько гибкая, что гонится за шумом именно в этой выборке; и непреодолимый шум, который ничем не исправить. Простые модели имеют bias, гибкие модели имеют высокую variance, а классическое предписание — найти золотую середину, степень 5 в таблице выше.
Стандартные инструменты все атакуют член variance:
- L2 regularisation (weight decay) добавляет к loss, притягивая веса к нулю и делая функцию более гладкой. В таблице выше ущерб наносит самый большой коэффициент степени 11; штраф за размер обезвреживает его.
- L1 добавляет вместо этого . Разница не косметическая: градиент L2 пропорционален весу и потому уменьшается вместе с весом, приближаясь к нулю, но не достигая его, тогда как градиент L1 — константа , которая продолжает толкать до конца. Поэтому L1 даёт веса, которые ровно равны нулю, — она выбирает features. L2 даёт маленькие веса. Используйте L2, когда нужна гладкость, L1 — когда нужна sparsity.
- Dropout7 обнуляет случайное подмножество активаций на каждом шаге обучения, так что ни один unit не может полагаться на присутствие какого-то конкретного другого unit.
- Early stopping следит за validation loss и останавливается, когда он начинает расти.
- Data augmentation создаёт больше training examples из тех, что у вас уже есть, и атакует проблему у источника: overfitting — это не только избыток параметров, но и нехватка данных.
- Cross-validation делит данные способами и обучает раз, что покупает надёжную оценку test error, когда данных слишком мало, чтобы выделить отдельный held-out set.
Double descent, или почему предыдущий раздел — не вся история
Ссылка на раздел: Double descent, или почему предыдущий раздел — не вся историяТеперь факт, который ломает эту картину.
История про bias-variance говорит, что после золотой середины больше параметров означает худшее обобщение. У современных языковых моделей намного больше параметров, чем позволяют классические правила для данных, которые они видят, и они великолепно обобщают. Оба утверждения верны, и совместить их — самое полезное в этой главе.
Сорок training points, двадцатимерные inputs, случайные ReLU features, и число features прогоняется от 2 до 5000 — причём выбирается minimum-norm решение всякий раз, когда существует много решений, которые подходят:
| train RMSE | test RMSE | |||
|---|---|---|---|---|
| 10 | 0.25 | 0.8822 | 1.2520 | 1.89 |
| 20 | 0.50 | 0.5962 | 1.1634 | 2.59 |
| 30 | 0.75 | 0.3896 | 1.5323 | 4.15 |
| 38 | 0.95 | 0.1769 | 3.7163 | 10.25 |
| 40 | 1.00 | 0.0000 | 5.8140 | 14.83 |
| 42 | 1.05 | 0.0000 | 3.1623 | 9.35 |
| 60 | 1.50 | 0.0000 | 1.1058 | 2.78 |
| 200 | 5.00 | 0.0000 | 0.6638 | 0.98 |
| 1500 | 37.50 | 0.0000 | 0.5859 | 0.33 |
| 5000 | 125.00 | 0.0000 | 0.5664 | 0.18 |
Читайте это в три части. До классическая история выполняется точно: ошибка падает, затем начинает расти. При — interpolation threshold, где у модели ровно достаточно параметров, чтобы пройти через каждую training point, — test error достигает пика, 5.81, в пять раз хуже маленькой модели. Этот пик — классическое предупреждение, и оно реально.
Затем она снова спускается. И продолжает спускаться: мимо , мимо , до самого , где test error 0.5664 лучше, чем когда-либо достигала лучшая under-parameterised модель. Модель с 5000 параметрами, подогнанная к 40 точкам, — лучшая модель в таблице.
Это double descent,89 и механизм виден в последней колонке. Как только , существует бесконечно много настроек параметров, которые идеально подходят к training data, и то, какую именно вы получите, зависит от способа выбора. Minimum-norm решение выбирает самое маленькое, и показывает, что это значит: norm достигает пика 14.83 прямо на threshold — там, где существует ровно одно interpolating solution и вы застряли с ним, каким бы экстремальным оно ни было, — а затем монотонно падает по мере роста , потому что больше параметров означает больше interpolating solutions на выбор, а значит самое маленькое доступное решение становится меньше. При norm равен 0.18, в восемьдесят раз меньше, чем на threshold.
Так что дополнительные параметры добавляют не complexity. Они добавляют выбор, и правило отбора тратит этот выбор на простоту. Regularisation находится не в loss function, а в алгоритме. Gradient descent из маленькой initialisation имеет документированное смещение к small-norm solutions, поэтому такое поведение появляется в реальных сетях, обученных обычным способом, а не только в линейной алгебре выше.
Практическое следствие, от которого зависит глава 10: «у модели больше параметров, чем данных, значит она будет overfit» — невалидный аргумент. Это было хорошим правилом, когда модели жили слева от threshold. Всё интересное теперь живёт далеко справа от него, где правило разворачивается.
Куда дальше
Ссылка на раздел: Куда дальшеИнструментов этой главы достаточно, чтобы обучить сеть, которая работает на данных, которые можно положить в таблицу: строки чисел, столбец меток.
Язык — не такой. Прежде чем модель сможет предсказывать следующее слово, кто-то должен решить, что вообще такое «слово», — и ответом будут не буквы и не слова, а vocabulary, который модель учит из сырых байтов training data. Это решение, принятое один раз до начала обучения, определяет, сколько разных вещей модель может сказать, сколько стоит запрос и почему модели, способные сдать экзамен по праву, не умеют надёжно считать буквы в strawberry.
Глава 7 строит tokenizer.
Источники и метод
Ссылка на раздел: Источники и методДля residual connections, использованных выше: He et al., Deep Residual Learning for Image Recognition (arXiv:1512.03385). Building makemore Part 3: Activations & Gradients, BatchNorm Андрея Карпати проходит diagnostic по activation histograms на реальной модели и остаётся лучшим практическим разбором первой половины этой главы. Лекции 8 и 11–13 курса Learning From Data Ясера Абу-Мостафы дают классическую теорию обобщения по-настоящему, включая части, которые эта глава сжала до одного абзаца.
Сноски
Ссылка на раздел: Сноски-
Glorot, X. and Bengio, Y. Understanding the difficulty of training deep feedforward neural networks. AISTATS (2010). Аргумент о сохранении дисперсии воспроизведён во вставке выше. ↩
-
He, K., Zhang, X., Ren, S. and Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852 (2015). ↩
-
Ioffe, S. and Szegedy, C. Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. arXiv:1502.03167 (2015). Обратите внимание, что объяснение через «internal covariate shift» из заголовка с тех пор было существенно оспорено; слой работает, но первоначальное объяснение почему — спорное. ↩
-
Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). ↩
-
Kingma, D. P. and Ba, J. Adam: A Method for Stochastic Optimization. arXiv:1412.6980 (2014). ↩
-
Loshchilov, I. and Hutter, F. Decoupled Weight Decay Regularization. arXiv:1711.05101 (2017). ↩
-
Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I. and Salakhutdinov, R. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR 15, pp. 1929–1958 (2014). ↩
-
Belkin, M., Hsu, D., Ma, S. and Mandal, S. Reconciling modern machine-learning practice and the classical bias–variance trade-off. PNAS 116(32), pp. 15849–15854 (2019). Статья, которая дала феномену название. ↩
-
Nakkiran, P., Kaplun, G., Bansal, Y., Yang, T., Barak, B. and Sutskever, I. Deep Double Descent: Where Bigger Models and More Data Hurt. arXiv:1912.02292 (2019). Показывает эффект в реальных глубоких сетях, причём по оси training time так же, как по оси размера модели. ↩