Я имел дело с этими проблемами несколько раз. И вот мой совет:
Использовать меньшее количество временных шагов
Скрытый вывод предыдущего временного шага передается текущим шагам и умножается на весовые коэффициенты. Когда вы умножаетесь несколько раз, градиент будет взрываться или исчезать экспоненциально с количеством временных шагов.
Допустим,
# 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
временных шагов.