Градиентный спуск: просто о сложном
Одним из самых часто используемых алгоритмов в машинном обучении является градиентный спуск, он применяется практически в каждой модели обучения. По своей сути, градиентный спуск это и есть то, как обучается любая модель машинного обучения. Метод градиентного спуска (с некоторой модификацией) широко используется для обучения глубоких нейронных сетей, и известен как метод обратного распространения ошибки.
В данной статье мы поставим все точки над i в вопросе о градиентном спуске.
Что такое градиент
Начнем с самых базовых понятий о производных. По сути производная есть тангенс угла наклона касательной (да, она как бы касается графика) к графику в точке, в которой вы ее вычисляете. А поскольку производная в точках минимума (и максимума тоже) всегда обращается в нуль (касательная просто становится параллельна оси ОХ), этот принципе часто используют в задачах оптимизации, или поиска минимального значения. На картинке ниже приведен очень простой пример графика, назовем его графиком функции потерь (или loss function, не будем подробно останавливаться на этом сейчас, это не столь важно). Здесь функция f(x)
имеет минимум в точке x = 0
, а касательные, построенные в точках x0 = 0, 0.4, 0.6
, указывают направление, в котором стоит двигаться, если нужно отыскать минимум (ну или просто наклон функции, чем он меньше, тем мы ближе к локальному минимуму! Как мы видим, касательная функции в точке x = 0
проходит точно параллельно оси ОХ, что и подтверждает нашу первоначальную гипотезу о минимуме в этой точке.
Возможно вы уже были знакомы с понятием производной, тогда в принципе градиент не будет чем то новым для вас.
Если говорить очень упрощенно, то градиент в одномерном случае и есть та самая производная от функции, о которой мы рассуждали выше. Но вообще говоря производная не имеет направления. А градиент должен его иметь, потому что градиент – это вектор. В данном случае все очень просто: направление градиента совпадает с направлением движения касательных, влево или вправо по оси ОХ.
Кстати, градиент какой-либо функции f в математике обычно обозначают таким забавным значком перевернутого треугольника – ∇f.
Разумеется, в реальных задачах все чуточку сложнее, и функции будут зависеть не от одной переменной, а например от двух (или более), как эта двумерная поверхность, по сути являющаяся трехмерной параболой.
Но основная идея останется та же самая – для того, чтобы отыскать минимум, нужно двигаться в сторону наискорейшего убывания функции (там где наклон становится очень маленьким), а значит, нужно уметь работать с градиентом.
Что такое градиентный спуск
Градиентный спуск – это метод нахождения минимального значения функции многих переменных. Такие методы еще иногда называют оптимизацией. Минимизация любой такой функции означает поиск самой глубокой впадины. Например, как видно на графике выше, если начать "скатываться" по стенкам этой параболы, то мы неизбежно достигнем локального минимума в точке 0.
А поиск минимума в контексте машинного обучения всегда означает попытку получения наименьшей возможной ошибки ну или повышение точности модели. Точность можно увеличить, перебирая набор учебных данных при настройке конкретных параметров модели (весов и смещений).
Как же все таки это работает? Известно, что градиент является направлением наискорейшего роста функции, а градиент со знаком минус (назовем его "антиградиент") – направлением наискорейшего убывания. Это ключевое свойство градиента, обосновывающее его использование в методах оптимизации.
Поскольку основное свойство антиградиента состоит в том, что он указывает в сторону наискорейшего убывания функции, будет логично стартовать из некоторой точки (назовем ее точкой начального значения, как на рисунке выше), сдвинуться в сторону "антиградиента", то есть градиента, но со знаком минус. Далее пересчитать этот самый антиградиент и снова сдвинуться в его сторону и так далее
Давайте запишем это более формально. Тогда градиентный спуск состоит в повторении следующих шагов. Не волнуйтесь, далее мы разберем их в коде на Python.
Суть каждого такого алгоритма – процесс получения наименьшего значения ошибки.
Перейдем к практике
Рассмотрим реализацию самого простейшего случая применения градиентного спуска, а именно, поиск минимума заданной функции. Как и было сказано выше, в одномерном случае, градиент функции (в проекции на выбранную ось) представляет собой обычную одномерную производную.
Для интереса возьмем функцию
с производной
Вы можете взять любую другую функцию, на ваш выбор, и проверить работу алгоритма.
Теперь выберем начальную точку x0 = 6
(начальная точка может быть практически любой, за исключением концов отрезка, если мы рассматриваем функцию на некотором отрезке).
Если непонятно, попробуйте выбрать несколько различных значений начальной точки, и проверить результат.
После этого необходимо выбрать размер шага (или γ, как это было обозначено выше) с которым мы будем "скатываться" в ямку минимума. Если выбрать слишком большой шаг, можно проскочить минимум, а если слишком маленький, то в случае сложной функции с несколькими минимумами, можно оказаться в локальном минимуме вместо глобального.
Мы выберем gamma = 0.01
.
Попробуйте поиграть с этими двумя параметрами.
Теперь попробуем реализовать все что мы запланировали в функции get_minumum
.
Ограничим число итераций max_iters
значением 10000, чтобы в случае неудачно подобранных значений gamma
и x0
, нам не пришлось выполнять лишние шаги и ждать сходимости слишком долго.
Запишем производную df
в виде лямбда функции (удобный концепт, позволяющий избавиться от тела функции), и передадим ее в функцию get_minimum
вместе с начальным значением x0
и шагом gamma
.
Ого, вау. Мы получили искомое значение. Остается только отметить, что в реальности есть несколько различных видов градиентного спуска, например стохастический спуск, или даже более хитрые вещи, позволяющие отыскивать глобальные минимумы более эффективно, избегая мелких ямок.
Статья подготовлена образовательной организацией Python Academy