Градиентный спуск – полный, пакетный и стохастический

Что такое полный, пакетный и стохастический градиентный спуск

Здравствуйте и вновь добро пожаловать на занятия по теме «Обработка данных: практическое глубокое обучение в Theano и TensorFlow».

В этой лекции мы обсудим различные способы реализации градиентного спуска, об их достоинствах и недостатках. Я бы хотел поговорить о трёх основных типах градиентного спуска – полном, пакетном и стохастическом.

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

Почему именно так?

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

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

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

Метод основывается на том факте, что все наблюдения являются независимыми и равномерно распределёнными, поэтому в долгосрочной перспективе ошибка будет уменьшаться, ведь все наблюдения берутся из одного и того распределения. В таком случае, когда расчёты сократились от N наблюдений до 1, – это замечательное улучшение метода.

В чём же недостаток?

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

Удачным компромиссов между этими двумя методами является пакетный градиентный спуск. Если у нас есть, к примеру, 10 000 наблюдений, то мы можем разделить их на 100 пакетов по 100 наблюдений в каждом и вычислять функцию затрат, основываясь на каждом пакете при каждой итерации:

Что в этом случае будет с функцией затрат?

Она тоже может становиться больше, но будет менее хаотична, чем в случае чисто стохастического градиентного спуска.

И пользуясь случаем, хочу отметить, что если вы желаете получить бесплатный материал, связанный с глубоким и машинным обучением, а также обработке данных, посетите мой сайт lazyprogrammer.me. Я постоянно пишу новые материалы по этим темам, так что проверяйте почаще. Там должно появиться предложение подписаться, так что вы можете подписаться на моё бесплатное введение в обработку данных. В нём много замечательных ресурсов для изучения языка Python, алгоритмов и структур данных, а также купонов на курсы на Udemy.

Полный, пакетный и стохастический градиентный спуск в коде

Теперь сравним изменение функции затрат в трёх случаях градиентного спуска – полного, пакетного и стохастического. Поскольку в этом примере много кода скопировано из других файлов, мы просто последовательно пройдёмся по тексту программы.

Итак, в начале файла мы импортируем все обычные библиотеки. Из файла util.py мы импортируем функцию get_transformed_data, чтобы преобразовать данные по методу главных компонент, а также функции forward, error_date, cost, gradW, gradb и y2indicator.

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from sklearn.utils import shuffle

from datetime import datetime

from util import get_transformed_data, forward, error_rate, cost, gradW, gradb, y2indicator

Для ускорения процесса мы вместо полной нейронной сети используем логистическую регрессию.

Итак, вначале мы используем функцию get_transformed_data, взяв лишь первые 300 столбцов. Далее нормализуем наши X путём вычитания среднего значения и деления на стандартное отклонение. Затем, поскольку функция get_transformed_data уже перемешала наши данные, мы оставляем последние 1 000 примеров в качестве проверочного набора, использую остальные в качестве учебного.

def main():

    X, Y, _, _ = get_transformed_data()

    X = X[:, :300]

    # normalize X first

    mu = X.mean(axis=0)

    std = X.std(axis=0)

    X = (X – mu) / std

    print “Performing logistic regression…”

    Xtrain = X[:-1000,]

    Ytrain = Y[:-1000]

    Xtest  = X[-1000:,]

    Ytest  = Y[-1000:]

Далее конвертируем наши Ytrain и Ytest с помощью функции y2indicator.

N, D = Xtrain.shape

Ytrain_ind = y2indicator(Ytrain)

Ytest_ind = y2indicator(Ytest)

Затем инициируем весовые коэффициенты и свободные члены. Обратите внимание, что инициируемые весовые коэффициенты установлены относительно малыми – пропорциональными квадратному корню из размерности. Ещё до записи этого видео я установил значения коэффициента обучения и штрафа регуляризации – 0,0001 и 0,01 соответственно. Количество итераций установим равным 200, чтобы вычисления шли не слишком долго.

# 1. full

W = np.random.randn(D, 10) / 28

b = np.zeros(10)

LL = []

