Проблема исчезающего градиента

Проблема исчезающего градиента. Описание

Здравствуйте и вновь добро пожаловать на занятия по теме «Глубокое обучение без учителя: глубокое обучение на языке Python, часть 4».

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

Что заставляет столкнуться нас с этой проблемой?

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

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

Почему же это приводит к проблеме исчезающего градиента?

Ключевым тут является градиент, или производная. Ведь какие производные у сигмоиды или гиперболического тангенса? Они всегда находятся между нулём и единицей и в большинстве своём находятся именно вблизи нуля. Из 1-й части курса по глубокому обучению мы помним, что для получения градиента для обратного распространения мы используем цепное правило, а это, по сути, значит, что по мере обратного распространения мы должны умножать все производные ошибок от впереди лежащих слоёв. Например, в самом первом слое мы умножаем производные ошибок всех последующих слоёв.

Чем плохо такое умножение? Когда мы перемножаем одно малое число (то есть то, которое меньше единицы) на другое малое число, мы получаем ещё более малое число. Попробуйте найти на калькуляторе 0,55, 0,510 и 0,550. Вы убедитесь, что чем больше степень, тем меньший результат.

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

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

Проблема исчезающего градиента. Демонстрация в коде

Начинаем с импорта обычных нужных нам библиотек и функций.

import numpy as np

import theano

import theano.tensor as T

import matplotlib.pyplot as plt

from sklearn.utils import shuffle

from util import relu, error_rate, getKaggleMNIST, init_weights

Прежде всего определим класс HiddenLayer.

class HiddenLayer(object):

    def __init__(self, D, M):

        W = init_weights((D, M))

        b = np.zeros(M)

        self.W = theano.shared(W)

        self.b = theano.shared(b)

        self.params = [self.W, self.b]

    def forward(self, X):

        return T.nnet.sigmoid(X.dot(self.W) + self.b)

Далее класс ANN.

class ANN(object):

    def __init__(self, hidden_layer_sizes):

        self.hidden_layer_sizes = hidden_layer_sizes

Функция fit. Это обычная нейронная сеть, вы уже знаете, как её писать.

def fit(self, X, Y, learning_rate=0.01, mu=0.99, epochs=30, batch_sz=100):

        N, D = X.shape

        K = len(set(Y))

        self.hidden_layers = []

        mi = D

for mo in self.hidden_layer_sizes:

       h = HiddenLayer(mi, mo)

       self.hidden_layers.append(h)

       mi = mo

Инициируем слой логистической регрессии.

W = init_weights((mo, K))

b = np.zeros

self.W = theano.shared(W)

self.b = theano.shared(b)

self.params = [self.W, self.b]

self.allWs = []

for h in self.hidden_layers:

  self.params += h.params

  self.allWs.append(h.W)

  self.allWs.append(self.W)

Определим входные и исходящие для Theano. Функция затрат – кросс-энтропийная функия ошибок.

        X_in = T.matrix(‘X_in’)

        targets = T.ivector(‘Targets’)

        pY = self.forward(X_in)

        cost = -T.mean( T.log(pY[T.arange(pY.shape[0]), targets]) )

        prediction = self.predict(X_in)

        dparams = [theano.shared(p.get_value()*0) for p in self.params]

        grads = T.grad(cost, self.params)

    updates = [

        (p, p + mu*dp – learning_rate*g) for p, dp, g in zip(self.params, dparams, grads)

    ] + [

        (dp, mu*dp – learning_rate*g) for for dp, g in zip(dparams, grads)

    ]

        train_op = theano.function(

            inputs=[X_in, targets],

            outputs=[cost, prediction],

            updates=updates,

        )

Пакетный градиентный спуск.

        n_batches = N / batch_sz

        costs = []

        lastWs = [W.get_value() for W in self.allWs]

        W_changes = []

        for i in xrange(epochs):

            print(“epoch:”, i)

            X, Y = shuffle(X, Y)

            for j in xrange(n_batches):

                Xbatch = X[j*batch_sz:(j*batch_sz + batch_sz)]

                Ybatch = Y[j*batch_sz:(j*batch_sz + batch_sz)]

                c, p = train_op(Xbatch, Ybatch)

                if j % 100 == 0:

                    print(“j / n_batches:”, j, “/”, n_batches, “cost:”, c, “error:”, error_rate(p, Ybatch))

                costs.append(c)

А вот тут появляется нечто новое. Мы начинаем считать изменения в весовых коэффициентах.

 W_change = [np.abs(W.get_value() – lastW).mean() for W, lastW in zip(self.allWs, lastWs)]

 W_changes.append(W_change)

 lastWs = [W.get_value() for W in self.allWs]

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

        W_changes = np.array(W_changes)

        plt.subplot(2,1,1)

        for i in xrange(W_changes.shape[1]):

            plt.plot(W_changes[:,i], label=’layer %s’ % i)

        plt.legend()

        plt.subplot(2,1,2)

        plt.plot(costs)

        plt.show()

Функция predict. Всё как обычно.

    def predict(self, X):

        return T.argmax(self.forward(X), axis=1)

Функция forward.

    def forward(self, X):

        Z = X

        for h in self.hidden_layers:

            Z = h.forward(Z)

        Y = T.nnet.softmax(Z.dot(self.W) + self.b)

        return Y

И функция main.

def main():

    Xtrain, Ytrain, Xtest, Ytest = getKaggleMNIST()

    dnn = ANN([1000, 750, 500])

    dnn.fit(Xtrain, Ytrain)

if __name__ == ‘__main__’:

main()

Что же, запустим этот код.

Как мы можем заметить, чем дальше, тем меньшими являются изменения в весовых коэффициентах.

Как мы можем заметить, чем дальше, тем меньшими являются изменения в весовых коэффициентах.

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

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

Share via
Copy link