Градиентный спуск в Python: реализация и теория

Введение Это руководство представляет собой введение в простой метод оптимизации, называемый градиентным спуском, который нашел широкое применение в современных моделях машинного обучения. Мы разработаем процедуру общего назначения для реализации градиентного спуска и применим ее для решения различных задач, включая классификацию с помощью контролируемого обучения. В этом процессе мы познакомимся с работой этого алгоритма и изучим влияние различных гиперпараметров на его производительность. Мы все

Вступление

Это руководство представляет собой введение в простой метод оптимизации, называемый градиентным спуском , который нашел широкое применение в современных моделях машинного обучения.

Мы разработаем процедуру общего назначения для реализации градиентного спуска и применим ее для решения различных задач, включая классификацию с помощью контролируемого обучения.

В этом процессе мы познакомимся с работой этого алгоритма и изучим влияние различных гиперпараметров на его производительность. Мы также рассмотрим варианты пакетного и стохастического градиентного спуска в качестве примеров.

Что такое градиентный спуск?

Градиентный спуск - это метод оптимизации, который может найти минимум целевой функции . Это жадный метод, который находит оптимальное решение, делая шаг в направлении максимальной скорости убывания функции.

Напротив, Gradient Ascent является близким аналогом, который находит максимум функции, следуя направлению максимальной скорости увеличения функции.

Чтобы понять, как работает градиентный спуск, рассмотрим функцию с несколькими переменными \ (f (\ textbf {w}) \), где \ (\ textbf w = [w_1, w_2, \ ldots, w_n] ^ T \). Чтобы найти \ (\ textbf {w} \), при котором эта функция достигает минимума, градиентный спуск использует следующие шаги:

  1. Выберите начальное случайное значение \ (\ textbf {w} \)

  2. Выберите количество максимальных итераций T

  3. Выберите значение скорости обучения \ (\ eta \ in [a, b] \)

  4. Повторяйте следующие два шага до тех пор, пока \ (f \) не изменится или количество итераций не превысит T

    a.Compute: \ (\ Delta \ textbf {w} = - \ eta \ nabla_ \
    textbf {w} f (\ textbf {w}) \)

    б. обновить \ (\ textbf {w} \) как: \ (\ textbf {w} \
    leftarrow \ textbf {w} + \ Delta \ textbf {w} \)

    Символ стрелки влево - это математически правильный способ написать оператор присваивания.

Здесь \ (\ nabla_ \ textbf {w} f \) обозначает градиент \ (f \), задаваемый следующим образом:
$
\ nabla_ \ textbf {w} f (\ textbf {w}) =
\ begin {bmatrix}
\ frac {\ partial f (\ textbf {w})} {\ partial w_1} \
\ frac {\ partial f (\ textbf {w})} {\ partial w_2} \
\ vdots \
\ frac {\ partial f (\ textbf {w})} {\ partial w_n}
\ end {bmatrix}
$

Рассмотрим пример функции двух переменных \ (f (w_1, w_2) = w_1 ^ 2 + w_2 ^ 2 \), тогда на каждой итерации \ ((w_1, w_2) \) обновляется как:

$$
\ begin {bmatrix}
w_1 \ w_2
\ end {bmatrix} \ leftarrow
\ begin {bmatrix}
w_1 \ w_2
\ end {bmatrix} - \ eta
\ begin {bmatrix}
2w_1 \ 2w_2
\ end {bmatrix}
$

На рисунке ниже показано, как градиентный спуск работает с этой функцией.

Кружки - это контуры этой функции. Если двигаться по контуру, значение функции не изменится и останется постоянным .

Это противоположно направлению градиента, где функция изменяется с максимальной скоростью. Следовательно, направление градиента функции в любой точке перпендикулярно касательной к контуру в этой точке.

Проще говоря, градиент можно принять как стрелку, указывающую в направлении, где функция изменяется больше всего.

