21 / 27 · Нейронные сети
Обучение глубоких сетей
Что делает глубокую сеть обучаемой и удерживает её от переобучения: инициализация, сохраняющая дисперсию сигналов (Глоро и Хе), дропаут как ансамбль, затухание весов против L2-штрафа в Adam, расписания шага обучения с разогревом и ранняя остановка, аугментация данных, остаточные связи и нормализация слоя.
Обратное распространение ошибки · Стохастические и адаптивные оптимизаторы · Регуляризация
На этой странице 14
Предыдущая заметка вычислила точные градиенты для сети любой глубины. Это не делает глубокие сети простыми в обучении. В стопке из 30 слоёв градиент, доходящий до первого слоя, — произведение 30 якобианов; у простой 30-слойной сети из раздела «Код» этой заметки его норма при инициализации равна , и ранние слои не сдвигаются с места; а сеть, достаточно гибкая, чтобы подогнаться под что угодно, подгоняется и под шум в метках обучающей выборки. Эта заметка собирает стандартные средства, которые не разобраны в других местах курсов: как инициализировать веса, дропаут, затухание весов, расписания шага обучения и раннюю остановку, аугментацию данных, остаточные связи и нормализацию слоя. Три соседние темы живут в курсе о генеративных моделях, и здесь на них только ссылки: батч-нормализация и нормализация по экземпляру, механизм затухающих и взрывающихся градиентов и подробный разбор запуска обучения; самое необходимое из этого порядка действий есть в рецепте по умолчанию в конце этой заметки.
Обозначения. Слой вычисляет (смещения при инициализации равны нулю и опущены) и . и — его число входов и число выходов (fan-in / fan-out), то есть входная и выходная ширины. Веса в начале — независимые случайные величины со средним 0 и дисперсией . — шаг обучения, — коэффициент затухания весов или L2-штрафа, — вероятность дропаута.
Инициализация, сохраняющая масштаб#
Почему не начать с нуля? Если все веса слоя в начале равны, каждый нейрон слоя вычисляет одну и ту же функцию и получает один и тот же градиент, так что нейроны навсегда остаются одинаковыми: слой ведёт себя как один нейрон. Случайные веса нарушают эту симметрию. Вопрос в их масштабе.
Прямой проход. Возьмём одну предактивацию при инициализации. Веса независимы друг от друга и от входов и имеют среднее 0. Слагаемые некоррелированы, поскольку при , поэтому их дисперсии складываются (заметка о совместных распределениях). Дисперсия каждого слагаемого равна . Следовательно,
Обратим внимание: здесь стоит второй момент , а не дисперсия , потому что после ReLU активации не центрированы (He et al., 2015, раздел 2.2).
- Линейный режим (tanh около нуля, ): , так что , и дисперсия сохраняется при .
- ReLU: если симметрична относительно нуля, ReLU оставляет половину её распределения и . Тогда , и дисперсия сохраняется при
За слоёв дисперсия умножается на : любой постоянный множитель на слой, отличный от 1, геометрически растёт или убывает.
Обратный проход. Тот же аргумент, применённый к , суммирует по выходам, поэтому градиенты сохраняют масштаб при в линейном режиме или при для ReLU. Прямое и обратное условия совпадают, только если . Glorot and Bengio (2010), раздел 4.2.1, формулы 10–12 и 16, берут компромисс и выбирают веса из равномерного распределения на , дисперсия которого ровно такая (у равномерного распределения на дисперсия ). Это инициализация Ксавье/Глоро (Xavier/Glorot initialization). Для ReLU He et al. (2015), формулы 10 и 14, показывают, что на практике достаточно сохранять масштаб в одном из направлений, поскольку два произведения отличаются лишь отношением первой и последней ширин; это инициализация Хе (He initialization).
Разобранный пример. Для квадратного слоя с инициализация Глоро даёт стандартное отклонение , а инициализация Хе — . С ReLU и дисперсией Глоро каждый слой умножает дисперсию на : после 30 слоёв на выходе остаётся от дисперсии первого слоя. С инициализацией Хе она не меняется; при она вырастает в раз.
Масштаб сигнала в глубокой сети при разной инициализации
Симуляция следует теории с точностью до случайных колебаний, и раздел «Код» повторяет её при ширине 256. Именно инициализация Хе позволяет простой 30-слойной ReLU-стопке вообще обучаться: в разделе «Код» та же стопка с инициализацией PyTorch по умолчанию имеет на первом слое градиент и остаётся на значении функции потерь , как у подбрасывания монеты, а с инициализацией Хе подгоняется под данные. Инициализация PyTorch по умолчанию для nn.Linear и nn.Conv2d имеет дисперсию , в шесть раз меньше, чем у Хе; раздел о свёртках курса о генеративных моделях объясняет, откуда она берётся и почему слои нормализации это компенсируют. Инициализация задаёт масштаб только в начале; слои нормализации и остаточные связи, о которых ниже, держат его под контролем во время обучения.
Дропаут#
Дропаут (dropout; Srivastava et al., 2014) во время обучения случайно выключает каждый нейрон слоя. В форме, которую используют сегодня («инвертированный» дропаут), каждая активация заменяется на
со свежей маской для каждого объекта и каждого шага. Деление на сохраняет математическое ожидание, , поэтому на тесте маску просто убирают (model.eval() в PyTorch; p в nn.Dropout(p) — вероятность выключения). В исходной статье нейроны сохраняются с вероятностью , а на тесте исходящие веса вместо этого умножаются на (Srivastava et al., 2014, раздел 1 и рисунок 2; раздел 10 отмечает эквивалентность): там — вероятность сохранения, а не выключения.
Ансамбль прореженных сетей. Сеть с выключаемыми нейронами содержит «прореженных» подсетей (thinned networks), по одной на маску, и все они разделяют одни и те же веса. Каждый шаг обучения обучает одну из них, выбранную случайно. На тесте полная сеть с масштабированием, сохраняющим математическое ожидание, заменяет среднее всех предсказаний. Это точно лишь в частных случаях, например для одного линейного слоя, но хорошо работает на практике: на MNIST усреднение предсказаний примерно 50 сэмплированных прореженных сетей сравнивается по качеству с масштабированной сетью (Srivastava et al., 2014, раздел 7.5). Как и бэггинг, дропаут усредняет много моделей и, как случайный лес, декоррелирует их, рандомизируя то, что видит каждая; в отличие от бэггинга, члены ансамбля разделяют веса.
Разобранный пример: дропаут как штраф. Для линейной модели с дропаутом на входах ожидаемую квадратичную ошибку можно вычислить в замкнутом виде. Запишем с , так что и . Поскольку ,
Дропаут добавляет штраф наподобие гребневого, вес которого для растёт с величиной признака и с вероятностью дропаута (Srivastava et al., 2014, раздел 9.1, в их параметризации). Для , , и : , штраф равен , а ожидаемая функция потерь — . Среднее методом Монте-Карло по миллиону масок в разделе «Код» даёт . Сам гребневый штраф разобран в заметке о регуляризации.
Типичные вероятности — от до на скрытых слоях. Дропаут важнее всего для больших полносвязных слоёв, обучаемых на скромных объёмах данных; трансформеры используют на весах внимания и остаточных ветвях, а свёрточные сети с батч-нормализацией часто обходятся малым дропаутом или вовсе без него.
С Adam затухание весов — не L2-штраф#
С SGD это одно и то же. Добавление к функции потерь добавляет к градиенту, и шаг SGD становится
веса на каждом шаге уменьшаются в раз, это и есть затухание весов (weight decay). Штраф — это отрицательный логарифм гауссовского априорного распределения, поэтому минимизатор — MAP-оценка.
С Adam они различаются. Adam делит каждую координату шага на скользящую оценку среднеквадратичного значения градиента этой координаты. С L2-штрафом слагаемое входит в градиент и тоже делится: вес, у которого градиенты обычно большие, затухает меньше, а вес с маленькими градиентами — больше. Отделённое затухание весов (decoupled weight decay, AdamW) применяет сжатие вне адаптивного шага, как это делает AdamW в PyTorch:
так что каждый вес затухает с одной и той же скоростью (Loshchilov and Hutter, 2019, раздел 2: предложение 1 об эквивалентности при SGD, предложение 2 о её нарушении для адаптивных методов, алгоритм 2 — AdamW, где затухание умножается на множитель расписания, но не на базовый шаг).
Разобранный пример. При и вес под AdamW уменьшается на за шаг, каковы бы ни были его градиенты. Под Adam с L2-штрафом уменьшение составляет примерно (если пренебречь небольшим вкладом самого штрафа в ): , если среднеквадратичное значение градиентов веса равно 10, и , если оно равно 0.1. L2-вариант сильнее всего регуляризует веса, которые данные почти не ограничивают, и слабее всего — те, на которые данные давят сильнее всего; в этом примере разница стократная.
На практике AdamW с от до — выбор по умолчанию для трансформеров, а смещения и масштабы слоёв нормализации обычно освобождают от затухания. Курс о генеративных моделях использует AdamW с параметрами PyTorch по умолчанию.
Расписания шага обучения, разогрев и ранняя остановка#
Постоянный шаг обучения — компромисс. Большие шаги быстро продвигают обучение, но со стохастическими градиентами оставляют итерацию прыгать в области вокруг минимума, размер которой растёт с , как показано в заметке об оптимизаторах; маленькие шаги подходят ближе, но идут долго. Расписание (schedule) начинает с большого шага и уменьшает его:
- Ступенчатое затухание (step decay): делить на 10 на фиксированных эпохах.
- Косинусное затухание (cosine decay; Loshchilov and Hutter, 2017, раздел 3, формула 5): — плавный спуск от до за эпох.
- Обратный квадратный корень (inverse square root) — расписание исходного трансформера (Vaswani et al., 2017, раздел 5.3, формула 3): , линейный рост в течение шагов (там ), а затем убывание как .
Разогрев (warm-up) начинает с маленького шага и линейно увеличивает его в течение первых шагов. В начале веса случайны, градиенты большие и несогласованные, а оценка в Adam опирается на очень малое число шагов; шаг полного размера может тогда отбросить сеть в плохую область. Goyal et al. (2017), раздел 2.2, понадобился постепенный разогрев в течение 5 эпох, чтобы обучить ResNet-50 с большими батчами и соответственно большим шагом, а трансформерам с post-norm он нужен из-за больших градиентов около выхода при инициализации, как обсуждается в заметке о трансформере.
Три расписания шага обучения
Разобранный пример. При , и косинусное расписание даёт , и : оно дольше линейного затухания остаётся около пика и проводит последнюю четверть на маленьких шагах.
Ранняя остановка (early stopping) использует число шагов как регуляризатор. Ошибка на обучении продолжает падать, а ошибка на валидации сначала падает, а затем растёт, когда сеть начинает подгоняться под шум; сохраняют контрольную точку с наименьшей функцией потерь на валидации и останавливаются, когда она не улучшалась заданное число эпох (терпение, patience). Для квадратичной функции потерь шагов градиентного спуска с шагом действуют приблизительно как L2-штраф с (Goodfellow, Bengio and Courville, 2016, раздел 7.8; Bishop, 2006, раздел 5.5.2): чем меньше шагов, тем сильнее регуляризация. В разделе «Код» сеть обучается на 300 цифрах, у 30% которых метки случайные. Функция потерь на валидации минимальна на эпохе 127 () и к эпохе 300 вырастает до ; контрольная точка с эпохи 127 верно классифицирует тестовых цифр, последняя — только . Валидационная выборка здесь используется для принятия решения, поэтому сообщаемая доля правильных ответов должна быть получена на отдельной тестовой выборке, как настаивает заметка о валидации.
Аугментация данных#
Когда известно, что метка не меняется при некоторых преобразованиях входа, можно обучаться на преобразованных копиях: случайные кадрирования, отражения и изменения цвета для фотографий, сдвиги во времени и добавленный шум для звука, выбрасывание слов или обратный перевод для текста. Модель в каждой эпохе видит новый вариант каждого примера и вынуждена становиться инвариантной к этим изменениям. Формально функция потерь на обучении становится математическим ожиданием по случайному преобразованию ,
и каждый мини-батч оценивает его с одним сэмплированным на объект — это оценка методом Монте-Карло. Преобразования кодируют априорное знание, и неверное знание вредит: горизонтально отражённая «b» — это «d», а 6, повёрнутая на 180°, — это 9. AlexNet обучалась на случайных кадрах и их зеркальных отражениях, что увеличило обучающую выборку в 2048 раз, а на тесте усредняла предсказания по десяти кадрам (Krizhevsky, Sutskever and Hinton, 2012, раздел 4.1). Mixup идёт дальше и обучается на выпуклых комбинациях пар примеров и их меток (Zhang et al., 2018, раздел 2).
Разобранный пример. В разделе «Код» сеть обучается на 300 цифрах — один раз на них как есть и один раз с четырьмя дополнительными копиями, сдвинутыми на один пиксель. На тестовых цифрах, центрированных так же, как обучающие, обе сети верно классифицируют ( и ). На тестовых цифрах, сдвинутых на один пиксель в случайном направлении, обычная сеть падает до , а сеть с аугментацией сохраняет . Аугментация покупает устойчивость к той изменчивости, которую покрывает; повысит ли она качество на тесте, зависит от того, есть ли эта изменчивость в тестовых данных.
Остаточные связи#
Глубже не значит автоматически лучше, даже на обучающей выборке. У простой 56-слойной свёрточной сети на CIFAR-10 ошибка на обучении выше, чем у 20-слойной (He et al., 2016a, рисунок 1). Эта деградация (degradation) — не переобучение, ведь более глубокая модель могла бы скопировать менее глубокую и сделать лишние слои тождественными; просто оптимизатор не находит таких решений. Остаточная связь (residual connection) делает тождественное отображение вариантом по умолчанию:
где — небольшая стопка слоёв, остаточная ветвь. При малых весах , и блок пропускает вход насквозь; блоку нужно выучить только поправку.
Два следствия объясняют, почему это помогает оптимизации (He et al., 2016b, формулы 4–5). В развёрнутом виде выход глубокой стопки — это вход плюс сумма поправок, , а не произведение. А по цепному правилу
так что градиент доходит до каждого слоя через тождественное слагаемое, не проходя ни через одну матрицу весов. Это делает затухание градиента гораздо менее вероятным, но не гарантирует его отсутствия: второе слагаемое может погасить тождественное. Ветвь выдаёт при любом входе, и якобиан блока равен нулю. Эквивалентно, стопка из остаточных блоков содержит путей разной длины, в зависимости от того, через какие блоки проходит каждый путь, и большая часть градиента 110-слойной остаточной сети течёт по путям глубиной всего от 10 до 34 слоёв (Veit, Wilber and Belongie, 2016).
В разделе «Код» стопка из 30 слоёв ширины 32 с инициализацией PyTorch по умолчанию имеет на первом слое градиент и не уходит со значения функции потерь , а те же слои с остаточными связями получают на первом слое градиент и идеально подгоняются под данные «две луны» за 200 шагов Adam. Остаточные связи требуют, чтобы вход и выход блока имели одинаковую форму (или проекции на обходном пути); благодаря им ResNet достигают сотен слоёв, а трансформеры складывают в стопку десятки блоков. Курс о генеративных моделях называет их среди средств против затухающих градиентов, рядом с рекуррентными ячейками с гейтами.
Нормализация слоя#
Слои нормализации перемасштабируют активации во время обучения, а не только при инициализации. Батч-нормализация стандартизирует каждый признак по объектам батча, что связывает каждый выход с остальной частью батча и требует скользящих средних при инференсе (курс о генеративных моделях). Нормализация слоя (layer normalization; Ba, Kiros and Hinton, 2016) стандартизирует каждый объект по его собственным признакам, , где и — среднее и дисперсия компонент . Она не зависит от батча, одинаково ведёт себя при обучении и инференсе и работает с последовательностями любой длины, поэтому её (или её вариант RMSNorm) использует каждый трансформер, как описано в заметке о трансформере. Таблица слоёв нормализации в курсе о генеративных моделях сравнивает, какие значения делят общее среднее и дисперсию в батч-нормализации, нормализации по экземпляру и нормализации слоя.
Рецепт по умолчанию#
В сумме разумная отправная точка для новой сети такова:
- Инициализация Хе для слоёв с активациями семейства ReLU (или инициализация библиотеки по умолчанию плюс слои нормализации).
- Остаточные связи и слой нормализации, как только в сети больше нескольких слоёв.
- AdamW с затуханием весов от до , не применяемым к смещениям и параметрам нормализации.
- Пиковый шаг обучения, найденный коротким перебором, линейный разогрев, затем косинусное затухание.
- Аугментация для каждой инвариантности, которая действительно есть у задачи; дропаут там, где сеть всё ещё переобучается.
- Валидационная выборка, проверяемая каждую эпоху, с сохранением лучшей контрольной точки; а до всего этого — проверка, что сеть может довести функцию потерь почти до нуля на одном маленьком батче: это ловит большинство ошибок в модели и функции потерь.
- Оценка в режиме оценки:
model.eval()отключает дропаут и заставляет батч-нормализацию использовать накопленные статистики,torch.no_grad()не строит граф, аmodel.train()возвращает режим обучения перед следующим шагом. Контрольная точка хранит состояние модели и оптимизатора и номер эпохи, чтобы запуск можно было продолжить или вернуться к его лучшей точке. Фиксированные начальные значения генераторов (torch.manual_seedи генератор NumPy) делают запуск воспроизводимым, а несколько таких значений показывают, какая часть разницы — шум. Тестовая выборка оценивается один раз, в конце, с настройками, выбранными на валидации.
Что изменилось#
| Простая глубокая сеть | Со средствами из этой заметки | |
|---|---|---|
| Масштаб сигнала в начале | геометрически убывает или растёт с глубиной | сохраняется инициализацией Хе или Глоро |
| Путь градиента к ранним слоям | произведение одного якобиана на слой | тождественный путь через остаточные связи |
| Регуляризация | нет: сеть подгоняется под шум в метках | дропаут, отделённое затухание весов, аугментация, ранняя остановка |
| Шаг обучения | одна константа | разогрев, затем затухание |
| Нормализация | нет | батч-нормализация для свёрточных сетей, нормализация слоя для трансформеров |
Ограничения#
- Выводы верны при инициализации. Сохранение дисперсии предполагает независимость весов и входов; после начала обучения веса становятся коррелированы с данными, и масштаб держит под контролем только нормализация.
- Дропаут не бесплатен. Он замедляет сходимость, взаимодействует с батч-нормализацией, а масштабирование на тесте лишь приближает среднее по ансамблю.
- Каждое средство добавляет гиперпараметры: вероятность дропаута, , пиковый шаг, длину разогрева, терпение. Они взаимодействуют, и значения по умолчанию выше — отправные точки, а не гарантии.
- Аугментации нужно знание предметной области. Преобразование, меняющее метку, учит неверной инвариантности, а для табличных данных естественного преобразования обычно нет.
- Теория отстаёт от практики. Почему помогают остаточные связи и нормализация, отчасти объяснено (распространение сигнала, более гладкие функции потерь), а отчасти всё ещё обсуждается.
Что нужно запомнить#
- Инициализацию выбирают так, чтобы оставалась постоянной: для ReLU (Хе), для tanh (Глоро); любой другой постоянный множитель на слой геометрически растёт или убывает.
- Дропаут умножает активации на с только во время обучения; он обучает ансамбль из подсетей с общими весами и для линейной модели действует как гребневый штраф, масштабированный по признакам.
- При SGD L2-штраф — это затухание весов; при Adam — нет, а в AdamW каждый вес затухает со скоростью .
- Сначала разогрев, потом затухание шага обучения (ступенчатое, косинусное, ); остановка на лучшей валидационной контрольной точке действует как L2-штраф .
- Аугментация обучает на преобразованиях, сохраняющих метку, и покупает ровно те инвариантности, которые покрывает.
- Остаточные связи дают градиенту тождественный путь; нормализация слоя стандартизирует каждый объект по его признакам независимо от батча.
Код#
Масштаб активаций при трёх инициализациях, дропаут как штраф, сжатие весов в AdamW против L2, ранняя остановка на зашумлённых метках, аугментация сдвигами и 30-слойная сеть с инициализацией Хе и остаточными связями и без них:
import numpy as np, torch
from torch import nn
from sklearn.datasets import load_digits, make_moons
from sklearn.model_selection import train_test_split
T = lambda a: torch.tensor(a, dtype=torch.float32)
# 1. масштаб z_30 в стопке из 30 ReLU-слоёв ширины 256 при дисперсиях весов 1/n, 2/n, 4/n
# (теория: 2^-29 = 1.9e-9, 2, 4 * 2^29 = 2.1e9)
torch.manual_seed(0)
x = torch.randn(512, 256)
for var in (1 / 256, 2 / 256, 4 / 256):
h = x
for _ in range(30):
z = h @ (torch.randn(256, 256) * var**0.5)
h = torch.relu(z)
print(f'{z.pow(2).mean().item():.2g}', end=' ')
print() # 1e-09 1.1 7.5e+09
# 2. дропаут в линейной модели: E(y - sum m_j w_j x_j / (1-p))^2 = (y - w.x)^2 + p/(1-p) sum w_j^2 x_j^2
xv, w, yv, p = np.array([1.0, 2.0]), np.array([0.5, -0.25]), 1.0, 0.5
m = np.random.default_rng(0).random((1_000_000, 2)) > p # каждый вход сохраняется с вероятностью 1 - p
print(round(((yv - (m / (1 - p) * w * xv).sum(1)) ** 2).mean(), 3), (yv - w @ xv) ** 2 + p / (1 - p) * (w**2 * xv**2).sum()) # 1.499 1.5
# 3. затухание весов: уменьшение за шаг веса theta = 1 под Adam с L2 (eta*lam*theta / RMS его
# градиентов) и под AdamW (eta*lam*theta) при RMS градиентов 10 и 0.1
eta, lam = 1e-3, 1e-2
print([f'{rms}: {eta * lam / rms:.0e} vs {eta * lam:.0e}' for rms in (10.0, 0.1)]) # ['10.0: 1e-06 vs 1e-05', '0.1: 1e-04 vs 1e-05']
# 4. ранняя остановка: 300 цифр, у 30% которых метки заменены случайными, сеть 64 -> 256 -> 10, Adam по всей выборке
D = load_digits()
Xtr, Xrest, ytr, yrest = train_test_split(D.data / 16, D.target, train_size=300, random_state=0, stratify=D.target)
Xva, Xte, yva, yte = train_test_split(Xrest, yrest, train_size=300, random_state=0, stratify=yrest)
rng = np.random.default_rng(0); noisy = ytr.copy(); flip = rng.random(300) < 0.3; noisy[flip] = rng.integers(0, 10, flip.sum())
torch.manual_seed(0)
net = nn.Sequential(nn.Linear(64, 256), nn.ReLU(), nn.Linear(256, 10))
opt = torch.optim.Adam(net.parameters(), lr=1e-3)
best = (np.inf, 0, None)
for epoch in range(1, 301):
opt.zero_grad(); nn.functional.cross_entropy(net(T(Xtr)), torch.tensor(noisy)).backward(); opt.step()
with torch.no_grad():
val = nn.functional.cross_entropy(net(T(Xva)), torch.tensor(yva)).item()
if val < best[0]:
best = (val, epoch, {k: v.clone() for k, v in net.state_dict().items()})
with torch.no_grad():
last_acc = (net(T(Xte)).argmax(1).numpy() == yte).mean()
net.load_state_dict(best[2]); best_acc = (net(T(Xte)).argmax(1).numpy() == yte).mean()
print(best[1], round(best[0], 3), round(val, 3), round(best_acc, 3), round(last_acc, 3)) # 127 0.592 0.757 0.881 0.788
# 5. аугментация: 300 обучающих цифр с копиями, сдвинутыми на один пиксель, и без них; тест на центрированных
# изображениях и на изображениях, сдвинутых на один пиксель в случайном направлении
def shift(img, dx, dy):
out = np.zeros_like(img)
out[max(dy, 0):8 + min(dy, 0), max(dx, 0):8 + min(dx, 0)] = img[max(-dy, 0):8 + min(-dy, 0), max(-dx, 0):8 + min(-dx, 0)]
return out
moves = [(1, 0), (-1, 0), (0, 1), (0, -1)]
Itr, Ite, ltr, lte = train_test_split(D.images / 16, D.target, train_size=300, random_state=0, stratify=D.target)
aug = np.concatenate([Itr] + [np.stack([shift(i, *mv) for i in Itr]) for mv in moves])
Ish = np.stack([shift(i, *moves[k]) for i, k in zip(Ite, np.random.default_rng(1).integers(0, 4, len(Ite)))])
def fit(X, y, epochs):
torch.manual_seed(0)
net = nn.Sequential(nn.Flatten(), nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 10))
opt = torch.optim.Adam(net.parameters(), lr=1e-3)
for _ in range(epochs):
for idx in torch.randperm(len(X)).split(64):
opt.zero_grad(); nn.functional.cross_entropy(net(T(X[idx])), torch.tensor(y[idx])).backward(); opt.step()
return lambda Z: (net(T(Z)).argmax(1).numpy() == lte).mean().round(3)
plain, augmented = fit(Itr, ltr, 100), fit(aug, np.tile(ltr, 5), 60)
print(plain(Ite), augmented(Ite), plain(Ish), augmented(Ish)) # 0.938 0.937 0.46 0.888
# 6. глубина 30, ширина 32, две луны: простая стопка с инициализацией PyTorch по умолчанию, та же стопка с
# инициализацией Хе и стопка по умолчанию с остаточными связями; норма градиента первого слоя при
# инициализации, затем функция потерь на обучении и доля правильных ответов после 200 шагов Adam
Xm, ym = make_moons(500, noise=0.1, random_state=0)
X, Y = T(Xm), T(ym)
class Deep(nn.Module):
def __init__(self, residual, he):
super().__init__()
self.inp, self.out, self.residual = nn.Linear(2, 32), nn.Linear(32, 1), residual
self.blocks = nn.ModuleList(nn.Linear(32, 32) for _ in range(30))
if he:
for layer in [self.inp, *self.blocks, self.out]:
nn.init.kaiming_normal_(layer.weight, nonlinearity='relu'); nn.init.zeros_(layer.bias)
def forward(self, x):
h = self.inp(x)
for block in self.blocks:
h = h + block(torch.relu(h)) if self.residual else block(torch.relu(h))
return self.out(torch.relu(h)).squeeze(1)
for residual, he in ((False, False), (False, True), (True, False)):
torch.manual_seed(0); net = Deep(residual, he)
nn.functional.binary_cross_entropy_with_logits(net(X), Y).backward()
g0 = net.inp.weight.grad.norm().item()
opt = torch.optim.Adam(net.parameters(), lr=1e-3)
for _ in range(200):
opt.zero_grad(); loss = nn.functional.binary_cross_entropy_with_logits(net(X), Y); loss.backward(); opt.step()
print(residual, he, f'{g0:.1e}', f'{loss.item():.3f}', ((net(X) > 0).float() == Y).float().mean().item())
# False False 3.2e-13 0.693 0.5 | False True 1.8e-01 0.000 1.0 | True False 2.4e+00 0.000 1.0
Дальше#
Все методы до сих пор, от ближайших соседей до глубоких сетей, учились на примерах с метками: цена, класс, оценка. Часто меток нет вовсе, есть только входы, и вопрос в том, какая у них структура: группы похожих клиентов, типичные и необычные транзакции, несколько направлений, объясняющих большую часть разброса. Следующая заметка открывает часть курса об обучении без учителя кластеризацией, начиная с k-средних и его функции потерь.
Литература#
- X. Glorot, Y. Bengio. Understanding the difficulty of training deep feedforward neural networks. AISTATS, 2010. Раздел 4.2.1, формулы 10–12 и 16 (нормированная инициализация).
- K. He, X. Zhang, S. Ren, J. Sun. Delving deep into rectifiers: surpassing human-level performance on ImageNet classification. ICCV, 2015. Раздел 2.2, формулы 7–14 (инициализация для ReLU-подобных активаций); рисунок 3 (30-слойная сеть, которая не обучается с инициализацией Глоро).
- N. Srivastava, G. Hinton, A. Krizhevsky, I. Sutskever, R. Salakhutdinov. Dropout: a simple way to prevent neural networks from overfitting. Journal of Machine Learning Research, 15:1929–1958, 2014. Раздел 1 и рисунок 2 (прореженные сети, масштабирование весов), раздел 7.5 (усреднение методом Монте-Карло против масштабирования весов), раздел 9.1 (линейная регрессия), раздел 10 (масштабирование во время обучения).
- I. Loshchilov, F. Hutter. Decoupled weight decay regularization. ICLR, 2019. Раздел 2, предложения 1–3, алгоритм 2.
- I. Loshchilov, F. Hutter. SGDR: stochastic gradient descent with warm restarts. ICLR, 2017. Раздел 3, формула 5 (косинусное затухание).
- P. Goyal et al. Accurate, large minibatch SGD: training ImageNet in 1 hour. arXiv:1706.02677, 2017. Раздел 2.2 (постепенный разогрев).
- A. Vaswani et al. Attention is all you need. NeurIPS, 2017. Раздел 5.3, формула 3 (расписание шага обучения).
- A. Krizhevsky, I. Sutskever, G. E. Hinton. ImageNet classification with deep convolutional neural networks. NeurIPS, 2012. Раздел 4.1 (аугментация данных).
- H. Zhang, M. Cisse, Y. N. Dauphin, D. Lopez-Paz. mixup: beyond empirical risk minimization. ICLR, 2018. Раздел 2.
- K. He, X. Zhang, S. Ren, J. Sun. Deep residual learning for image recognition. CVPR, 2016 (цитируется как 2016a). Рисунок 1 (деградация), формула 1.
- K. He, X. Zhang, S. Ren, J. Sun. Identity mappings in deep residual networks. ECCV, 2016 (цитируется как 2016b). Формулы 4–5.
- A. Veit, M. Wilber, S. Belongie. Residual networks behave like ensembles of relatively shallow networks. NeurIPS, 2016.
- J. L. Ba, J. R. Kiros, G. E. Hinton. Layer normalization. arXiv:1607.06450, 2016.
- I. Goodfellow, Y. Bengio, A. Courville. Deep Learning. MIT Press, 2016. Разделы 7.4 (аугментация данных), 7.8 (ранняя остановка), 7.12 (дропаут), 8.4 (инициализация).
- C. M. Bishop. Pattern Recognition and Machine Learning. Springer, 2006. Раздел 5.5.2 (ранняя остановка как регуляризация).
- E. Sokolov et al. Машинное обучение (HSE), конспекты лекций, лекция 7: введение в глубинное обучение (нормализация, аугментация, остаточные сети), и материалы семинара о батч-нормализации, CNN и ResNet.