Как найти оптимум функции потерь: от градиентного спуска до Adam

Введение

Зачастую задачи машинного обучения формулируются таким образом, что «веса» модели, которую мы строим, возникают, как решение оптимизационной задачи. В качестве VIP-примера рассмотрим задачу линейной регрессии:

По сути, мы получили чистейшую задачу квадратичной оптимизации. В чем особенность конкретно этой задачи? Она выпуклая.

Для интересующихся определением.

Функция является (нестрого) выпуклой (вниз), если для любых верно, что

Чтобы запомнить, в какую сторону неравенство, всегда полезно рисовать следующую картинку с графическим определением выпуклой функции.

21

Эквивалентное определение, если функция достаточно гладкая – гессиан неотрицательно определен в любой точке, то есть в каждой точке функция хорошо приближается параболоидом ветвями вверх. Отсюда по критерию минимальности второго порядка автоматически следует, что всякая точка локального оптимума является точкой локального минимума, то есть локальных максимумов и сёдел в выпуклом мире попросту не существует.

Важное свойство выпуклых функций – локальный минимум автоматически является глобальным (но не обязательно единственным!). Это позволяет избегать уродливых ситуаций, которые с теоретической точки зрения могут встретиться в невыпуклом случае, например, вот такой:

Теорема (No free lunch theorem) Пусть – алгоритм оптимизации, использующий локальную информацию (все производные в точке). Тогда существует такая невыпуклая функция , что для нахождения глобального минимума на квадрате с точностью требуется совершить хотя бы шагов.

Для интересующихся доказательствами.

Будем строить наш контрпример, пользуясь принципом сопротивляющегося оракула (или рассуждениями с противником, кому как привычнее называть).

Разделим нашу область на подкубики размера . Зададим функцию следующим образом – она будет тождественно равна на всех кубиках, кроме одного, в середине которого будет точка с значением (мы не специфицируем, как значение будет гладко «снижаться» до ; можно построить кусочно-линейную функцию, а потом сгладить её).

А именно поставим ноль в тот кубик, который наш алгоритм оптимизации посетит последним. Так как кубиков у нас , то алгоритм должен всегда совершить как минимум шагов, попробовав все кубики. Итого у нас следующая картинка ():

21

Отметим дополнительно, что полученный контрпример можно сделать какой угодно гладкости (но не аналитическим).

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

Встречаются ли в жизни функции невыпуклые? Повсеместно! Например, функция потерь при обучении нейронных сетей, как правило, не является выпуклой. Но отсюда не следует, что любой алгоритм их оптимизации будет обязательно неэффективным: ведь «контрпример» из теоремы довольно специфичен. И, как мы увидим, оптимизировать невыпуклые функции очень даже возможно.

Найти глобальный минимум невыпуклой функции – очень трудная задача, но зачастую нам хватает локального, который является, в частности, стационарной точкой: такой, в которой производная равна нулю. Все теоретические результаты в случае невыпуклых задач, как правило, касаются поиска таких точек, и алгоритмы тоже направлены на их отыскание.

Этим объясняется и то, что большинство алгоритмов оптимизации, придуманных для выпуклого случая, дословно перешли в невыпуклый. Теоретическая причина в следующем: в выпуклом случае поиск стационарной точки и поиск минимума – буквально одна и та же задача, поэтому то, что хорошо ищет минимум в выпуклом случае, ожидаемо будет хорошо искать стационарные точки в невыпуклом. Практическая же причина в том, что оптимизаторы в библиотеках никогда не спрашивают, выпуклую ли им функцию подают на вход, а просто работают и работают хорошо.

Внимательный читатель мог возразить на моменте подмены задачи: подождите-ка, мы ведь хотим сделать функцию как можно меньше, а не стационарную точку искать какую-то непонятную. Доказать в невыпуклом случае тут, к сожалению, ничего невозможно, но на практике мы снова используем алгоритмы изначально для выпуклой оптимизации. Почему?

Причина номер 1: сойтись в локальный минимум лучше, чем никуда. Об этом речь уже шла.

