В этой статье мы подробнее рассмотрим градиентный спуск, так как он широко используется в машинном обучении и является настолько общим методом, что может быть полезен в разнообразнейших ситуациях.
Суть в следующем. Пусть у вас есть функция, минимум которой вы хотите найти, и пусть вам нужно найти такие входные данные, при которых функция была бы в минимуме. Как правило, мы хотим минимизировать функцию затрат или ошибок. Может потребоваться также и найти максимумы – например, когда мы ищем максимум для функции правдоподобности некоторого распределения вероятностей. Всё, что нужно сделать – это просто поменять местами стороны. Чтобы объяснить это, рассмотрим очень простой одномерный пример.
Как правило, в машинном обучении мы используем размерности, гораздо большие единицы, но этот пример позволит наглядно увидеть суть.
Итак, пусть у нас есть простая функция.
Мы знаем, что минимум функции при w=0, но, предположим, мы этого не знаем. Наш весовой коэффициент установим случайным образом. Предположим, w=20. Мы знаем, что производная dJ/dw равна 2w.
Установим коэффициент обучения равным 0,1. В первом приближении мы имеем:
w – 0,1*2w = 20 – 0,1*40 = 16.
Поэтому установим w=16. Во втором приближении
w – 0,1*2w = 16 – 0,1*2*16 = 12,8.
Это даёт нам новое значение w=12,8. В третьем приближении
w – 0,1*2w = 12,8 – 0,1*2*12,8 = 10,24.
Как вы можете видеть, на каждом шаге мы всё ближе и ближе к нулю, зато каждый шаг становится меньшим, так как при приближении к нулю наклон становится меньше.
Теперь давайте попробуем реализовать это в коде и посмотрим, сможем ли мы полностью дойти до нуля. Импортируем библиотеку NumPy, установим значение w=20 и будем печатать результат на каждом шаге итераций. Количество приближений установим равным 30.
import numpy as np
w = 20
для i in xrange(30):
w = w – 0.1*2*w
print w
Как видим, w достигает значения 0,02, так что, похоже, 30 приближений недостаточно. Попробуем 100 приближений.
w = 20
for i in xrange(100):
w = w – 0.1*2*w
print w
Теперь результат равен
– это очень близко к нулю.
Надеюсь, вы убедились, что, медленно продвигаясь в направлении градиента функции, мы всё ближе и ближе подходим к минимум этой функции.
Почему этот метод так важен?
По мере продвижения далее в глубокое обучение и машинное обучение функции будут становиться всё более сложными. Для нейронных сетей с softmax
нахождение производной может занять у вас несколько часов или даже дней.
При переходе к свёрточным и возвратным нейронным сетям градиенты, конечно, можно найти на бумаге, но нет никакого желания тратить на это время. Куда лучше потратить время на проверку различной архитектуры и параметров, не заботясь о градиентах.
Тем более, что для вычисления градиентов можно использовать специальные библиотеки, такие как Theano и TensorFlow.
Впрочем, весьма желательно понимать, что происходит, потому что тогда вычисление градиентов становится ещё одним инструментом в нашем инструментарии по машинному обучению, и мы можем применять его где угодно, даже в таких вещах, как скрытые модели Маркова.
В качестве упражнения попытайтесь найти градиент и решение для следующей функции затрат, используя градиентный спуск.
Содержание страницы
Градиентный спуск и линейная регрессия
Теперь рассмотрим, как использовать градиентный спуск применительно к линейной регрессии. Что такое линейная регрессия в Python рассмотрели подробно в этой статье.
Как вы должны помнить, функция затрат – это то, что мы пытаемся минимизировать, это наша квадратичная ошибка.
Мы также знаем, что такое производная, так как уже имели с ней дело.
Но в случае градиентного спуска мы вместо приравнивания производной к нулю и решения относительно w просто делаем небольшие шаги в направлении градиента, пока w не сойдётся с решением. Стоит отметить, что поскольку двойка является константой, мы можем опустить её, поскольку она может быть поглощена коэффициентом обучения. Поэтому полный gradient descent algorithm имеет вид
w = draw sample from N(0, 1/D)
для t=1..T
Сначала мы устанавливаем некоторое случайное значение для w.
Хорошим значением может быть значение по распределению Гаусса с центром в нуле и дисперсией от единицы до D, где D – размерность. Следующее – вход в цикл для указанного количества шагов с последующим обновлением значения только что полученного градиентного спуска. Как вариант – цикл можно заканчивать, когда изменение w становится меньше некоторой установленной величины.
У вас может возникнуть вопрос – как выбирать коэффициент обучения? Коэффициент обучения называют «гиперпараметром», поскольку он не есть параметром собственно модели, но используется для нахождения решения. В линейной регрессии его величина не играет слишком уж важной роли, но, как это уже обсуждалось в введении в градиентный спуск, чересчур большое значение коэффициента обучения не позволит ряду приближений сойтись, поскольку мы будем не скользить вниз по градиенту, а просто «прыгать» туда-сюда над «оврагом».
Чересчур же малое значение коэффициента обучения приведёт к тому, что градиентный спуск будет очень медленным. Вопрос оптимального значения гиперпараметров сейчас находится в стадии активных исследований и будет обсуждаться позднее.
На данном же этапе самое главное – много практиковаться. Получив богатый опыт, вы сможете развить в себе интуицию, которая крайне важна на всех этапах глубокого обучения, важнее любого алгоритма поиска хорошего значения гиперпараметра. Интуиция, которой вы обладаете сейчас, прямо пропорциональна длительности ваших тренировок, так что всё в ваших руках!
Обход ловушки фиктивной переменной с помощью градиентного спуска
Сейчас мы покажем, как ловушка фиктивной переменной перестаёт быть проблемой при использовании градиентного спуска. Мы смоделируем специальную ситуацию, когда невозможно использовать формальное решение, а затем покажем, как градиентный спуск позволяет выполнить задание.
Если вы не хотите самостоятельно писать код, то соответствующий файл называется gradient_descent.pi.
Итак, начнём с импорта библиотек NumPy и Matplotlib.
import numpy as np
import matplotlib as plt
Установим количество опытов равным 10, а размерность – равную трём. У нас получается Х в виде матрицы размерностью NxD. Установим первый столбец равным единице; установим первые пять элементов второго столбца и последние пять элементов третьего столбца также равными единице.
X = np.zeros((N, D))
X[:, 0] = 1
X[:5, 1] = 1
X[:5, 2] = 1
Так что наша матрица Х будет иметь вид
array([[1., 1., 0.],
[1., 1., 0.],
[1., 1., 0.],
[1., 1., 0.],
[1., 1., 0.],
[1., 0., 1.],
[1., 0., 1.],
[1., 0., 1.],
[1., 0., 1.],
[1., 0., 1.]])
Зададим Y так, чтобы для первой половины данных значение Y было равно нулю, а для второй половины – равным единице.
Y = np.array([0]*5 + [1]*5)
Таким образом, Y будет иметь вид
array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
Вы можете видеть, что обычное решение
w = np.linalg.solve(X.T.dot(X), X.T.dot(Y))
не работает, так как XTX является единичной матрицей.
Поэтому мы воспользуемся градиентным спуском. Сохраним прежнюю функцию затрат, а потом изобразим её графически, чтобы вы смогли убедиться, что она падает по мере выполнения градиентного спуска. Зададим случайные весовые коэффициенты таким образом, чтобы дисперсия была в диапазоне от 1 до D, установим коэффициент обучения равным 0,001 и запустим 1000 циклов. Мы можем также заодно вычислить среднеквадратическую ошибку.
costs = []
w = np.random.randn(D) / np.sqrt(D)
learning_rate = 0.001
для t in xrange(1000):
Yhat = X.dot(w)
delta = Yhat – Y
w = w – learning_rate*X.T.dot(delta)
mse = delta.dot(delta) / N
costs.append(mse)
Теперь изобразим это графически.
plt.plot(costs)
plt.show()
Итак, мы удостовериться, что функция затрат падает на каждом шаге приближения градиентного спуска.
Давайте также отобразим окончательное значение w и изобразим график.
plt.plot(Yhat, label=’prediction’)
plt.plot(Y, label=’targey’)
plt.legend()
plt.show()
Мы видим, что предсказанные значения Y очень близко к целевым.