Импульс в глубоком обучении

Импульс

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

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

Существует два типа импульса – обычный, использующий интуитивное понимание импульса, и импульс Нестерова, алгебраически более сложный и более трудный для понимания, но я упоминаю и его, чтобы вы почитали о нём, если вам станет интересно.

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

\nu (t - 1) = \Delta w (t- 1),

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

\Delta w (t) = \mu\nu (t-1) - \eta \bigtriangledown J (t),

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

Основная идея импульса Нестерова заключается в том, что мы смотрим вперёд и в случае ошибки делаем поправку.

Импульс в глубоком обучении

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

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

\nu (t) = \mu\nu (t-1) - \eta \bigtriangledown J (t),

\Delta w (t) = - \mu\nu (t-1) + (1 + \mu) \nu (t).

Именно такую формулировку мы и будем использовать в коде.

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

Код для обучения нейронной сети с применением импульса

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

Если вы хотите лишь загрузить код, не набирая его самостоятельно, перейдите по адресу и найдите папку ann_class2. Соответствующие файлы для данной конкретной лекции называются momentum.py и mlp.py.

Mlp.py содержит лишь код для градиентного спуска с использованием многослойного персептрона. У нас тут две версии многослойного персептрона – с использованием сигмоиды и с использованием усечённого линейного преобразователя (функции relu). Код для них мы рассматривали в предыдущем курсе – «Глубокое обучение на языке Python, часть 1».

Итак, прежде всего мы запустим код для импульса в версии с использованием сигмоиды.

import numpy as np

def forward(X, W1, b1, W2, b2):

    Z = 1 / (1 + np.exp(-( X.dot(W1) + b1 )))

    # rectifier

    # Z = X.dot(W1) + b1

    # Z[Z < 0] = 0

    A = Z.dot(W2) + b2

    expA = np.exp(A)

    Y = expA / expA.sum(axis=1, keepdims=True)

    return Y, Z

 

def derivative_w2(Z, T, Y):

    return Z.T.dot(Y – T)

def derivative_b2(T, Y):

    return (Y – T).sum(axis=0)

def derivative_w1(X, Z, T, Y, W2):

    return X.T.dot( ( ( Y-T ).dot(W2.T) * ( Z*(1 – Z) ) ) ) # for sigmoid

    # return X.T.dot( ( ( Y-T ).dot(W2.T) * (Z > 0) ) ) # for relu

 

def derivative_b1(Z, T, Y, W2):

    return (( Y-T ).dot(W2.T) * ( Z*(1 – Z) )).sum(axis=0) # for sigmoid

    # return (( Y-T ).dot(W2.T) * (Z > 0)).sum(axis=0) # for relu

Теперь пройдёмся по тексту кода файла momentum.py. Итак, мы импортируем все обычные библиотеки, а также все необходимые функции из файла util.py – get_normalized_data, error_rate, cost и y2indicator, и из файла mlp.py – функцию forward и все функции для вычисления производных.

import numpy as np

from sklearn.utils import shuffle

import matplotlib.pyplot as plt

from util import get_normalized_data, error_rate, cost, y2indicator

from mlp import forward, derivative_w2, derivative_w1, derivative_b2, derivative_b1

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

def main():

    max_iter = 20 # make it 30 for sigmoid

    print_period = 10