Следование направлению отрицательного градиента приведет к точкам, в которых значение функции уменьшается с максимальной скоростью. Скорость обучения , также называемая размером шага , определяет, насколько быстро или медленно мы движемся в направлении градиента.

градиентныйспуск{.ezlazyload}

Добавление импульса

При использовании градиентного спуска мы сталкиваемся со следующими проблемами:

  1. Попадание в ловушку локального минимума, что является прямым следствием жадности этого алгоритма.

  2. Превышение и отсутствие глобального оптимума, это прямой результат слишком быстрого движения в направлении градиента.

  3. Колебание - это явление, которое возникает, когда значение функции существенно не меняется независимо от направления ее продвижения. Вы можете думать об этом как о навигации по плато, вы находитесь на одной высоте независимо от того, куда вы идете

Для решения этих проблем в выражение для \ (\ Delta \ textbf {w} \) добавляется импульсный член \ (\ alpha \), чтобы стабилизировать скорость обучения при движении к глобальному оптимальному значению.

Ниже мы используем верхний индекс \ (i \) для обозначения номера итерации:
$
\ Delta \ textbf {w} ^ i = - \ eta \ nabla_ \ textbf {w} f (\
textbf {w} ^ i) + \ alpha \ textbf {w} ^ {i-1}
$

Реализация градиентного спуска в Python

Прежде чем мы начнем писать фактический код для градиентного спуска, давайте импортируем некоторые библиотеки, которые мы будем использовать, чтобы помочь нам:

 import numpy as np 
 import matplotlib 
 import matplotlib.pyplot as plt 
 import sklearn.datasets as dt 
 from sklearn.model_selection import train_test_split 

Теперь, разобравшись с этим, давайте продолжим и определим функцию gradient_descent() . В этой функции цикл заканчивается, когда:

  1. Количество итераций превышает максимальное значение

  2. Разница в значениях функции между двумя последовательными итерациями падает ниже определенного порога.

Параметры обновляются на каждой итерации в соответствии с градиентом целевой функции.

Функция принимает следующие параметры:

  • max_iterations : максимальное количество итераций для запуска

  • threshold : Остановить, если разница в значениях функции между двумя последовательными итерациями падает ниже этого порога.

  • w_init : Начальная точка, откуда начинать градиентный спуск.

  • obj_func : ссылка на функцию, которая вычисляет целевую функцию.

  • grad_func : ссылка на функцию, которая вычисляет градиент функции.

  • extra_param : дополнительные параметры (при необходимости) для obj_func и grad_func

  • learning_rate : размер шага для градиентного спуска. Он должен быть в [0,1]

  • momentum : импульс для использования. Он должен быть в [0,1]

Также функция вернет:

  • w_history : Все точки в космосе, посещенные градиентным спуском, в которых была оценена целевая функция.

  • f_history : соответствующее значение целевой функции, вычисленное в каждой точке.

1
<!-- -->
 # Make threshold a -ve value if you want to run exactly 
 # max_iterations. 
 def gradient_descent(max_iterations,threshold,w_init, 
 obj_func,grad_func,extra_param = [], 
 learning_rate=0.05,momentum=0.8): 
 
 w = w_init 
 w_history = w 
 f_history = obj_func(w,extra_param) 
 delta_w = np.zeros(w.shape) 
 i = 0 
 diff = 1.0e10 
 
 while i<max_iterations and diff>threshold: 
 delta_w = -learning_rate*grad_func(w,extra_param) + momentum*delta_w 
 w = w+delta_w 
 
 # store the history of w and f 
 w_history = np.vstack((w_history,w)) 
 f_history = np.vstack((f_history,obj_func(w,extra_param))) 
 
 # update iteration number and diff between successive values 
 # of objective function 
 i+=1 
 diff = np.absolute(f_history[-1]-f_history[-2]) 
 
 return w_history,f_history 

Оптимизация функций с градиентным спуском

