Модуль 4. Производные и оптимизация¶
Чему вы научитесь в этом модуле
- Воспринимать градиент как направление наискорейшего роста функции и объяснять, почему оптимизация движется в противоположном направлении.
- Применять цепное правило дифференцирования вручную — ту самую операцию, из которой в модуле 8 будет собрано обратное распространение ошибки (backpropagation).
- Проверять аналитически вычисленный градиент с помощью численного приближения. Это ключевой приём отладки процесса обучения.
- Демонстрировать на графике, что происходит при слишком большом шаге обучения и при слишком маленьком.
- Объяснять зигзагообразное поведение спуска через число обусловленности из модуля 3.
Время: примерно две недели. Пререквизиты: модуль 3.
Ноутбук: открыть в Colab · notebooks/04-derivatives-and-optimisation.ipynb
Зачем нужен этот модуль¶
Линейная регрессия в модуле 3 решилась точно: одно уравнение — и ответ готов. Это редкая удача. У подавляющего большинства задач, которые встретятся дальше в курсе, точного аналитического решения не существует — ни у нейронной сети, ни у политики в обучении с подкреплением, ни у рекомендательной модели.
В таких случаях остаётся один универсальный подход: начать с какого-нибудь (пусть плохого) ответа и постепенно улучшать его маленькими шагами, пока улучшать станет нечего. Всё обучение в этом курсе — это именно он. Различие между конкретными методами обучения сводится лишь к тому, как вычислять направление каждого шага и какой длины его делать.
Производная¶
Производная — это скорость изменения функции. Она отвечает на вопрос: насколько изменится значение функции \(f\), если мы совсем немного увеличим аргумент \(x\).
Определение через предел важно для одного принципиального практического применения: производную всегда можно приблизительно вычислить численно, подставив вместо бесконечно малого предела конкретное маленькое значение \(h\). Такое вычисление будет медленным и не совсем точным, но зато оно не требует аналитического вывода формулы. А значит, это идеальный способ проверить формулу, которую вы вывели самостоятельно.
Этот приём называется gradient check, и в модуле 8 он не раз сэкономит вам целые дни отладки. Суть проста: аналитическая формула и её численное приближение обязаны совпадать с точностью до нескольких значащих цифр. Если совпадения нет — ошибка в выводе формулы, а не в данных.
Из таблицы производных понадобятся следующие:
| \(f(x)\) | \(f'(x)\) |
|---|---|
| \(x^n\) | \(nx^{n-1}\) |
| \(e^x\) | \(e^x\) |
| \(\ln x\) | \(1/x\) |
| \(\sigma(x) = \frac{1}{1+e^{-x}}\) | \(\sigma(x)(1 - \sigma(x))\) |
Последняя строка — это сигмоида. Замечательное свойство: её производная выражается через неё саму. Это одна из причин, по которой сигмоида так долго оставалась излюбленной функцией активации в нейронных сетях.
Градиент¶
Когда функция зависит не от одной переменной, а от нескольких, производную можно взять по каждой переменной в отдельности (зафиксировав остальные). Все такие частные производные, собранные в один вектор, образуют градиент:
У градиента есть одно ключевое свойство, которое и делает его настолько полезным: градиент указывает в направлении наискорейшего роста функции, а его длина (норма) равна скорости этого роста.
Из этого свойства вытекает весь метод оптимизации. Если вы хотите найти минимум функции — двигайтесь в направлении, противоположном градиенту:
Здесь \(\eta\) — это длина шага, также называемая learning rate (скорость обучения). Всё обучение во всём курсе — это эта единственная формула, повторённая миллионы раз.
Почему именно направление наискорейшего роста
Изменение значения функции \(f\) при маленьком шаге \(\delta\) приблизительно равно \(\nabla f \cdot \delta\) — скалярному произведению градиента и вектора шага. Это то самое скалярное произведение из модуля 3. При фиксированной длине шага \(\|\delta\|\) это выражение достигает максимума, когда \(\delta\) сонаправлен с \(\nabla f\) (то есть угол между ними равен нулю). Значит, максимальный рост функции происходит вдоль градиента, а максимальное убывание — в противоположном направлении.
Здесь скалярное произведение из предыдущего модуля работает не как метафора, а как строгое математическое обоснование.
Цепное правило¶
Часто мы имеем дело с функцией, которая является композицией других функций (функцией от функции). Производная такой композиции вычисляется как произведение производных составляющих:
Это самое важное правило дифференцирования во всём курсе. Нейронная сеть по своей сути — это композиция: выход одного слоя подаётся на вход следующему, и так далее. Алгоритм обратного распространения ошибки (backpropagation) — это просто цепное правило, применённое по цепочке слоёв справа налево (от выхода к входу). Ничего сверх этого в нём нет.
Разберём на примере функции потерь
Рассмотрим логистическую регрессию. Линейная комбинация: \(z = w \cdot x\), предсказанная вероятность: \(p = \sigma(z)\), функция потерь (бинарная кросс-энтропия): \(L = -\big(y\ln p + (1-y)\ln(1-p)\big)\).
Нам нужно найти \(\partial L/\partial w\) — градиент потерь по весам. Двигаемся по цепочке справа налево, вычисляя каждое звено:
Перемножаем все три звена по цепному правилу:
Замечательно: всё промежуточное сократилось. Итоговый градиент — это просто ошибка предсказания \((p - y)\), умноженная на входной вектор \(x\). Точно такой же вид имеет градиент у линейной регрессии с квадратичной потерей и у последнего слоя нейросети с softmax. Это не случайное совпадение, а следствие правильно подобранной пары «функция активации + функция потерь».
Градиентный спуск и длина шага¶
Направление движения известно — против градиента. Осталось определить длину шага, и именно от неё зависит, будет ли обучение работать.
Слишком маленький \(\eta\). Спуск сходится к минимуму, но делает это крайне медленно. На достижение результата уходят тысячи шагов там, где при правильном шаге хватило бы десятков. Обидно, но хотя бы не разрушительно.
Слишком большой \(\eta\). Шаг «перелетает» через минимум и попадает в точку, где значение функции потерь выше, чем было до шага. Следующий шаг перелетает ещё сильнее, потому что градиент в точке перелёта стал больше. Значение потерь начинает расти, затем превращается в nan. Это не «модель не обучается» — это арифметика расходящейся числовой последовательности.
Правильный \(\eta\) подбирается экспериментально, и главный инструмент подбора — график функции потерь в зависимости от номера итерации. Потеря должна убывать монотонно и достаточно быстро. Такой график — это не украшение для отчёта, а основной рабочий инструмент.
Первое, что нужно сделать, когда обучение не идёт
Уменьшите шаг обучения в десять раз и запустите заново. В большинстве случаев это одновременно и диагностика, и лечение: если после уменьшения шага обучение пошло — значит, шаг был слишком велик. Если и после уменьшения не пошло — ищите ошибку в вычислении градиента, и ищите её с помощью gradient check.
Овраг: почему одного шага недостаточно¶
Теперь разберёмся, почему на практике минимум не находится за несколько шагов.
Представьте функцию, минимум которой лежит на дне длинного узкого оврага. Градиент в любой точке склона указывает поперёк оврага (в направлении наибольшей крутизны), а не вдоль его дна. В результате градиентный спуск начинает «биться» от одной стенки оврага к другой и продвигается к минимуму лишь очень медленно.
Отношение крутизны стенок к пологости дна — это ровно число обусловленности из модуля 3. Чем оно больше, тем уже овраг и тем сильнее выражен зигзаг траектории.
Отсюда два способа решения проблемы, и оба встретятся далее в курсе.
Нормализация признаков. Приведение всех признаков к одному масштабу превращает вытянутое облако данных в более «круглое», уменьшает число обусловленности и распрямляет траекторию спуска. Вот настоящая причина, по которой данные масштабируют перед обучением. Не «потому что так принято», а потому что без этого спуск идёт зигзагом и сходится в разы медленнее.
Момент (momentum). Каждый шаг дополнительно накапливает инерцию — «помнит» направление предыдущих шагов:
Колебания поперёк оврага при усреднении гасят друг друга (потому что направления чередуются), а движение вдоль дна постепенно накапливается. Всего одна дополнительная строка кода — а сходимость на плохо обусловленных задачах ускоряется в разы.
Стохастический градиентный спуск (SGD)¶
Последний важный элемент. Вычислять градиент по всему набору данных целиком — вычислительно дорого: при миллионе объектов каждый шаг требует миллиона слагаемых.
Стохастический градиентный спуск (SGD) вычисляет градиент не по всем данным, а по случайно выбранному подмножеству — батчу (мини-пакету). Получившаяся оценка градиента оказывается шумной (неточной), зато количество шагов, которые можно сделать в единицу времени, возрастает на порядки.
Шум в данном случае — не только неизбежная цена, но и полезное свойство. Точный градиент (вычисленный по всем данным) может привести алгоритм в первый же попавшийся локальный минимум, из которого он уже не выберется. Шумный градиент способен «вытряхнуть» алгоритм из неглубокого локального минимума и отправить его дальше — к, возможно, более глубокому и лучшему.
Размер батча представляет собой компромисс между точностью оценки градиента и числом шагов в единицу времени. Из модуля 2 нам известен «обменный курс»: шум оценки убывает как \(\sqrt{\text{размер батча}}\). Это значит, что батч, увеличенный вчетверо, даёт вдвое менее шумную оценку градиента — но при этом каждый шаг обходится в четыре раза дороже.
Практическая часть¶
Часть 1. Работа с ноутбуком¶
Откройте notebooks/04-derivatives-and-optimisation.ipynb.
Что содержится внутри:
- Численная производная против аналитической. Экспериментируем с выбором шага \(h\): слишком большой — ошибка аппроксимации (приближение неточное), слишком маленький — ошибка округления (компьютерная арифметика теряет значащие цифры).
- Gradient check на логистической функции потерь. Тот самый приём, который будет незаменим в модуле 8.
- Градиентный спуск на квадратичной функции. Визуализация траектории на линиях уровня.
- Перебор значений шага. Наблюдаем три режима: нормальная сходимость, замедленная сходимость, расходимость до
nan. - Овраг. Та же задача, но с числом обусловленности 50. Зигзагообразную траекторию видно невооружённым глазом.
- Момент на той же задаче. Подсчитываем, во сколько раз уменьшилось необходимое число шагов.
- SGD против полного градиентного спуска на задаче регрессии: сравнение по числу шагов в секунду и гладкости траектории сходимости.
Часть 2. Регрессия градиентным спуском¶
Возьмите задачу регрессии из модуля 3, для которой точный ответ уже известен.
- Решите её градиентным спуском с нуля, без использования
lstsq. - Сравните найденные веса с точным решением. Насколько близко удалось подойти и за сколько шагов.
- Отмасштабируйте признаки (приведите к единичной дисперсии) и повторите спуск. Сколько шагов потребовалось теперь.
- Вычислите число обусловленности до и после масштабирования. Соотнесите изменение числа обусловленности с изменением числа шагов.
Смысл этого упражнения в том, что правильный ответ вам заранее известен — вы его получили аналитически в модуле 3. В дальнейшем в курсе такой роскоши не будет, и ощущение «спуск сошёлся именно туда, куда нужно» стоит получить сейчас.
Задание¶
- Выведите производную сигмоиды из определения \(\sigma(x) = 1/(1 + e^{-x})\). Убедитесь, что результат равен \(\sigma(x)(1-\sigma(x))\).
- Выведите \(\partial L/\partial w\) для линейной регрессии с квадратичной функцией потерь \(L = \frac{1}{2}(y - w \cdot x)^2\). Сравните результат с градиентом логистической регрессии из текста модуля.
- Реализуйте gradient check как отдельную функцию: на вход она принимает функцию, её аналитический градиент и точку, в которой проводится проверка; на выходе возвращает относительное расхождение между аналитическим и численным градиентами. Эта функция пригодится вам в модуле 8.
- Найдите наибольшее значение шага \(\eta\), при котором градиентный спуск на функции \(f(x) = x^2\) всё ещё сходится. Ответ должен быть получен аналитически (точное число), а не подобран эмпирически.
- Сконструируйте задачу оптимизации с числом обусловленности около 1000 и продемонстрируйте, во сколько раз добавление момента сокращает необходимое число шагов для достижения минимума.
Проверка усвоения¶
- Почему градиентный спуск движется в направлении, противоположном градиенту, а не вдоль него?
- Что происходит при слишком большом шаге обучения и как это проявляется на графике функции потерь?
- Сформулируйте цепное правило дифференцирования и объясните, почему без него обучение нейронных сетей было бы невозможно.
- Почему в градиенте логистической регрессии всё промежуточное сокращается и остаётся \((p-y)x\)?
- Что такое gradient check и почему это первое, что следует сделать при подозрении на ошибку в вычислении градиента?
- Как число обусловленности матрицы связано с зигзагообразным поведением градиентного спуска?
- Зачем нормализуют признаки — дайте геометрический ответ, а не «потому что так принято».
- В чём заключается полезная сторона шума в стохастическом градиентном спуске, помимо его очевидного вреда (неточности)?
Что дальше¶
Часть I завершена. Теперь у нас есть полный набор базовых понятий: утверждение и база сравнения, параметр и оценка, вектор и матрица, градиент и шаг обучения. Далее этот язык будет применяться к конкретным задачам.
В части II появятся первые настоящие модели — линейные модели, деревья решений, градиентный бустинг. Они же станут базовыми линиями (эталонами для сравнения) для всего, что последует дальше. Значительную часть заявленных «прорывов» в литературе способен обыграть правильно настроенный бустинг, и увидеть это собственными глазами стоит до того, как начнутся нейронные сети.
Обучение — это одна строка: шаг в направлении, противоположном градиенту. Всё остальное в курсе — про то, как вычислять направление шага и какой длины его делать.