Далее нормализуем наши X и Y, устанавливаем коэффициент обучения равным 0,00004 и регуляризацию, равную 0,01. Данные уже перемешаны, поэтому просто оставляем последние 1 000 примеров в качестве проверочного набора, остальные служат учебным. Далее, как обычно, с помощью функции y2indicator преобразуем целевые переменные в матрицы. Размер пакета установим равным 500, а количество скрытых узлов – равным 300. На предыдущих лекциях я рассказывал, как выбираются именно такие цифры. После этого инициируем наши весовые коэффициенты и свободные члены.

    X, Y = get_normalized_data()

    lr = 0.00004

    reg = 0.01

    Xtrain = X[:-1000,]

    Ytrain = Y[:-1000]

    Xtest  = X[-1000:,]

    Ytest  = Y[-1000:]

    Ytrain_ind = y2indicator(Ytrain)

    Ytest_ind = y2indicator(Ytest)

    N, D = Xtrain.shape

    batch_sz = 500

    n_batches = N // batch_sz

 

    M = 300

    K = 10

    W1 = np.random.randn(D, M) / 28

    b1 = np.zeros(M)

    W2 = np.random.randn(M, K) / np.sqrt(M)

    b2 = np.zeros(K)

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

    # 1. batch

    # cost = -16

    LL_batch = []

    CR_batch = []

    for i in xrange(max_iter):

        for j in xrange(n_batches):

            Xbatch = Xtrain[j*batch_sz:(j*batch_sz + batch_sz),]

            Ybatch = Ytrain_ind[j*batch_sz:(j*batch_sz + batch_sz),]

            pYbatch, Z = forward(Xbatch, W1, b1, W2, b2)

            # print “first batch cost:”, cost(pYbatch, Ybatch)

 

            # updates

            W2 -= lr*(derivative_w2(Z, Ybatch, pYbatch) + reg*W2)

            b2 -= lr*(derivative_b2(Ybatch, pYbatch) + reg*b2)

            W1 -= lr*(derivative_w1(Xbatch, Z, Ybatch, pYbatch, W2) + reg*W1)

            b1 -= lr*(derivative_b1(Z, Ybatch, pYbatch, W2) + reg*b1)

            if j % print_period == 0:

                # calculate just for LL

                pY, _ = forward(Xtest, W1, b1, W2, b2)

                ll = cost(pY, Ytest_ind)

                LL_batch.append(ll)

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

 

                err = error_rate(pY, Ytest)

                CR_batch.append(err)

                print(“Error rate:”, err)

    pY, _ = forward(Xtest, W1, b1, W2, b2)

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

Во втором случае применим пакетный градиентный спуск с использованием импульса, поэтому у нас появился новый гиперпараметр – величина импульса. Установим её равной 0,9. Кроме того, поскольку нам необходимо следить за предыдущими значениями весовых коэффициентов, обнулим их. Далее – всё как раньше, разве что несколько изменена формула вычисления весовых коэффициентов, поскольку теперь нам необходимо учитывать импульс.

    # 2. batch with momentum

    W1 = np.random.randn(D, M) / 28

    b1 = np.zeros(M)

    W2 = np.random.randn(M, K) / np.sqrt(M)

    b2 = np.zeros(K)

    LL_momentum = []

    CR_momentum = []

    mu = 0.9

    dW2 = 0

    db2 = 0

    dW1 = 0

    db1 = 0

    for i in xrange(max_iter):

        for j in xrange(n_batches):

            Xbatch = Xtrain[j*batch_sz:(j*batch_sz + batch_sz),]

            Ybatch = Ytrain_ind[j*batch_sz:(j*batch_sz + batch_sz),]

            pYbatch, Z = forward(Xbatch, W1, b1, W2, b2)

 

            # updates

            dW2 = mu*dW2 – lr*(derivative_w2(Z, Ybatch, pYbatch) + reg*W2)

            W2 += dW2

            db2 = mu*db2 – lr*(derivative_b2(Ybatch, pYbatch) + reg*b2)

            b2 += db2

            dW1 = mu*dW1 – lr*(derivative_w1(Xbatch, Z, Ybatch, pYbatch, W2) + reg*W1)

            W1 += dW1

            db1 = mu*db1 – lr*(derivative_b1(Z, Ybatch, pYbatch, W2) + reg*b1)

            b1 += db1

            if j % print_period == 0:

                # calculate just for LL

                pY, _ = forward(Xtest, W1, b1, W2, b2)

                # print “pY:”, pY

                ll = cost(pY, Ytest_ind)

                LL_momentum.append(ll)

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

                err = error_rate(pY, Ytest)

                CR_momentum.append(err)

                print(“Error rate:”, err)

    pY, _ = forward(Xtest, W1, b1, W2, b2)

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

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