Теперь, когда у нас есть универсальная реализация градиентного спуска, давайте запустим ее на нашем примере 2D-функции \ (f (w_1, w_2) = w_1 ^ 2 + w_2 ^ 2 \) с круговыми контурами.

Функция имеет минимальное нулевое значение в начале координат. Давайте сначала визуализируем функцию, а затем найдем ее минимальное значение.

Визуализация целевой функции f (x)

Приведенная visualize_fw() генерирует 2500 точек на сетке с одинаковым интервалом и вычисляет значение функции в каждой точке.

Функция function_plot() отображает все точки разными цветами в зависимости от значения \ (f (\ textbf w) \) в этой точке. Все точки, в которых значение функции одинаково, имеют одинаковый цвет:

 def visualize_fw(): 
 xcoord = np.linspace(-10.0,10.0,50) 
 ycoord = np.linspace(-10.0,10.0,50) 
 w1,w2 = np.meshgrid(xcoord,ycoord) 
 pts = np.vstack((w1.flatten(),w2.flatten())) 
 
 # All 2D points on the grid 
 pts = pts.transpose() 
 
 # Function value at each point 
 f_vals = np.sum(pts*pts,axis=1) 
 function_plot(pts,f_vals) 
 plt.title('Objective Function Shown in Color') 
 plt.show() 
 return pts,f_vals 
 
 # Helper function to annotate a single point 
 def annotate_pt(text,xy,xytext,color): 
 plt.plot(xy[0],xy[1],marker='P',markersize=10,c=color) 
 plt.annotate(text,xy=xy,xytext=xytext, 
 # color=color, 
 arrowprops=dict(arrowstyle="->", 
 color = color, 
 connectionstyle='arc3')) 
 
 # Plot the function 
 # Pts are 2D points and f_val is the corresponding function value 
 def function_plot(pts,f_val): 
 f_plot = plt.scatter(pts[:,0],pts[:,1], 
 c=f_val,vmin=min(f_val),vmax=max(f_val), 
 cmap='RdBu_r') 
 plt.colorbar(f_plot) 
 # Show the optimal point 
 annotate_pt('global minimum',(0,0),(-5,-7),'yellow') 
 
 pts,f_vals = visualize_fw() 

визуализация градиентногоспуска{.ezlazyload}

Запуск градиентного спуска с различными гиперпараметрами

Теперь пора запустить градиентный спуск, чтобы минимизировать нашу целевую функцию. Чтобы вызвать gradient_descent() , мы определяем две функции:

  • f() : вычисляет целевую функцию в любой точке w
  • grad() : вычисляет градиент в любой точке w

Чтобы понять влияние различных гиперпараметров на градиентный спуск, функция solve_fw() вызывает gradient_descent() с 5 итерациями для разных значений скорости обучения и импульса.