Причина номер 2: в окрестности локального минимума функция становится выпуклой, и там мы сможем быстро сойтись.

Причина номер 3: иногда невыпуклая функция является в некотором смысле «зашумленной» версией выпуклой или похожей на выпуклую. Например, посмотрите на эту картинку (функция Леви):

21

У этой функции огромное количество локальных минимумов, но «глобально» она кажется выпуклой. Что-то отдаленно похожее наблюдается и в случае нейронных сетей. Нашей задачей становится не скатиться в маленький локальный минимум, который всегда рядом с нами, а в большую-большую ложбину, где значение функции минимально и в некотором смысле стабильно.

Причина номер 4: оказывается, что градиентные методы весьма часто сходятся именно к локальным минимумам.

Сразу отметим важную разницу между выпуклой и невыпуклой задачами: в выпуклом случае работа алгоритма оптимизации не очень существенно зависит от начальной точки, поскольку мы всегда скатимся в точку оптимума. В невыпуклом же случае правильно выбранная точка старта – это уже половина успеха.

Теперь перейдём к разбору важнейших алгоритмов оптимизации.

Градиентный спуск (GD)

Опишем самый простой метод, который только можно придумать – градиентный спуск. Для того, чтобы его определить, вспомним заклинание из любого курса матанализа: «градиент – это направление наискорейшего локального возрастания функции», тогда антиградиент – это направление наискорейшего локального убывания.

Для интересующихся формализмом.

Воспользуемся формулой Тейлора для (направления спуска):

Мы хотим уменьшить значение функции, то есть

При имеем . Более того, мы хотим наискорешйшего убывания, поэтому это скалярное произведение хочется минимизировать. Сделаем это при помощи неравенства Коши-Буняковского:

Равенство в неравенстве Коши-Буняковского достигается при пропорциональности аргументов, то есть

Тогда пусть – начальная точка градиентного спуска. Тогда каждую следующую точку мы выбираем следующим образом:

где – это размер шага (он же learning rate). Общий алгоритм градиентного спуска пишется крайне просто и элегантно:

x = normal(0, 1)                # можно пробовать и другие виды инициализации
repeat S times:                 # другой вариант: while abs(err) > tolerance
   h = grad_f(x)                # вычисляем направление спуска
   x -= alpha * h               # обновляем значение в точке

Эту схему в приложении к линейной регрессии можно найти в параграфе про линейные модели.

После всего этого начинаются тонкости:

  • А как вычислять градиент?
  • А как выбрать размер шага?
  • А есть ли какие-то теоретические оценки сходимости?

Начнем разбирать вопросы постепенно. Для вычисления градиентов современный человек может использовать инструменты автоматического дифференцирования. Идейно, это вариация на тему алгоритма обратного распространения ошибки (backpropagation), ведь как правило человек задает функции, составленные из элементарных при помощи умножений/делений/сложений/композиций. Такой метод реализован во всех общих фреймворках для нейронных сетей (Tensorflow, PyTorch, Jax).

Но, вообще говоря, возникает некоторая тонкость. Например, расмотрим задачу линейной регрессии. Запишем её следующим образом:

Видим, что слагаемых суммарно – размер выборки. При порядка и (это количество признаков) порядка вычисление градиента за становится жутким мучением. Но если от избавиться без дополнительных предположений (например, о разреженности) нельзя, то с зависимостью от в каком-то смысле удастся разделаться при помощи метода стохастического градиентного спуска.

Хранение градиентов тоже доставит нам проблемы. У градиента столько же компонент, сколько параметров у модели, и если мы имеем дело с глубокой нейросетью, это даст значительные затраты дополнительной памяти. Хуже того, метод обратного распространения ошибки устроен так, что нам приходится помнить все промежуточные представления для вычисления градиентов. Поэтому вычислить градиент целиком невозможно ни для какой нормальной нейросети, и от этой беды тоже приходится спасаться с помощью стохастического градиентного спуска.

