Градиентный спуск — это итеративный алгоритм оптимизации, используемый для минимизации математической функции путем итеративной корректировки значений ее входных данных. Он в основном используется в задачах машинного обучения и оптимизации.
В этот момент, как и многие из вас, я тоже потерялся! Что, черт возьми это? Но потерпите меня. Начнем с более простых слов и примера. Представьте, что вы хотите спуститься с холма, чтобы достичь дна. Вы не знаете, как легче всего спуститься, поэтому делаете небольшие шаги и смотрите на свое окружение. После каждого шага вы проверяете, приближаетесь ли вы к дну или отдаляетесь от него. Если вы подходите ближе, вы продолжаете двигаться в том же направлении. Но если вы уходите, вы поворачиваетесь и делаете шаг в противоположном направлении.
Этот процесс небольших шагов и корректировки вашего направления — это то, чего хочет достичь градиентный спуск. В градиентном спуске вместо физического холма у нас есть математическая функция, которую мы хотим минимизировать. Функция может представлять что угодно, например стоимость производства чего-либо или ошибку в модели прогнозирования.
Цель градиентного спуска — найти входные значения функции, которые дают нам наименьшее возможное значение. Это как найти подножие математического холма. Для этого мы начинаем с некоторых начальных значений для входных данных и вычисляем градиент функции в этой точке.
Градиент указывает нам направление наибольшего подъема или направление, в котором функция увеличивается больше всего. Как и в нашем примере с холмом, мы хотим двигаться в направлении, противоположном градиенту, чтобы идти вниз и минимизировать функцию.
Итак, делаем небольшой шаг в сторону, противоположную градиенту, и пересчитываем градиент в новой точке. Мы продолжаем делать это, корректируя наши входные значения и делая небольшие шаги в направлении, которое заставляет функцию уменьшаться больше всего, пока мы не достигнем точки, в которой функция больше не уменьшается или не будем удовлетворены результатом.
Рабочий пример градиентного спуска
Примером градиентного спуска из реальной жизни является поиск наилучшего маршрута к дому вашего друга с помощью навигационного приложения. Приложение учитывает различные факторы, такие как расстояние, трафик и дорожные условия, чтобы рассчитать самый быстрый маршрут. Изначально он не знает лучшего маршрута, поэтому начинает с некоторого предположения. Он смотрит на текущий маршрут и вычисляет градиент функции времени в пути в этой точке.
Если градиент сообщает приложению, что время в пути уменьшится при выборе другого поворота, это предполагает небольшую корректировку маршрута. Он продолжает делать это, пересчитывая градиент и предлагая небольшие корректировки, пока не найдет маршрут, минимизирующий время в пути. Приложение использует градиентный спуск, чтобы постоянно улучшать предлагаемый маршрут на основе меняющихся условий и отзывов пользователей.
Скучная математика градиентного спуска
Давайте снова рассмотрим пример с холмом. В каждой позиции вы знаете две вещи: градиент (для вашей позиции или параметра) и размер шага (скорость обучения). Эта информация позволяет вам решить, куда переместить и обновить вашу позицию или параметр. Затем с этим обновленным параметром пересчитывается градиент, и этот процесс повторяется до тех пор, пока не будет достигнута сходимость или локальные минимумы.
Что такое конвергенция?
Ситуация, при которой проигрыш (или ваше положение по отношению к нижней части холма) существенно не меняется. Другими словами, вы застряли в позиции вблизи дна.
Давайте пройдемся по градиентному спуску шаг за шагом.
- Получить определение функции
def func(x): return 2*x**2
2. Получите градиент для функции
def grad(x): return 4*x
3. Обновите параметр, используя скорость обучения (lr)
x_new = x_old - lr*grad(x_old)
4. Повторяйте шаги со 2 по 3, пока не будет достигнута конвергенция. Это помогает нам остановить цикл, когда либо градиент становится очень маленьким, либо количество итераций превышает 100. Их можно варьировать в зависимости от приложения.
while abs(grad(x_old)) > 0.001 and no_of_iter < 100
Теперь давайте определим полную функцию градиентного спуска:
def grad_desc(x_old, lr=0.1): #variable to count number of iterations no_of_iter = 0 #variables to keep track of all values history_x = [] history_func = [] #saving first value history_x.append(x_old) history_func.append(func(x_old)) #loop to stop at convergence while abs(grad(x_old)) > 0.001 and no_of_iter < 100: #Use gradient to update parameters x_new = x_old - lr*grad(x_old) #save current value in history variables history_x.append(x_new) history_func.append(func(x_new)) #update old variables x_old = x_new #increment no. of iterations no_of_iter += 1 return history_x, history_func
Примечание: history_x и history_func сохраняются для отслеживания изменения параметров.
Давайте попробуем построить кривую и посмотреть, как движется градиентный спуск для достижения минимума.
#running gradient descent with initial position (4.7) #with learning rate of 0.1 hist_x, hist_func = grad_desc(4.7, lr=0.1) #Plotting 2D graph import numpy as np import matplotlib.pyplot as plt #getting all values of x to plot x = np.linspace(-5, 5, 100) #Calculating function value using x y = [func(i) for i in x] #plotting function plt.plot(x, y,color='blue') #plotting history of movement plt.plot(hist_x, hist_func,color='red',marker='o') plt.show()
Выход:
На что влияет скорость обучения?
Это значение, которое управляет размером шага или изменениями, которые необходимо выполнить в параметре.
Если ваш x = 2, grad(x) становится равным 8.
(i) При скорости обучения 0,1 ваше изменение становится -0,1 * 8 = -0,8.
Используя это, вы достигаете своего минимума за 20 шагов, как показано на графике.
Количество итераций: 20
Локальные минимумы происходят по адресу: 0,00017183944668295985
Значение функции в локальных минимумах: 5.90575908726116e-08
(ii) При скорости обучения 0,2 наше изменение становится -0,2 * 8 = -1,6.
Количество итераций: 7
Локальные минимумы происходят по адресу: 6.0159999999999885e-05
Значение функции в локальных минимумах: 7,238451199999973e-09
На этот раз размер вашего шага был больше; следовательно, мы достигли минимума за 7 шагов.
(iii) При скорости обучения 0,49 ваше изменение становится -0,49 * 8 = -3,92.
Но дальнейшее увеличение скорости обучения приведет к решению, отличному от самого быстрого или даже правильного.
Количество итераций: 100
Локальные минимумы встречаются на: 0,0792905009865931
Значение функции в локальных минимумах: 0,012573967093409842
В этом случае мы исчерпали наши 100 итераций, но еще не достигли минимума. В двух других случаях мы достигли минимума всего за 7 или 20 шагов.
Пример с двумя переменными:
Давайте попробуем другую функцию с двумя параметрами. Этот метод может быть расширен для включения любого количества переменных.
#define function def func(x1,x2): return 2*x1**2 + (x2-1)**2 #define gradient of function with respect to first variable def grad_x1(x1): return 4*x1 #define gradient of function with respect to second variable def grad_x2(x2): return 2*(x2-1) def grad_desc(x1_old,x2_old, lr=0.1): #variable to count number of iterations no_of_iter = 0 #variables to keep track of all values history_x = [] history_func = [] #saving initial value history_x.append([x1_old,x2_old]) history_func.append(func(x1_old,x2_old)) #loop to stop at convergence while (abs(grad_x1(x1_old)) > 0.001 and abs(grad_x2(x2_old)) > 0.001) and no_of_iter < 100: #Use gradient to update parameters x1_new = x1_old - lr*grad_x1(x1_old) x2_new = x2_old - lr*grad_x2(x2_old) #save current value in history variables history_x.append([x1_new,x2_new]) history_func.append(func(x1_new,x2_new)) #update old variables x1_old = x1_new x2_old = x2_new #increment no. of iterations no_of_iter += 1 return history_x, history_func #running gradient descent with initial position (1.3,4.7) #with learning rate of 0.3 hist_x, hist_func = grad_desc(1.3,4.7, lr=0.3) #Plotting 3D graph import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') #getting all x1 and x2 values feature_x1 = np.arange(-5,5,0.005) feature_x2 = np.arange(-5,5,0.005) #creating a mesh using x1, x2 values [X1, X2] = np.meshgrid(feature_x1, feature_x2) #Calculating function value using X1,X2 Z = np.square(X1)*2 + np.square(X2-1) #plotting history of movement ax.plot(np.array(hist_x)[:,0],np.array(hist_x)[:,1],hist_func,marker='o',color='r',markersize=5,linewidth=3) #plotting function ax.plot_surface(X1, X2, Z, cmap=Pastel1, alpha=0.6) plt.show()
Вывод:
Скорость обучения: 0,3
Количество итераций: 6
Локальные минимумы встречаются по адресу: 8.319999999999996e-05, 1.0151552000000001
Значение функции в локальных минимумах: 0,00022969393152000444
Бонус: контурный сюжет
Вы можете попробовать построить контурный график, используя следующий код Python:
Код:
import matplotlib.pyplot as plt import numpy as np #getting all x1 and x2 values feature_x1 = np.arange(-8,8,0.01) feature_x2 = np.arange(-8,8,0.01) #creating a mesh using x1, x2 values [X1, X2] = np.meshgrid(feature_x1, feature_x2) #Calculating function value using X1,X2 Z = np.square(X1)*2 + np.square(X2-1) fig, ax = plt.subplots(1, 1) ax.contour (X1, X2, Z) ax.plot(np.array(hist_x)[:,0],np.array(hist_x)[:,1],marker='o',color='r',markersize=5,linewidth=3) ax.set_title('Contour Plot') ax.set_xlabel('feature_x1') ax.set_ylabel('feature_x2') plt.show()
Выход:
Примечание. Вы можете запустить следующую команду в терминале Python, чтобы получить интерактивный график.
%matplotlib qt
Запустите следующую команду в терминале Python, чтобы вернуться к обычному (или встроенному графику).
%matplotlib inline
Вот еще один анимированный пример от Google, который вы можете попробовать.
Уменьшение потерь: оптимизация скорости обучения | Машинное обучение | Google для разработчиков
В следующем посте мы увидим различные варианты градиентного спуска.