Функция visualize_learning() строит значения \ ((w_1, w_2) \), при этом значения функции отображаются разными цветами. Стрелки на графике позволяют легко отследить, какая точка была обновлена с последней:

 # Objective function 
 def f(w,extra=[]): 
 return np.sum(w*w) 
 
 # Function to compute the gradient 
 def grad(w,extra=[]): 
 return 2*w 
 
 # Function to plot the objective function 
 # and learning history annotated by arrows 
 # to show how learning proceeded 
 def visualize_learning(w_history): 
 
 # Make the function plot 
 function_plot(pts,f_vals) 
 
 # Plot the history 
 plt.plot(w_history[:,0],w_history[:,1],marker='o',c='magenta') 
 
 # Annotate the point found at last iteration 
 annotate_pt('minimum found', 
 (w_history[-1,0],w_history[-1,1]), 
 (-1,7),'green') 
 iter = w_history.shape[0] 
 for w,i in zip(w_history,range(iter-1)): 
 # Annotate with arrows to show history 
 plt.annotate("", 
 xy=w, xycoords='data', 
 xytext=w_history[i+1,:], textcoords='data', 
 arrowprops=dict(arrowstyle='<-', 
 connectionstyle='angle3')) 
 
 def solve_fw(): 
 # Setting up 
 rand = np.random.RandomState(19) 
 w_init = rand.uniform(-10,10,2) 
 fig, ax = plt.subplots(nrows=4, ncols=4, figsize=(18, 12)) 
 learning_rates = [0.05,0.2,0.5,0.8] 
 momentum = [0,0.5,0.9] 
 ind = 1 
 
 # Iteration through all possible parameter combinations 
 for alpha in momentum: 
 for eta,col in zip(learning_rates,[0,1,2,3]): 
 plt.subplot(3,4,ind) 
 w_history,f_history = gradient_descent(5,-1,w_init, f,grad,[],eta,alpha) 
 
 visualize_learning(w_history) 
 ind = ind+1 
 plt.text(-9, 12,'Learning Rate = '+str(eta),fontsize=13) 
 if col==1: 
 plt.text(10,15,'momentum = ' + str(alpha),fontsize=20) 
 
 fig.subplots_adjust(hspace=0.5, wspace=.3) 
 plt.show() 

Давайте запустим solve_fw() и посмотрим, как скорость обучения и импульс влияют на градиентный спуск:

 solve_fw() 

визуализация градиентным спуском с разнымигиперпараметрами{.ezlazyload}

Этот пример поясняет роль импульса и скорости обучения.

На первом графике с нулевым импульсом и скоростью обучения, установленной на 0,05, обучение идет медленно, и алгоритм не достигает глобального минимума. Как видно из графиков в первом столбце, увеличение импульса ускоряет обучение. Другой крайностью является последний столбец, где скорость обучения остается высокой. Это вызывает колебания, которыми можно до некоторой степени управлять, добавляя импульс.

Общее руководство для градиентного спуска - использовать небольшие значения скорости обучения и более высокие значения импульса.

Градиентный спуск для минимизации среднеквадратической ошибки

Градиентный спуск - это красивый и простой метод минимизации среднеквадратической ошибки в контролируемой задаче классификации или регрессии.

Предположим, нам даны \ (m \) обучающие примеры \ ([x_ {ij}] \) с \ (i = 1 \ ldots m \), где каждый пример имеет \ (n \) особенности, то есть \ (j = 1 \ ldots n \). Если соответствующие целевые и выходные значения для каждого примера равны \ (t_i \) и \ (o_i \) соответственно, то функция среднеквадратичной ошибки \ (E \) (в данном случае наша объектная функция) определяется как:

$$
E = \ frac {1} {m} \ Sigma_ {i = 1} ^ m (t_i - o_i) ^ 2
$

Где выход \ (o_i \) определяется взвешенной линейной комбинацией входов, заданной следующим образом:

$$
o_i = w_0 + w_1 x_ {i1} + w_2 x_ {i2} + \ ldots + w_n x_ {in}
$

Неизвестным параметром в приведенном выше уравнении является вектор весов \ (\ textbf w = [w_0, w_1, \ ldots, w_n] ^ T \).

Целевая функция в этом случае представляет собой среднеквадратичную ошибку с градиентом, определяемым как:

$$
\ nabla _ {\ textbf w} E (\ textbf w) = - \ Sigma_ {i = 1} ^ {m} (t_i - o_i) \ textbf {x} _i
$

Где \ (x_ {i} \) - i-й пример. или массив объектов размера n .

Все, что нам сейчас нужно, это функция для вычисления градиента и функция для вычисления среднеквадратичной ошибки.