Теперь перейдем к размеру шага. Теория говорит о том, что если функция гладкая, то можно брать достаточно маленький размер шага, где под достаточно маленьким подразумевается , где – некоторая константа, которая зависит от гладкости задачи (так называемая константа Липшица). Вычисление этой константы может быть задачей сложнее, чем изначальная задача оптимизации, поэтому этот вариант нам не годится. Более того, эта оценка крайне пессимистична – мы ведь хотим размер шага как можно больше, чтобы уменьшить функцию как можно больше, а тут мы будем изменять все очень мало.

Существует так называемый метод наискорейшего спуска: выбираем размер шага так, чтобы как можно сильнее уменьшить функцию:

Одномерная оптимизация является не сильно сложной задачей, поэтому теоретически мы можем её совершать (например, методом бинарного/тернарного поиска или золотого сечения), можно этот шаг также совершать неточно. Но сразу стоит заметить, что это можно делать, только если функция вычислима более-менее точно за разумное время, в случае линейной регрессии это уже не так (не говоря уже о нейронных сетях).

Также есть всевозможные правила Армихо/Гольдштейна/Вульфа и прочее и прочее, разработанные в давние 60-е, и для их проверки требуется снова вычислять значения функции в точке. Желающие могут посмотреть на эти условия на википедии. Про более хитрые вариации выбора шагов мы поговорим позже, но сразу стоит сказать, что эта задача довольно сложная.

По поводу теории: сначала скажем что-то про выпуклый случай.

В максимально общем выпуклом случае без дополнительных предположений оценки для градиентного спуска крайне и крайне пессимистичные: чтобы достичь качества , то есть

достаточно сделать шагов, где — это расстояние от до . Выглядит очень плохо: ведь чтобы достичь точности , необходимо сделать порядка шагов градиентного спуска. Но на практике такого не происходит, потому что на самом деле верны разные предположения, дающие более приятные свойства. Для контраста, укажем оценку в случае гладкой и сильно выпуклой в точке оптимума функции: за шагов будет достигнута точность

где – это так называемое число обусловленности задачи. По сути, это число измеряет, насколько линии уровня функции вытянуты в окрестности оптимума.

Морали две:

  • Скорость сходимости градиентного спуска сильно зависит от обусловленности задачи;
  • Также она зависит от выбора хорошей точки старта, ведь везде входит расстояние от точки старта до оптимума.

В качестве ссылки на доказательство укажем на работу Себастиана Стича, где оно довольно простое и общее.

В невыпуклом же случае все куда хуже с точки зрения теории: требуется порядка шагов в худшем случае даже для гладкой функции, где – желаемая точность уменьшения нормы градиента.

Стохастический градиентный спуск (SGD)

Теперь мы попробуем сэкономить в случае регрессии и подобных ей задач. Будем рассматривать функционалы вида

где сумма проходится по всем объектам выборки (которых может быть очень много).
Теперь сделаем следующий трюк: заметим, что это усреднение – это по сути взятие матожидания. Таким образом, мы говорим, что наша функция выглядит как

где равномерно распределена по обучающей выборке. Задачи такого вида возникают не только в машинном обучении; иногда встречаются и просто задачи стохастического программирования, где происходит минимизация матожидания по неизвестному (или слишком сложному) распределению.

Для функционалов такого вида мы также можем посчитать градиент, он будет выглядеть довольно ожидаемо:

Будем считать, что вычисление матожидания напрямую невозможно.

Новый взгляд из статистики дает возможность воспользоваться классическим трюком: давайте подменим матожидание на его несмещенную Монте-Карло оценку. Получается то, что можно назвать стохастическим градиентом:

Говоря инженерным языком, мы подменили вычисление градиента по всей выборке вычислением по случайной подвыборке. Подвыборку часто называют (мини)батчем, а число – размером батча.

По-хорошему, наука предписывает нам каждый раз независимо генерировать батчи, но это трудно с вычислительной точки зрения. Вместо этого воспользуемся следующим приёмом: сначала перемешаем нашу выборку (чтобы внести дополнительную случайность), а затем будем рассматривать последовательно блоки по элементов выборки. Когда мы просмотрели всю выборку – перемешиваем еще раз и повторяем проход. Очередной прогон по обучающей выборке называется эпохой. И, хотя, казалось бы, независимо генерировать батчи лучше, чем перемешивать лишь между эпохами, есть несколько результатов, демонстрирующих обратное: одна работа и вторая (более новая); главное условие успеха – правильно изменяющийся размер шага.

