Как я могу оптимизировать поток градиента в LSTM с помощью Pytorch? - PullRequest
1 голос
/ 27 апреля 2019

Я работаю в lstm с данными временных рядов, и я обнаружил проблему в градиентах моей сети.У меня есть один слой из 121 клетки.Для каждой ячейки у меня есть одно входное значение, и я получаю одно выходное значение.Я работаю с размером пакета в 121 значение и определяю ячейку lstm с помощью batch_first = True, поэтому мои выходные данные - [batch, timestep, features].

Как только у меня есть выходные данные (тензор размера [121,121,1]), я вычисляю потери, используя MSELoss (), и делаю обратное распространение.И тут возникает проблема.Просматривая градиенты каждой ячейки, я замечаю, что градиенты первых 100 ячеек (более или менее) равны нулю.

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

Кто-нибудь знает, что происходит?

Спасибо!

PS .: Я показываю вам поток градиента последних ячеек: enter image description here


Обновление: Как я уже пытался спросить, у меня все еще есть вопрос о обратном распространении LSTM.Как видно из рисунка ниже, в одной ячейке, кроме градиентов, поступающих из других ячеек, я думаю, что есть и другая форма градиента.enter image description here

Например, давайте посмотрим на ячейку 1. Я получаю выход y1 и вычисляю потери E1.Я делаю то же самое с другими клетками.Поэтому, когда я делаю обратное распространение в ячейке 1, я получаю dE2/dy2 * dy2/dh1 * dh1/dw1 + ..., которые являются градиентами, относящимися к следующим ячейкам в сети (BPTT), как объяснили @ kmario23 и @DavidNg.И у меня также есть градиент, связанный с E1 (dE1/dy1 * dy1/dw1).Первые градиенты могут исчезнуть во время потока, но этот нет.

Итак, если подвести итог, хотя у меня длинный слой ячеек lstm, я понимаю, что у меня есть градиент, связанный только с каждой ячейкой, поэтому я не понимаю, почему у меня есть градиенты, равные нулю.Что происходит с ошибкой, связанной с E1?Почему рассчитывается только bptt?

1 Ответ

0 голосов
/ 27 апреля 2019

Я имел дело с этими проблемами несколько раз. И вот мой совет:

Использовать меньшее количество временных шагов

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

# it's exploding
1.01^121 = 101979  # imagine how large it is when the weight is not 1.01

# or it's vanishing
0.9^121 = 2.9063214161987074e-06 # ~ 0.0 when we init the weight smaller than 1.0

Для меньшего количества беспорядка, я беру пример простого RNNCell - с весами W_ih и W_hh без смещения. И в вашем случае W_hh - это просто одно число, но регистр может быть обобщен для любой матрицы W_hh. Мы также используем активацию indentity.

Если развернуть RNN по всем временным шагам K=3, мы получим:

h_1 = W_ih * x_0 + W_hh * h_0 (1)
h_2 = W_ih * x_1 + W_hh * h_1 (2)
h_3 = W_ih * x_2 + W_hh * h_2 (3)

Поэтому, когда нам нужно обновить веса W_hh, мы должны накапливать все градиенты на шаге (1), (2), (3).

grad(W_hh) = grad(W_hh at step 1) + grad(W_hh at step 2) + grad(W_hh at step 3)

# step 3
grad(W_hh at step3) = d_loss/d(h_3) * d(h_3)/d(W_hh)
grad(W_hh at step3) = d_loss/d(h_3) * h_2


# step 2
grad(W_hh at step2) = d_loss/d(h_2) * d(h_2)/d(W_hh)
grad(W_hh at step2) = d_loss/d(h_3) * d_(h_3)/d(h_2) * d(h_2)/d(W_hh)
grad(W_hh at step2) = d_loss/d(h_3) * d_(h_3)/d(h_2) * h_1

# step 1
grad(W_hh at step1) = d_loss/d(h_1) * d(h_1)/d(W_hh)
grad(W_hh at step1) = d_loss/d(h_3) * d(h_3)/d(h_2) * d(h_2)/d(h_1) * d(h_1)/d(W_hh)
grad(W_hh at step1) = d_loss/d(h_3) * d(h_3)/d(h_2) * d(h_2)/d(h_1) * h_0

# As we also:
d(h_i)/d(h_i-1) = W_hh

# Then:
grad(W_hh at step3) = d_loss/d(h_3) * h_2
grad(W_hh at step2) = d_loss/d(h_3) * W_hh * h_1
grad(W_hh at step1) = d_loss/d(h_3) * W_hh * W_hh * h_0
Let d_loss/d(h_3) = v

# We accumulate all gradients for W_hh
grad(W_hh) = v * h_2 + v * W_hh * h_1 + v * W_hh * W_hh * h_0

# If W_hh is initialized too big >> 1.0, grad(W_hh) explode quickly (-> infinity).
# If W_hh is initialized too small << 1.0, grad(W_hh) vanishes quickly (-> 0), since h_2, h_1 are vanishing after each forward step (exponentially)

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

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

bs = 121
seq_len = 121
new_seq_len = seq_len // k # k = 2, 2.5 or anything to experiment

X (of [bs,seq_len, 1]) -> [ X1[bs, new_seq_len, 1], X2[bs, new_seq_len, 1],...]

Затем вы передаете каждую маленькую партию Xi в модель так, чтобы начальное скрытое значение было h_(i-1), которое является скрытым выходом предыдущей партии `X (i-1)

h_i = model(Xi, h_(i-1))

Так что это поможет модели выучить некоторую длительную зависимость как модель 121 временных шагов.

...