Затем gradient_descent() можно использовать как есть. Обратите внимание, что все обучающие примеры обрабатываются вместе при вычислении градиента. Следовательно, эта версия градиентного спуска для обновления весов называется пакетным обновлением или пакетным обучением :

 # Input argument is weight and a tuple (train_data, target) 
 def grad_mse(w,xy): 
 (x,y) = xy 
 (rows,cols) = x.shape 
 
 # Compute the output 
 o = np.sum(x*w,axis=1) 
 diff = yo 
 diff = diff.reshape((rows,1)) 
 diff = np.tile(diff, (1, cols)) 
 grad = diff*x 
 grad = -np.sum(grad,axis=0) 
 return grad 
 
 # Input argument is weight and a tuple (train_data, target) 
 def mse(w,xy): 
 (x,y) = xy 
 
 # Compute output 
 # keep in mind that wer're using mse and not mse/m 
 # because it would be relevant to the end result 
 o = np.sum(x*w,axis=1) 
 mse = np.sum((yo)*(yo)) 
 mse = mse/2 
 return mse 

Запуск градиентного спуска с оптическим распознаванием текста

Чтобы проиллюстрировать градиентный спуск в задаче классификации, мы выбрали наборы данных цифр, включенные в sklearn.datasets .

Чтобы не усложнять задачу, давайте проведем тестовый прогон градиентного спуска для задачи с двумя классами (цифра 0 против цифры 1). Приведенный ниже код загружает цифры и отображает первые 10 цифр. Это дает нам представление о характере тренировочных точек:

 # Load the digits dataset with two classes 
 digits,target = dt.load_digits(n_class=2,return_X_y=True) 
 fig,ax = plt.subplots(nrows=1, ncols=10,figsize=(12,4),subplot_kw=dict(xticks=[], yticks=[])) 
 
 # Plot some images of digits 
 for i in np.arange(10): 
 ax[i].imshow(digits[i,:].reshape(8,8),cmap=plt.cm.gray) 
 plt.show() 

оптимизация классификации с градиентнымспуском{.ezlazyload}

Нам также понадобится метод train_test_split из sklearn.model_selection для разделения обучающих данных на поезд и тестовый набор. В приведенном ниже коде выполняется градиентный спуск на обучающем наборе, изучаются веса и строится среднеквадратическая ошибка на разных итерациях.

При запуске градиентного спуска мы сохраним скорость обучения и импульс очень маленькими, поскольку входные данные не нормализованы или стандартизированы. Кроме того, пакетная версия градиентного спуска требует меньшей скорости обучения:

 # Split into train and test set 
 x_train, x_test, y_train, y_test = train_test_split( 
 digits, target, test_size=0.2, random_state=10) 
 
 # Add a column of ones to account for bias in train and test 
 x_train = np.hstack((np.ones((y_train.size,1)),x_train)) 
 x_test = np.hstack((np.ones((y_test.size,1)),x_test)) 
 
 # Initialize the weights and call gradient descent 
 rand = np.random.RandomState(19) 
 w_init = rand.uniform(-1,1,x_train.shape[1])*.000001 
 w_history,mse_history = gradient_descent(100,0.1,w_init, 
 mse,grad_mse,(x_train,y_train), 
 learning_rate=1e-6,momentum=0.7) 
 
 # Plot the MSE 
 plt.plot(np.arange(mse_history.size),mse_history) 
 plt.xlabel('Iteration No.') 
 plt.ylabel('Mean Square Error') 
 plt.title('Gradient Descent on Digits Data (Batch Version)') 
 plt.show() 

Оптимизация среднеквадратичной ошибки с градиентнымспуском{.ezlazyload}

Выглядит отлично! Давайте проверим частоту ошибок нашего OCR на обучающих и тестовых данных. Ниже приведена небольшая функция для вычисления коэффициента ошибок классификации, которая вызывается на обучающем и тестовом наборе:

 # Returns error rate of classifier 
 # total miclassifications/total*100 
 def error(w,xy): 
 (x,y) = xy 
 o = np.sum(x*w,axis=1) 
 
 #map the output values to 0/1 class labels 
 ind_1 = np.where(o>0.5) 
 ind_0 = np.where(o<=0.5) 
 o[ind_1] = 1 
 o[ind_0] = 0 
 return np.sum((oy)*(oy))/y.size*100 
 
 train_error = error(w_history[-1],(x_train,y_train)) 
 test_error = error(w_history[-1],(x_test,y_test)) 
 
 print("Train Error Rate: " + "{:.2f}".format(train_error)) 
 print("Test Error Rate: " + "{:.2f}".format(test_error)) 

 Train Error Rate: 0.69 
 Test Error Rate: 1.39 