Получаем следующий алгоритм, называемый стохастическим градиентным спуском (stochastic gradient descent, SGD):

x = normal(0, 1)                    # инициализация
repeat E times:                     # цикл по количеству эпох
   for i = 0; i <= N; i += B:
        batch = data[i:i+B]
        h = grad_loss(batch).mean() # вычисляем оценку градиента как среднее по батчу
        x -= alpha * h

Дополнительное удобство такого подхода – возможность работы с внешней памятью, ведь выборка может быть настолько большой, что она помещается только на жёсткий диск. Сразу отметим, что в таком случае стоит выбирать достаточно большим: обращение к данным с диска всегда медленнее, чем к данным из оперативной памяти, так что лучше бы сразу забирать оттуда побольше.

Поскольку стохастические градиенты являются лишь оценками истинных градиентов, SGD может быть довольно шумным:

21

Поэтому если вы обучаете глубокую нейросеть и у вас в память влезает лишь батч размером с 2-4 картинки, модель, возможно, ничего хорошего не сможет выучить. Аппроксимация градиента и поведение SGD может стать лучше с ростом размера батча – и обычно его действительно хочется подрастить, но парадоксальным образом слишком большие батчи могут порой испортить дело (об этом дальше в этом параграфе!).

Теоретический анализ

Теперь перейдем к теоретической стороне вопроса. Сходимость SGD обеспечивается несмещенностью стохастического градиента. Несмотря на то, что во время итераций копится шум, суммарно он зачастую оказывается довольно мал.

Теперь приведем оценки. Сначала, по традиции, в выпуклом случае. Для выпуклой функции потерь за шагов будет достигнута точность порядка

где – это дисперсия стохградиента, а – константа сильной выпуклости, показывающая, насколько функция является «не плоской» в окрестности точки оптимума. Доказательство в том же препринте С. Стича.

Мораль в следующем: дисперсия стохастического градиента, вычисленного по батчу размера равна , где – это дисперсия одного градиента. То есть увеличение размера батча помогает и с теоретической точки зрения.

В невыпуклом случае оценка сходимости SGD просто катастрофически плохая: требуется шагов для того, чтобы сделать норму градиента меньше . В теории есть всевозможные дополнительные способы снижения дисперсии с лучшими теоретическими оценками (Stochastic Variance Reduced Gradient (SVRGD), Spider, etc), но на практике они активно не используются.

Использование дополнительной информации о функции

Методы второго порядка

Основной раздел.

Постараемся усовершенствовать метод стохастического градиентного спуска. Сначала заметим, что мы используем явно не всю информацию об оптимизируемой функции.

Вернемся к нашему VIP-примеру линейной регресии с регуляризацией:

Эта функция достаточно гладкая, и может быть неплохой идеей использовать её старшие производные для ускорения сходимости алгоритма. В наиболее чистом виде этой философии следует метод Ньютона и подобные ему; о них вы можете прочитать в соответствующем разделе. Отметим, что все такие методы, как правило, довольно дорогие (исключая L-BFGS), и при большом размере задачи и выборки ничего лучше вариаций SGD не придумали.

Проксимальные методы

Основной раздел.

К сожалению, не всегда функции такие красивые и гладкие. Для примера рассмотрим Lasso-регресию:

Второе, не гладкое слагаемое резко ломает все свойства этой задачи: теоретически оценки для градиентного спуска становятся гораздо хуже (и на практике тоже). С другой стороны, регуляризационное слагаемое устроено очень просто, и эту дополнительную структурную особенность можно и нужно эксплуатировать. Методы решения задачи вида

где – простая функция (в некотором смысле), а – гладкая, называются методами композитной оптимизации. Глубже погрузиться в них можно в соответствующем разделе, посвященном проксимальным методам.

Использование информации о предыдущих шагах