lr = 0.0001

reg = 0.01

t0 = datetime.now()

for i in xrange(200):

p_y = forward(Xtrain, W, b)

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

 W += lr*(gradW(Ytrain_ind, p_y, Xtrain) – reg*W)

b += lr*(gradb(Ytrain_ind, p_y) – reg*b)

 p_y_test = forward(Xtest, W, b)

 ll = cost(p_y_test, Ytest_ind)

LL.append(ll)

 

 if i % 10 == 0:

err = error_rate(p_y_test, Ytest)

print “Cost at iteration %d: %.6f” % (i, ll)

print “Error rate:”, err

p_y = forward(Xtest, W, b)

print “Final error rate:”, error_rate(p_y, Ytest)

print “Elapsted time for full GD:”, datetime.now() – t0

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

Далее преобразуем X в двухмерную матрицу, поскольку в случае одномерной градиент не работает.

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

# 2. stochastic

W = np.random.randn(D, 10) / 28

b = np.zeros(10)

LL_stochastic = []

lr = 0.0001

reg = 0.01

t0 = datetime.now()

for i in xrange(1): # takes very long since we’re computing cost for 41k samples

tmpX, tmpY = shuffle(Xtrain, Ytrain_ind)

for n in xrange(min(N, 500)): # shortcut so it won’t take so long…

x = tmpX[n,:].reshape(1,D)

y = tmpY[n,:].reshape(1,10)

p_y = forward(x, W, b)

W += lr*(gradW(y, p_y, x) – reg*W)

b += lr*(gradb(y, p_y) – reg*b)

p_y_test = forward(Xtest, W, b)

ll = cost(p_y_test, Ytest_ind)

LL_stochastic.append(ll)

 

if n % (N/2) == 0:

err = error_rate(p_y_test, Ytest)

print “Cost at iteration %d: %.6f” % (i, ll)

print “Error rate:”, err

p_y = forward(Xtest, W, b)

print “Final error rate:”, error_rate(p_y, Ytest)

print “Elapsted time for SGD:”, datetime.now() – t0

При пакетном градиентном спуске, опять-таки, почти всё то же самое. Установим, что в каждом пакете по 500 примеров, так что общее количество пакетов будет равно N, делённое на размер пакета.

# 3. batch

W = np.random.randn(D, 10) / 28

b = np.zeros(10)

LL_batch = []

lr = 0.0001

reg = 0.01

batch_sz = 500

n_batches = N / batch_sz

t0 = datetime.now()

for i in xrange(50):

tmpX, tmpY = shuffle(Xtrain, Ytrain_ind)

for j in xrange(n_batches):

x = tmpX[j*batch_sz:(j*batch_sz + batch_sz),:]

y = tmpY[j*batch_sz:(j*batch_sz + batch_sz),:]

p_y = forward(x, W, b)

W += lr*(gradW(y, p_y, x) – reg*W)

b += lr*(gradb(y, p_y) – reg*b)

p_y_test = forward(Xtest, W, b)

ll = cost(p_y_test, Ytest_ind)

LL_batch.append(ll)

if j % (n_batches/2) == 0:

err = error_rate(p_y_test, Ytest)

print “Cost at iteration %d: %.6f” % (i, ll)

print “Error rate:”, err

p_y = forward(Xtest, W, b)

print “Final error rate:”, error_rate(p_y, Ytest)

print “Elapsted time for batch GD:”, datetime.now() – t0

И в конце мы выводим все наши данные на экран.

x1 = np.linspace(0, 1, len(LL))

plt.plot(x1, LL, label=”full”)

x2 = np.linspace(0, 1, len(LL_stochastic))

 plt.plot(x2, LL_stochastic, label=”stochastic”)

x3 = np.linspace(0, 1, len(LL_batch))

plt.plot(x3, LL_batch, label=”batch”)

plt.legend()

plt.show()

if __name__ == ‘__main__’:

main()

Итак, запустим программу.

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

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

 

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Комментариев: 1
  1. Евгений

    это машинное обучение или нейронные сети?

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Share via
Copy link