Стохастический градиентный спуск в Python

В предыдущем разделе мы использовали схему пакетного обновления для градиентного спуска.

Другая версия градиентного спуска - это онлайн- схема или схема стохастического обновления, где каждый обучающий пример берется по одному для обновления весов.

После того, как все обучающие примеры пройдены, мы говорим, что эпоха завершилась. Обучающие примеры перемешиваются перед каждой эпохой для лучших результатов.

Приведенный ниже фрагмент кода представляет собой небольшую модификацию функции gradient_descent() для включения ее стохастического аналога. Эта функция принимает (обучающий набор, цель) в качестве параметра вместо дополнительного параметра. Термин «итерации» был переименован в «эпохи»:

 # (xy) is the (training_set,target) pair 
 def stochastic_gradient_descent(max_epochs,threshold,w_init, 
 obj_func,grad_func,xy, 
 learning_rate=0.05,momentum=0.8): 
 (x_train,y_train) = xy 
 w = w_init 
 w_history = w 
 f_history = obj_func(w,xy) 
 delta_w = np.zeros(w.shape) 
 i = 0 
 diff = 1.0e10 
 rows = x_train.shape[0] 
 
 # Run epochs 
 while i<max_epochs and diff>threshold: 
 # Shuffle rows using a fixed seed to reproduce the results 
 np.random.seed(i) 
 p = np.random.permutation(rows) 
 
 # Run for each instance/example in training set 
 for x,y in zip(x_train[p,:],y_train[p]): 
 delta_w = -learning_rate*grad_func(w,(np.array([x]),y)) + momentum*delta_w 
 w = w+delta_w 
 
 i+=1 
 w_history = np.vstack((w_history,w)) 
 f_history = np.vstack((f_history,obj_func(w,xy))) 
 diff = np.absolute(f_history[-1]-f_history[-2]) 
 
 return w_history,f_history 

Давайте запустим код, чтобы увидеть, каковы результаты для стохастической версии градиентного спуска:

 rand = np.random.RandomState(19) 
 w_init = rand.uniform(-1,1,x_train.shape[1])*.000001 
 w_history_stoch,mse_history_stoch = stochastic_gradient_descent( 
 100,0.1,w_init, 
 mse,grad_mse,(x_train,y_train), 
 learning_rate=1e-6,momentum=0.7) 
 
 # Plot the MSE 
 plt.plot(np.arange(mse_history_stoch.size),mse_history_stoch) 
 plt.xlabel('Iteration No.') 
 plt.ylabel('Mean Square Error') 
 plt.title('Gradient Descent on Digits Data (Stochastic Version)') 
 
 plt.show() 

частота ошибок: стохатический и пакетный градиентныйспуск{.ezlazyload}

Также проверим частоту ошибок:

 train_error_stochastic = error(w_history_stoch[-1],(x_train,y_train)) 
 test_error_stochastic = error(w_history_stoch[-1],(x_test,y_test)) 
 
 print("Train Error rate with Stochastic Gradient Descent: " + 
 "{:.2f}".format(train_error_stochastic)) 
 print("Test Error rate with Stochastic Gradient Descent: " 
 + "{:.2f}".format(test_error_stochastic)) 

 Train Error rate with Stochastic Gradient Descent: 0.35 
 Test Error rate with Stochastic Gradient Descent: 1.39 

Сравнение пакетной и стохастической версий

Теперь сравним пакетную и стохастическую версии градиентного спуска.