# 3. batch with Nesterov momentum

    W1 = np.random.randn(D, M) / 28

    b1 = np.zeros(M)

    W2 = np.random.randn(M, K) / np.sqrt(M)

    b2 = np.zeros(K)

    LL_nest = []

    CR_nest = []

    mu = 0.9

    W2 = 0

    b2 = 0

    W1 = 0

    b1 = 0

    for i in xrange(max_iter):

        for j in xrange(n_batches):

            Xbatch = Xtrain[j*batch_sz:(j*batch_sz + batch_sz),]

            Ybatch = Ytrain_ind[j*batch_sz:(j*batch_sz + batch_sz),]

            pYbatch, Z = forward(Xbatch, W1, b1, W2, b2)

            # updates

            dW2 = mu*mu*dW2 – (1 + mu)*lr*(derivative_w2(Z, Ybatch, pYbatch) + reg*W2)

            W2 += dW2

            db2 = mu*mu*db2 – (1 + mu)*lr*(derivative_b2(Ybatch, pYbatch) + reg*b2)

            b2 += db2

            dW1 = mu*mu*dW1 – (1 + mu)*lr*(derivative_w1(Xbatch, Z, Ybatch, pYbatch, W2) + reg*W1)

            W1 += dW1

            db1 = mu*mu*db1 – (1 + mu)*lr*(derivative_b1(Z, Ybatch, pYbatch, W2) + reg*b1)

            b1 += db1

            if j % print_period == 0:

                # calculate just for LL

                pY, _ = forward(Xtest, W1, b1, W2, b2)

                # print “pY:”, pY

                ll = cost(pY, Ytest_ind)

                LL_nest.append(ll)

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

                err = error_rate(pY, Ytest)

                CR_nest.append(err)

                print(“Error rate:”, err)

    pY, _ = forward(Xtest, W1, b1, W2, b2)

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

И, наконец, выведем на экран результаты работы во всех трёх случаях, чтобы можно было их сравнить.

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

    plt.plot(LL_momentum, label=”momentum”)

    plt.plot(LL_nest, label=”nesterov”)

    plt.legend()

    plt.show()

if __name__ == ‘__main__’:

main()

Итак, запустим наш файл.

Импульс в глубоком обучении

Как вы можете видеть, при использовании импульса результат совпадает с эталонным тестом с использованием логистической регрессии, причём с импульсом программа работает куда быстрее, чем в случае обычного пакетного градиентного спуска.

Если мы внимательнее сравним оба применённых типа импульса, то увидим, что результаты весьма схожи.

Вернёмся теперь к файлу mlp.py и изменим его таким образом, чтобы в скрытом слое вместо сигмоиды использовалась функция relu.

import numpy as np

def forward(X, W1, b1, W2, b2):

    # Z = 1 / (1 + np.exp(-( X.dot(W1) + b1 )))

    # rectifier

    Z = X.dot(W1) + b1

    Z[Z < 0] = 0

    A = Z.dot(W2) + b2

    expA = np.exp(A)

    Y = expA / expA.sum(axis=1, keepdims=True)

    return Y, Z

 

def derivative_w2(Z, T, Y):

    return Z.T.dot(Y – T)

def derivative_b2(T, Y):

    return (Y – T).sum(axis=0)

def derivative_w1(X, Z, T, Y, W2):

    # return X.T.dot( ( ( Y-T ).dot(W2.T) * ( Z*(1 – Z) ) ) ) # for sigmoid

    return X.T.dot( ( ( Y-T ).dot(W2.T) * (Z > 0) ) ) # for relu

def derivative_b1(Z, T, Y, W2):

    # return (( Y-T ).dot(W2.T) * ( Z*(1 – Z) )).sum(axis=0) # for sigmoid

    return (( Y-T ).dot(W2.T) * (Z > 0)).sum(axis=0) # for relu

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

Понравилась статья? Поделить с друзьями:
Добавить комментарий

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