Следующая претензия к методу градиентного спуска – мы не используем информацию о предыдущих шагах, хотя, кажется, там может храниться что-то полезное.

Метод инерции, momentum

Начнем с физической аналогии. Представим себе мячик, который катится с горы. В данном случае гора – это график функции потерь в пространстве параметров нашей модели, а мячик – её текущее значение. Реальный мячик не застрянет перед небольшой кочкой, так как у него есть некоторая масса и уже накопленный импульс – некоторое время он способен двигаться даже вверх по склону. Аналогичный прием может быть использован и в градиентной оптимизации. В англоязычной литературе он называется Momentum.

21

С математической точки зрения, мы добавляем к градиентному шагу еще одно слагаемое:

Сразу заметим, что мы немного усугубили ситуацию с подбором шага, ведь теперь нужно подбирать не только , но и . Для обычного, не стохастического градиентного спуска мы можем адаптировать метод наискорейшего и получить метод тяжелого шарика:

Но, увы, для SGD это работать не будет.

Выгода в невыпуклом случае от метода инерции довольно понятна – мы будем пропускать паразитные локальные минимумы и седла и продолжать движение вниз. Но выгода есть также и в выпуклом случае. Рассмотрим плохо обусловленную квадратичную задачу, для которой линии уровня оптимизируемой функции будут очень вытянутыми эллипсами, и запустим на SGD с инерционным слагаемым и без него. Направление градиента будет иметь существенную вертикальную компоненту, а добавление инерции как раз «погасит» паразитное направление. Получаем следующую картинку:

21

Также удобно бывает представить метод моментума в виде двух параллельных итерационных процессов:

Accelerated Gradient Descent (Nesterov Momentum)

Рассмотрим некоторую дополнительную модификацию, которая была предложена в качестве оптимального метода первого порядка для решения выпуклых оптимизационных задач.

Можно доказать, что в сильно выпуклом и гладком случае найти минимум с точностью нельзя быстрее, чем за

итераций, где – число обусловленности задачи. Напомним, что для обычного градиентного спуска в экспоненте у нас был не корень из , а просто , то есть, градиентный спуск справляется с плохой обусловленностью задачи хуже, чем мог бы.

В 1983 году Ю.Нестеровым был предложен алгоритм, имеющий оптимальную по порядку оценку. Для этого модифицируем немного моментум и будем считать градиент не в текущей точке, а как бы в точке, в которую мы бы пошли, следуя импульсу:

Сравним с обычным momentum:

21

Комментарий: иногда упоминается, что Nesterov Momentum «заглядывает в будущее» и исправляет ошибки на данном шаге оптимизации. Конечно, никто не заглядывает в будущее в буквальном смысле.

В работе Нестерова были предложены конкретные (и довольно магические) константы для импульса, которые получаются из некоторой еще более магической последовательности. Мы приводить их не будем, поскольку мы в первую очередь заинтересованы невыпуклым случаем.

Nesterov Momentum позволяет значительно повысить устойчивость и скорость сходимости в некоторых случаях. Но, конечно, он не является серебряной пулей в задачах оптимизации, хотя в выпуклом мире и является теоретически неулучшаемым.

Также отметим, что ускоренный метод может напрямую примениться к проксимальному градиентному спуску. В частности, применение ускоренного метода к проксимальному алгоритму решения регрессии (ISTA) называется FISTA (Fast ISTA).

Общие выводы:

  • Добавление momentum к градиентному спуску позволяет повысить его устойчивость и избегать маленьких локальных минимумов/максимумов;
  • В выпуклом случае добавление моментного слагаемого позволяет доказуемо улучшить асимптотику и уменьшить зависимость от плохой обусловленности задачи.
  • Идея ускорения применяется к любым около-градиентным методам, в том числе и к проксимальным, позволяя получить, например, ускоренный метод для -регрессии.

Адаптивный подбор размера шага

Выше мы попытались эксплуатировать свойства градиентного спуска. Теперь же пришел момент взяться за больной вопрос: как подбирать размер шага? Он максимально остро встаёт в случае SGD: ведь посчитать значение функции потерь в точке очень дорого, так что методы в духе наискорейшего спуска нам не помогут!