Мы зафиксируем скорость обучения для обеих версий на одно и то же значение и изменим импульс, чтобы увидеть, насколько быстро они сходятся. Начальные веса и критерии остановки для обоих алгоритмов остаются одинаковыми:

 fig, ax = plt.subplots(nrows=3, ncols=1, figsize=(10,3)) 
 
 rand = np.random.RandomState(11) 
 w_init = rand.uniform(-1,1,x_train.shape[1])*.000001 
 eta = 1e-6 
 for alpha,ind in zip([0,0.5,0.9],[1,2,3]): 
 w_history,mse_history = gradient_descent( 
 100,0.01,w_init, 
 mse,grad_mse,(x_train,y_train), 
 learning_rate=eta,momentum=alpha) 
 
 w_history_stoch,mse_history_stoch = stochastic_gradient_descent( 
 100,0.01,w_init, 
 mse,grad_mse,(x_train,y_train), 
 learning_rate=eta,momentum=alpha) 
 
 # Plot the MSE 
 plt.subplot(130+ind) 
 plt.plot(np.arange(mse_history.size),mse_history,color='green') 
 plt.plot(np.arange(mse_history_stoch.size),mse_history_stoch,color='blue') 
 plt.legend(['batch','stochastic']) 
 
 # Display total iterations 
 plt.text(3,-30,'Batch: Iterations='+ 
 str(mse_history.size) ) 
 plt.text(3,-45,'Stochastic: Iterations='+ 
 str(mse_history_stoch.size)) 
 plt.title('Momentum = ' + str(alpha)) 
 
 # Display the error rates 
 train_error = error(w_history[-1],(x_train,y_train)) 
 test_error = error(w_history[-1],(x_test,y_test)) 
 
 train_error_stochastic = error(w_history_stoch[-1],(x_train,y_train)) 
 test_error_stochastic = error(w_history_stoch[-1],(x_test,y_test)) 
 
 print ('Momentum = '+str(alpha)) 
 
 print ('\tBatch:') 
 print ('\t\tTrain error: ' + "{:.2f}".format(train_error) ) 
 print ('\t\tTest error: ' + "{:.2f}".format(test_error) ) 
 
 print ('\tStochastic:') 
 print ('\t\tTrain error: ' + "{:.2f}".format(train_error_stochastic) ) 
 print ('\t\tTest error: ' + "{:.2f}".format(test_error_stochastic) ) 
 
 
 plt.show() 

 Momentum = 0 
 Batch: 
 Train error: 0.35 
 Test error: 1.39 
 Stochastic: 
 Train error: 0.35 
 Test error: 1.39 
 Momentum = 0.5 
 Batch: 
 Train error: 0.00 
 Test error: 1.39 
 Stochastic: 
 Train error: 0.35 
 Test error: 1.39 
 Momentum = 0.9 
 Batch: 
 Train error: 0.00 
 Test error: 1.39 
 Stochastic: 
 Train error: 0.00 
 Test error: 1.39 

output_28_1{.ezlazyload}

Хотя нет существенной разницы в точности между двумя версиями классификатора, стохастическая версия является явным победителем, когда дело доходит до скорости сходимости. Для достижения того же результата, что и у его пакетного аналога, требуется меньше итераций.

Выводы

Градиентный спуск - это простой и легкий в реализации прием.

В этом уроке мы проиллюстрировали градиентный спуск на функции двух переменных с круговыми контурами. Затем мы расширили наш пример, чтобы минимизировать среднеквадратичную ошибку в задаче классификации, и построили простую систему распознавания текста. Мы также обсудили стохастический вариант градиентного спуска.

В этом руководстве была разработана функция общего назначения для реализации градиентного спуска. Мы рекомендуем читателям использовать эту функцию для решения различных задач регрессии и классификации с разными гиперпараметрами, чтобы лучше понять, как она работает.

comments powered by Disqus