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



Во-первых, нужен набор данных. В данном случае создаетсянабор данных с помощью библиотеки Scikit-Learn:

 

from sklearn.datasets import make_moons

from sklearn.cross_validation import train_test_split

X, y = make_moons(n_samples=5000, random_state=42, noise=0.1)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

 

Вот что мы получили:

Рисунок 1 – Графическое представление набора данных


Далее, определяется модель нейронной сети. Это будет три слоя сети (один скрытый слой):

 

import numpy as np

n_feature = 2

n_class = 2

n_iter = 10

 

def make_network(n_hidden=100):

model = dict(

   W1=np.random.randn(n_feature, n_hidden),

   W2=np.random.randn(n_hidden, n_class)

)

return model

 

Также определяется две операции: прямое распространение и обратное распространение. Сначала сделаем первый вариант:

 

def softmax(x):

return np.exp(x) / np.exp(x).sum()

 

def forward(x, model):

h = x @ model['W1']

h[h < 0] = 0

prob = softmax(h @ model['W2'])

return h, prob

 

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


 

ReLU определяется как f(x)=max(0,x), но, вместо того, чтобы делать np.max(0, x), есть ловкий трюк реализации: x[x < 0] = 0.

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

 

Теперь определяется вторая операция. Обратное распространение выглядит следующим образом:

 

def backward(model, xs, hs, errs):

dW2 = hs.T @ errs

dh = errs @ model['W2'].T

dh[hs <= 0] = 0

dW1 = xs.T @ dh

return dict(W1=dW1, W2=dW2)

 

Создаётся основа алгоритма. Выполняется реализация функцииsgd. Выглядит она так:

 

def sgd(model, X_train, y_train, batch_size):

for iter in range(n_iter):

   print('Iteration {}'.format(iter))

   X_train, y_train = shuffle(X_train, y_train)

   for i in range(0, X_train.shape[0], batch_size):

       X_train_mini = X_train[i:i + batch_size]

       y_train_mini = y_train[i:i + batch_size]

       model = sgd_step(model, X_train_mini, y_train_mini)

return model


Выполняется реализация функции sgd_step. Выглядит она так:

 

def sgd_step(model, X_train, y_train):

grad = get_batch_grad(model, X_train, y_train)

model = model.copy()

for layer in grad:

   model[layer] += learning_rate * grad[layer]

return model

 

Выполняется реализация функции get_batch_grad. Выглядит она так:

 

def get_batch_grad(model, X_train, y_train):

xs, hs, errs = [], [], []

for x, cls_idx in zip(X_train, y_train):

   h, y_pred = forward(x, model)

   y_true = np.zeros(n_class)

   y_true[int(cls_idx)] = 1.

   err = y_true - y_pred

   xs.append(x)

   hs.append(h)

   errs.append(err)

return backward(model, np.array(xs), np.array(hs), np.array(errs))

 

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


Реализация Momentum

 

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

 

Учитывая, что заготовок программы уже готов, нужно реализовать лишь основную функцию данного метода. Функция momentum приведена ниже:

 

def momentum(model, X_train, y_train, batch_size):

velocity = {k: np.zeros_like(v) for k, v in model.items()}

gamma = .9

batches = get_batch(X_train, y_train, batch_size)

for iter in range(1, n_iter + 1):

   idx = np.random.randint(0, len(batches))

   X_mini, y_mini = batches[idx]

   grad = get_batch_grad(model, X_mini, y_mini)

   for layer in grad:

       velocity[layer] = gamma * velocity[layer] + alpha * grad[layer]

       model[layer] += velocity[layer]

return model

 

Включается новая переменная velocity, которая будет накапливать импульс для каждого параметра. Обновление переменной будет происходить слагаемым alpha*grad[layer] на каждом новом шаге градиентного спуска. Так же происходит небольшое уменьшение с помощью коэффициента gamma значения переменной velocity, которая была вычислена на предыдущем шаге.

Реализация AdaGrad

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

 

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

 

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

 

def adagrad(model, X_train, y_train, batch_size):

cache = {k: np.zeros_like(v) for k, v in model.items()}

batches = get_batch(X_train, y_train, batch_size)

for iter in range(1, n_iter + 1):

   idx = np.random.randint(0, len(batches))

   X_mini, y_mini = batches[idx]

   grad = get_batch_grad(model, X_mini, y_mini)

   for k in grad:

       cache[k] += grad[k]**2

       model[k] += alpha * grad[k] / (np.sqrt(cache[k]) + eps)

return model

 

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


 

Реализация RMSProp

 

Можно заметить, что в накопительной части Adagrad, значение cache[k] += grad[k]**2 монотонно возрастает в следствие суммы и квадрата. Это может быть проблематичным , так как скорость обучения будет монотонно убывать к очень крошечной скорости обучения.

 

Для борьбы с этой проблемой, RMSProp раскладывает прошлое накопленное значение градиента, так чтобырассматривалась только часть последних градиентов. Теперь, вместо того, чтобы рассматривать все последние градиенты, RMSProp ведет себя как скользящая средняя.

 

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

 

def rmsprop(model, X_train, y_train, batch_size):

cache = {k: np.zeros_like(v) for k, v in model.items()}

gamma = .9

batches = get_batch(X_train, y_train, batch_size)

  for iter in range(1, n_iter + 1):

   idx = np.random.randint(0, len(batches))

X_mini, y_mini = batches[idx]

   grad = get_batch_grad(model, X_mini, y_mini)

   for k in grad:

       cache[k] = gamma * cache[k] + (1 - gamma) * (grad[k]**2)

       model[k] += alpha * grad[k] / (np.sqrt(cache[k]) + eps)

return model

 

Основное отличие в вычислении значения cache[k] и теперь накопленное значение градиента не будет агрессивно монотонно возрастать.

ТЕСТИРОВАНИЕ И СРАВНЕНИЕ

 

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


Дата добавления: 2018-04-05; просмотров: 524; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!