Нужно действовать несколько хитрее.

Adagrad

Рассмотрим первый алгоритм, который является адаптацией стохастического градиентного спуска. Впервые он предложен в статье в JMLR 2011 года, но она написана в очень широкой общности, так что читать её достаточно сложно.

Зафиксируем – исходный learning rate. Затем напишем следующую формулу обновления:

Возведение в квадрат и деления векторов покомпонентные. По сути, мы добавляем некоторую квазиньютоновость и начинаем динамически подбирать размер шага для каждой координаты по отдельности. Наш размера шага для фиксированной координаты – это какая-то изначальная константа (learning rate), деленная на корень из суммы квадратов координат градиентов плюс дополнительный параметр сглаживания , предотвращающий деление на ноль. Добавка на практике оставляется дефолтными 1e-8 и не изменяется.

Идея следующая: если мы вышли на плато по какой-то координате и соответствующая компонента градиента начала затухать, то нам нельзя уменьшать размер шага слишком сильно, поскольку мы рискуем на этом плато остаться, но в то же время уменьшать надо, потому что это плато может содержать оптимум. Если же градиент долгое время довольно большой, то это может быть знаком, что нам нужно уменьшить размер шага, чтобы не пропустить оптимум. Поэтому мы стараемся компенсировать слишком большие или слишком маленькие координаты градиента.

Но довольно часто получается так, что размер шага уменьшается слишком быстро и для решения этой проблемы придумали другой алгоритм.

RMSProp

Модифицируем слегка предыдущую идею: будем не просто складывать нормы градиентов, а усреднять их в скользящем режиме:

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

Общие выводы:

  • Благодаря адаптивному подбору шага в современных оптимизаторах не нужно подбирать последовательность размеров всех шагов, а достаточно выбрать всего одно число – learning rate , всё остальное сделает за вас сам алгоритм. Но learning rate все еще нужно выбирать крайне аккуратно: алгоритм может либо преждевременно выйти на плато, либо вовсе разойтись. Пример приведен на иллюстрации ниже.

21

Объединяем все вместе...

Adam

Теперь покажем гвоздь нашей программы: алгоритм Adam, который считается решением по умолчанию и практически серебряной пулей в задачах стохастической оптимизации.

Название Adam = ADAptive Momentum намекает на то, что мы объединим идеи двух последних разделов в один алгоритм. Приведем его алгоритм, он будет немного отличаться от оригинальной статьи отсутствием коррекций смещения (bias correction), но идея останется той же самой:

Как правило, в этом алгоритме подбирают лишь один гиперпараметр – learning rate. Остальные же: , и – оставляют стандартными и равными 0.9, 0.99 и 1e-8 соответственно. Подбор составляет главное искусство.

Зачастую, при начале работы с реальными данными начинают со значения learning rate равного 3e-4. История данного значения достаточно забавна: в 2016 году Андрей Карпатый (Andrej Karpathy) опубликовал шутливый пост в Twitter.

21

После чего сообщество подхватило эту идею (до такой степени, что иногда число 3e-4 называют Karpathy constant).

Обращаем ваше внимание, что при работе с учебными данными зачастую полезно выбирать более высокий (на 1-2 порядка) начальный learning rate (например, при классификации MNIST, Fashion MNIST, CIFAR или при обучении языковой модели на примере поэзии выбранного поэта).

Также стоит помнить, что Adam требует хранения как параметров модели, так и градиентов, накопленного импульса и нормировочных констант (cache). Т.е. достижение более быстрой (с точки зрения количества итераций/объема рассмотренных данных) сходимости требует больших объемов памяти. Кроме того, если вы решите продолжить обучение модели, остановленное на некоторой точке, необходимо восстановить из чекпоинта не только веса модели, но и накопленные параметры Adam. В противном случае оптимизатор начнёт сбор всех своих статистик с нуля, что может сильно сказаться на качестве дообучения. То же самое касается вообще всех описанных выше методов, так как каждый из них накапливает какие-то статистики во время обучения.

