А что вообще творится в нейросетях? Напомните!
Ранее мы рассказывали, как «сырые» данные (картинка, видео или текст) разбиваются на мелкие кусочки и подаются на каждый входной нейрон. Дальше входные нейроны анализируют что-то очень простое — есть ли в их кусочке картинки 10 белых пикселей, есть ли буква «Т» в слове — и если да, нейрон «активируется», то есть передает свой сигнал в следующий слой с определенной силой. Сигналы комбинируются, потом собираются в абстрактные комбинации десятков комбинаций сигналов, и в конце концов активируют один из нейронов, который классифицирует картинку как котика или хлеб.
А как активируется нейрон?
Нейрон — это функция, которая получает на вход числа (обычно много), что-то с ними делает и возвращает одно число (обычно от 0 до 1). Функция, из которой нейрон состоит, называется функцией активации, или передаточной функцией. Именно она отвечает за то, будет ли передан сигнал нейрона дальше (1 на выходе функции — если будет, 0 — если нет). В самом простом виде функция активации может быть пороговой:
В данном случае, если какие-то значения на входе нейрона дали положительную сумму — нейрон активировался. Порог активации здесь выглядит как вертикальная палочка, простой переключатель вкл/выкл, а значит, нейрон может быть либо активен, либо нет — как лампочка. Есть и более сложные функции, которые могут изменять силу выходного сигнала, могут «чуть-чуть активировать» нейрон, или «активировать его посильнее»:
Выбор функции активации нейронов влияет на то, какие задачи они могут решать, и их подбор — задача создателя нейросети. Если создатель понимает, какие функции для чего подходят, у него больше шансов сделать крутую нейросеть, которая решит его проблему.
Вот главное: нейрон — это функция. Чем больше ее значение, тем важнее сигнал нейрона для всей сети.
Почему это важно в контексте обучения нейросети?
Коротко: нейроны постоянно дают слишком сильный или слабый сигнал и сбивают предсказание, их нужно настраивать.
Нейроны глупые! Без ручной настройки силы их выходных сигналов нейросеть даёт случайные предсказания. К выходному значению нейрона можно (и нужно) прибавлять некоторый вес. Вес прибавляется или отнимается в зависимости от того, насколько сильно в прошлый раз нейросеть ошиблась с предсказанием. Этот этап работы, когда нейроны штрафуются за все прошлые «косяки», называется обратным распространением ошибки. Какие-то нейроны активируются слишком сильно, какие-то — слабо, но все равно они уводят предсказание нейросети в сторону от правильного. Поскольку нейронов слишком много, и за какие конкретно признаки каждый из них отвечает — неясно, настроить каждый вес вручную невозможно. Люди придумали способ настраивать веса автоматически.
Для этого существует функция потерь. Что она делает?
Коротко: ее значение возрастает, если нейросеть сильно ошибается. Значит, надо найти такое решение, где функция возрастать не будет.
Есть функция, возрастающая при сильной ошибке предсказания. Чем больше ошибка, тем сильнее уменьшаются (штрафуются) веса активированных нейронов. Человеку нужно свести меру ошибки к минимуму, научить нейросеть делать верные предсказания о том, котик на картинке или булочка. Если функция потерь дает высокие значения при большой ошибке, мы просто ищем такие веса, при которых она дает минимальные значения.
Кстати, функций потерь тоже существует много, они по-разному подходят для разных задач, и крутые программисты за 300к/сек знают, как в них сориентироваться. Функции потерь объединяет то, что они возрастают вместе с мерой ошибки предсказания. Она вычисляется просто: из числа X (предсказанного) вычитаем число Y (то, что должно было предсказаться) — получаем число Err. Оно становится одним из аргументов функции потерь.
Здесь надо вспомнить производные
Задача подбора весов нейронной сети также называется тренировкой или задачей оптимизации. (Ведь optimus — наилучший, а значит, мы и ищем лучшие веса?) Веса нам нужны те, при которых функция потерь принимает минимальное значение. Когда мы знаем это значение, рассчитать нужные веса становится легко — все равно, что решить уравнение.
А значит, желаемая схема обучения нейросети выглядит примерно так: Функция потерь принимает минимальное значение → находим соответствующие этому значению веса → ошибка минимальна → предсказание нейросети точно → вы великолепны.
Пора рассказать, что такое градиент
Если для функции многих переменных по очереди рассчитать частные производные для каждой из переменных (и записать эти производные в ряд, и взять в скобочки), получится вектор, называемый градиентом. Так как одна переменная соответствует одной координатной оси, каждый элемент вектора показывает скорость изменения функции вдоль своей оси, а все вместе они показывают направление, в котором быстрее всего возрастает функция в целом.
Вот главное: градиент — это вектор, показывающий направление, в котором функция многих переменных быстрее всего возрастает или падает. «Градиент функции f» записывается так: ∇f (можно читать как «набла f»).
для переменных x, y, z?Подсказка: должен получиться вектор с тремя элементами.
Сложность в том, чтобы уследить, какие из «констант» — множители переменной, что рассматриваются в данный момент, а какие — отбрасываются как слагаемые.
Подробнее о градиентах
Кроме функции потерь градиентный спуск также требует градиент, который является dJ/dw (производная функции потерь относительно одного веса, выполненная для всех весов). dJ/dw зависит от вашего выбора функции потерь. Наиболее распространена функция потерь среднеквадратичной ошибки.
Производная этой функции относительно любого веса (эта формула показывает вычисление градиента для линейной регрессии):
Объясните наконец, что такое градиентный спуск!
Градиентный спуск — это эвристический алгоритм, который выбирает случайную точку, рассчитывает направление скорейшего убывания функции (пользуясь градиентом функции в данной точке), а затем пошагово рассчитывает новые значения функции, двигаясь в выбранную сторону. Если убывание значения функции становится слишком медленным, алгоритм останавливается и говорит, что нашел минимум.
Стоит помнить, что говорим не о двумерных функциях. Представить, что происходит, можно по этой картинке:
Градиентный спуск выбирает случайную точку, находит направление самого быстрого убывания функции и двигается до ближайшего минимума вдоль этого направления. Кстати, размер одного шага можно настроить, это бывает очень важно. Если на каком-то этапе разность между старой точкой (до шага) и новой снижается ниже предела, считается, что минимум найден, алгоритм завершен. Можно вообразить работу градиентного спуска как игру в «холодно-горячо» до тех пор, пока степень «потепления» не станет пренебрежительно малой.
- Градиентный спуск выбирает случайную точку старая_точка, принадлежащую функции
- Новая_точка рассчитывается как старая_точка минус (размер_шага * градиент_в_старой_точке)
- ЕСЛИ модуль (новая_точка минус старая_точка) < порогового_пределаТО старая_точка = минимум функцииИНАЧЕ — повторить алгоритм для новая_точка
А почему это вообще должно работать?
Градиентный спуск ищет ближайшую к случайно выбранной точке впадину на графике функции. А поскольку в нейросетях функции очень сложные и локальных впадин-минимумов на них много, такой подход должен быть неэффективен в вопросах обучения нейросети и всегда натыкаться на локальные минимумы.
Тем не менее градиентный спуск как метод обучения почему-то работает хорошо. В 2015 группа ученых из Курантовского института математических наук в Нью-Йорке нашла этому объяснение, показав, что большая часть локальных минимумов функций потерь, используемых в нейросетях, располагается близко к глобальному минимуму. Эта близость и позволяет натренированным при помощи градиентного спуска нейросетям справляться с задачами достаточно эффективно.
Надеемся, что теперь вы разобрались с тем, что значит «подобрать веса» или «обучить нейросеть», а также с тем, на что уходит такое большое количество времени и ресурсов при тренировке (спойлер: на повторяющуюся монотонную подстановку переменных в градиент функции и на пошаговую коррекцию весов нейросети после предсказания).
А зачем настраивать размер шага?
Размер шага алгоритма определяет, насколько мы собираемся двигать точку на функции потерь, и этот параметр называется «скоростью обучения». Слегка запутывающее название, поскольку не всегда высокая скорость обучения гарантирует хороший результат. Скорее скорость обучения стоит воспринимать как ширину шагов, с которыми человек с завязанными глазами ищет, где «горячо» или «холодно». В некоторых случаях бывает так, что слишком широкие шаги вообще не позволяют достичь минимума, и машина бесконечно перешагивает через него, затем градиент «разворачивает» ее обратно, и алгоритм снова перескакивает через минимум. Маленькая скорость обучения хоть и придает точности, зато, конечно, увеличивает время на обучение нейросети.
Коэффициент скорости обучения
Всё, о чём написано выше, есть в учебнике. Вы можете открыть любую книгу о градиентном спуске, там будет написано то же самое. Формулы градиентов для каждой функции потерь можно найти в интернете, не зная как вывести их самостоятельно.
Однако проблема у большинства моделей возникает с коэффициентом скорости обучения. Давайте взглянем на обновленное выражение для каждого веса (j лежит в диапазоне от 0 до количества весов, а Theta-j это j-й вес в векторе весов, k лежит в диапазоне от 0 до количества смещений, где B-k — это k-е смещение в векторе смещений). Здесь alpha – коэффициент скорости обучения. Из этого можно сказать, что мы вычисляем dJ/dTheta-j (градиент веса Theta-j), и затем шаг размера alpha в этом направлении. Следовательно, мы спускаемся по градиенту. Чтобы обновить смещение, замените Theta-j на B-k.
Если этот размера шага alpha слишком велик, мы преодолеем минимум: то есть промахнемся мимо минимума. Если alpha слишком мала, мы используем слишком много итераций, чтобы добраться до минимума. Итак, alpha должна быть подходящей.
Использование градиентного спуска
Что ж, вот и всё. Это всё, что нужно знать про градиентный спуск. Давайте подытожим всё в псевдокоде.
Примечание: Весы здесь представлены в векторах. В более крупных моделях они, наверное, будут матрицами.
3. Подобно ситуации с весами, добавьте градиент смещения к аккумулятивной переменной.
Теперь, ПОСЛЕ перебирания всех примеров обучения, выполните следующие действия:
1. Поделите аккумулятивные переменные весов и смещения на количество примеров обучения. Это даст нам средние градиенты для всех весов и средний градиент для смещения. Будем называть их обновленными аккумуляторами (ОА).
2. Затем, используя приведенную ниже формулу, обновите все веса и смещение. Вместо dJ / dTheta-j вы будете подставлять ОА (обновленный аккумулятор) для весов и ОА для смещения. Проделайте то же самое для смещения.
Повторите этот процесс от начала до конца для некоторого количества итераций. Это означает, что для 1-й итерации ГС вы перебираете все примеры обучения, вычисляете градиенты, потом обновляете веса и смещения. Затем вы проделываете это для некоторого количества итераций ГС.
Различные типы градиентного спуска
1. Мini-batch: тут, вместо поочерёдного перебора всех примеров обучения и произведения необходимых вычислений для каждого из них, мы производим вычисления для n примеров обучения сразу. Этот выбор подходит для очень больших наборов данных.
2. Стохастический градиентный спуск: в этом случае вместо перебора и использования всего набора примеров обучения мы применяем подход “используй только один”. Здесь нужно отметить несколько моментов:
- Набор примеров обучения необходимо перемешать перед каждым его проходом в ГС, чтобы перебирать их каждый раз в случайном порядке.
- Поскольку каждый раз используется только один пример обучения, то ваш путь к локальному минимуму будет очень неоптимальным;
- С каждой итерацией ГС вам нужно перемешать набор обучения и выбрать случайный пример обучения;
- Поскольку вы используете только один пример обучения, ваш путь к локальным минимумам будет очень шумным.
3. Серия ГС: это то, о чем написано в предыдущих разделах. Цикл на каждом примере обучения.
Пример кода на python
Применимо к cерии ГС, так будет выглядеть блок учебного кода на Python.
Часто задаваемые вопросы о градиентном спуске
Вопрос: Что такое градиент в машинном обучении?
Ответ: Градиент — это вектор, составленный из частных производных функции потерь по всем параметрам модели. Он показывает направление наискорейшего роста функции.
Вопрос: Почему метод называется «спуском»?
Ответ: Потому что алгоритм движется в направлении, противоположном градиенту (антиградиенту), то есть в сторону наискорейшего уменьшения функции потерь, «спускаясь» в её минимум.
Вопрос: Что такое скорость обучения (learning rate)?
Ответ: Это гиперпараметр, который определяет размер шага на каждой итерации обновления весов. Слишком большое значение может привести к расходимости, слишком маленькое — к очень медленному обучению.
Вопрос: В чём разница между стохастическим, пакетным и полным градиентным спуском?
Ответ: Полный (batch) использует все данные для расчёта градиента, стохастический (SGD) — один случайный пример, а мини-пакетный (mini-batch) — небольшую случайную подвыборку данных. Это компромисс между скоростью и стабильностью.
Вопрос: Что такое локальный минимум и как градиентный спуск с ним справляется?
Ответ: Локальный минимум — это точка, где функция потерь минимальна в небольшой окрестности, но не является глобальным минимумом. Стохастические методы за счёт «шума» в оценке градиента могут выпрыгивать из локальных минимумов.
Вопрос: Что такое момент в градиентном спуске?
Ответ: Момент — это техника, которая добавляет в расчёт обновления параметров инерцию от предыдущих шагов. Это помогает ускорить сходимость и сгладить колебания, особенно в узких оврагах функции потерь.
Вопрос: Всегда ли градиентный спуск находит глобальный минимум?
Ответ: Нет, не всегда. Для невыпуклых функций (как в глубоких нейросетях) гарантированно находится только локальный минимум или седловая точка. На практике часто этого достаточно для хорошего качества модели.
Вопрос: Что такое затухание градиента (vanishing gradient)?
Ответ: Это проблема в глубоких сетях, когда градиенты, распространяемые обратно от выходов ко входам, становятся чрезвычайно малыми. Это мешает обучению ранних слоёв сети.
Вопрос: Как выбрать хорошую скорость обучения?
Ответ: Часто её подбирают экспериментально. Также используются адаптивные методы (Adam, RMSprop) или расписания (schedules), которые автоматически меняют скорость обучения в процессе тренировки.
Вопрос: Можно ли использовать градиентный спуск без функции потерь?
Ответ: Нет, градиентный спуск — это метод оптимизации, которому необходима целевая функция (функция потерь или ошибки), которую нужно минимизировать. Без неё нечего оптимизировать.
Краткая памятка: суть градиентного спуска
- Цель обучения нейросети — найти такие веса, при которых функция потерь (ошибка) минимальна.
- Градиент функции потерь — это вектор, указывающий направление её наискорейшего роста.
- Градиентный спуск двигает веса в направлении, противоположном градиенту (антиградиенту), чтобы уменьшить ошибку.
- Скорость обучения (learning rate) определяет размер шага на каждой итерации обновления.
- Слишком большой шаг может «перепрыгнуть» минимум, слишком маленький — делает обучение очень долгим.
- Полный градиентный спуск (Batch GD) использует все данные для расчёта градиента, что может быть медленно для больших датасетов.
- Стохастический градиентный спуск (SGD) использует один случайный пример, что быстро, но шумно.
- Мини-пакетный градиентный спуск — золотая середина, использует небольшую случайную подвыборку данных.
- Методы с моментом (Momentum, Adam) помогают ускорить сходимость и избежать колебаний.
- Градиентный спуск не гарантирует нахождение глобального минимума для сложных нейросетей.
- Проблема затухания градиента может мешать обучению глубоких сетей.
- Адаптивные оптимизаторы (Adam) автоматически настраивают скорость обучения для каждого параметра.
- Градиенты вычисляются эффективно с помощью алгоритма обратного распространения ошибки (backpropagation).
- Градиентный спуск — это итеративный процесс, который повторяется до сходимости или достижения предела итераций.
- Это фундаментальный метод, лежащий в основе обучения практически всех современных нейронных сетей.




