Интересный факт: Adam расходится на одномерном контрпримере, что совершенно не мешает использовать его для обучения нейронных сетей. Этот факт отлично демонстрирует, насколько расходятся теория и практика в машинном обучении. В той же работе предложено исправление этого недоразумения, но его активно не применяют и продолжают пользоваться «неправильным» Adamом потому что он быстрее сходится на практике.

AdamW

А теперь давайте добавим -регуляризацию неявным образом, напрямую в оптимизатор и минуя адаптивный размер шага:

Это сделано для того, чтобы эффект -регуляризации не затухал со временем и обобщающая способность модели была выше. Оставим ссылку на одну заметку про этот эффект. Отметим, впрочем, что этот алгоритм особо не используется.

Практические аспекты

Расписания

Часто learning rate понижают итеративно: каждые условные 5 эпох (LRScheduler в Pytorch) или же при выходе функции потерь на плато. При этом лосс нередко ведет себя следующим схематичным образом:

21

Помимо этого используют другие варианты «расписаний» для learning rate. Из часто применяемых неочевидных лайфхаков: сначала сделать warmup, то есть увеличивать learning rate, а затем начать постепенно понижать. Использовалось в известной статье про трансформеры. В ней предложили следующую формулу:

По сути, первые шагов происходит линейный рост размера шага, а затем он начинает уменьшаться как , где — число итераций.

Есть и вариант с косинусом из отдельной библиотеки для трансформеров.

21

В этой же библиотеке можно также почерпнуть идею рестартов: с какого-то момента мы снова включаем warmup, увеличивая размер шага.

Большие батчи

Представим ситуацию, что мы хотим обучить свою нейронную сеть на нескольких GPU. Одно из решений выглядит следующим образом: загружаем на каждую видеокарту нейронную сеть и свой отдельный батч, вычисляем стохастические градиенты, а затем усредняем их по всем видеокартам и делаем шаг. Что плохого может быть в этом?

По факту, эта схема в некотором смысле эквивалентна работе с одним очень большим батчем. Хорошо же, нет разве?

На самом деле существует так называемый generalization gap: использование большого размера батча может приводить к худшей обобщающей способности итоговой модели. О причине этого эффекта можно поспекулировать, базируясь на текущих знаниях о ландшафтах функций потерь при обучении нейронных сетей.

Больший размер батча приводит к тому, что оптимизатор лучше «видит» ландшафт функции потерь для конкретной выборки и может скатиться в маленькие «узкие» паразитные локальные минимумы, которые не имеют обобщающий способности — при небольшом шевелении этого ландшафта (distributional shift c тренировочной на тестовую выборку) значение функции потерь резко подскакивает. В свою очередь, широкие локальные минимумы дают модель с лучшей обобщающей способностью. Эту идею можно увидеть на следующей картинке:

21

Иными словами, большие батчи могут приводить к переобучению, но это можно исправить правильным динамическим подбором learning rate, как будет продемонстрировано далее. Сразу отметим, что совсем маленькие батчи – это тоже плохо, с ними ничего не получится выучить, так как каждая итерация SGD знает слишком мало о ландшафте функции потерь.

LARS

Мы рассмотрим нестандартный оптимизатор для обучения нейронных сетей, которого нет в Pytorch по умолчанию, но который много где используется: Layer-wise Adaptive Rate Scaling (LARS). Он позволяет эффективно использовать большие размеры батчей, что очень важно при вычислении на нескольких GPU.

Основная идея заключена в названии – нужно подбирать размер шага не один для всей сети или каждого нейрона, а отдельный для каждого слоя по правилу, похожему на RMSProp. По сравнению с оригинальным RMSProp подбор learning rate для каждого слоя дает большую стабильность обучения.

Теперь рассмотрим формулу пересчета: пусть – это веса слоя , . Параметры алгоритма: базовый learning rate (на который запускается расписание), коэффициент инерции , коэффециент затухания весов (как в AdamW).

for l in range(L):                                              # Цикл по слоям
    g_l = stochgrad(w_prev)[l]                                  # Вычисляем стохградиент из батча для текущего слоя
    lr = eta * norm(w[l]) / (norm(g_l) + beta * norm(w[l]))     # Вычислеяем learning rate для текущего слоя
    v[l] = m * v[l] + lr * (g_l + beta * w[l])                  # Обновляем momentum
    w[l] -= v[l]                                                # Делаем градиентный шаг по всему слою сразу
w_prev = w                                                      # Обновляем веса

LAMB

Этот оптимизатор введен в статье Large Batch Optimization For Deep Learning и является идейным продолжателем LARS, более приближенным к Adam, чем к обычному RMSProp. Его параметры – это параметры Adam , которые берутся как в Adam, а также параметр , который отвечает за затухание весов ( в LARS).

for l in range(L):                                              # Цикл по слоям
    g_l = stochgrad(w_prev)[l]                                  # Вычисляем стохградиент из батча для текущего слоя
    m[l] = beta_1 * m[l] + (1 - beta_1) * g_l                   # Вычисляем моментум
    v[l] = beta_2 * v[l] + (1 - beta_2) * g_l                   # Вычисляем новый размер шага
    m[l] /= (1 - beta_1**t)                                     # Шаг для уменьшения смещения из Adam
    v[l] /= (1 - beta_2**t)
    r[l] = m[l] / sqrt(v[l] + eps)                              # Нормируем моментум как предписывает Adam
    lr = eta * norm(w[l]) / norm(r[l] + llambda * w[l])         # Как в LARS
    w[l] = w[l] - lr *  (r[l] + llambda * w[l])                 # Делаем шаг по моментуму
w_prev = w                                                      # Обновляем веса

Усреднение

Теперь снова заглянем в теорию: на самом деле, все хорошие теоретические оценки для SGD проявляются, когда берётся усреднение по точкам.

Этот эффект при обучении нейронных сетей был исследован в статье про алгоритм SWA. Суть очень проста: давайте усреднять веса модели по каждой -й итерации; можно считать, что по эпохам. В итоге, веса финальной модели являются усреднением весов моделей, имевших место в конце каждой эпохи.

В результате такого усреднения сильно повышается обобщающая способность модели: мы чаще попадаем в те самые широкие локальные минимумы, о которых мы говорили в разделе про большие батчи. Вдохновляющая картинка из статьи прилагается:

21

На второй и третьей картинке изображено сравнение SGD и SWA при обучении нейронной сети (Preactivation ResNet-164 on CIFAR-100) при одной и той же инициализации.

На первой же картинке изображено, как идеологически должен работать SWA. Также мы видим тут демонстрацию эффекта концентрации меры: после обучения стохастический градиентный спуск становится случайным блужданием по области в окрестности локального минимума. Если, например, предположить, что итоговая точка – это нормальное распределение с центром в реальном минимуме в размерности , то все эти точки с большой вероятности будут находиться в окрестности сферы радиуса . Интуитивную демонстрацию многомерного нормального распределения можно увидеть на следующей картинке из книги Р.Вершинина "High-Dimensional Probability" (слева в размерности 2, справа в большой размерности):

21

Поэтому, чтобы вычислить центральную точку этой гауссианы, усреднение просто необходимо, по такому же принципу работает и SWA.

Предобуславливание

Теперь мы снова обратимся к теории: скорость сходимости градиентного спуска (даже ускоренного) очень сильно зависит от числа обусловленности задачи. Разумной идеей будет попытаться использовать какие-то сведения о задаче и улучшить этот показатель, тем самым ускорив сходимость.

В теории, здесь могут помочь техники предобуславливания. Но, к сожалению, попытки наивно воплотить эту идею приводят к чему-то, похожему на метод Ньютона, в котором нужно хранить большую-большую матрицу для обучения больших моделей. Способ обойти эту проблему рассмотрели в статье о методе Shampoo, который использует то, что веса нейронной сети зачастую удобно представлять как матрицу или даже многомерный тензор. Таким образом, Shampoo можно рассматривать как многомерный аналог AdaGrad.

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф13.6. Implicit bias
Следующий параграф14.2. Проксимальные методы

Как оптимизировать функции потерь с $L_1$-регуляризацией