如何使用 Pytorch 优化 LSTM 中的梯度流?

How can I optimize gradient flow in LSTM with Pytorch?

我在 lstm 中处理时间序列数据,我发现我的网络梯度存在问题。我有一层 121 个 lstm 单元。对于每个单元格,我有一个输入值,我得到一个输出值。我使用 121 个值的批量大小,并使用 batch_first = True 定义 lstm 单元,所以我的输出是 [batch,timestep,features].

获得输出(大小为 [121,121,1] 的张量)后,我使用 MSELoss() 计算损失并反向传播它。这里出现了问题。查看每个单元格的梯度,我注意到前 100 个单元格(或多或少)的梯度为空。

理论上,如果我没记错的话,当我反向传播错误时,我会为每个输出计算一个梯度,所以每个单元格都有一个梯度。如果那是真的,我不明白为什么在第一个单元格中它们为零。

有人知道发生了什么事吗?

谢谢!

PS.: 我给你看最后一个单元格的梯度流:


更新: 正如我之前尝试询问的那样,我仍然对 LSTM 反向传播有疑问。从下图中可以看出,在一个单元格中,除了来自其他单元格的渐变之外,我认为还有另一种渐变形式。

例如,让我们看一下单元格 1。我得到输出 y1 并计算损失 E1。我对其他细胞也这样做。因此,当我在单元格 1 中反向传播时,我得到 dE2/dy2 * dy2/dh1 * dh1/dw1 + ... 这是与网络中的后续单元格 (BPTT) 相关的梯度,正如@kmario23 和@DavidNg 所解释的那样。我还有与 E1 (dE1/dy1 * dy1/dw1) 相关的梯度。第一个梯度可以在流动过程中消失,但这个不会。

所以,总而言之,虽然有很长的 lstm 单元层,但据我所知,我有一个只与每个单元相关的梯度,因此我不明白为什么我的梯度等于零。与 E1 相关的错误会发生什么?为什么只计算bptt?

这些问题我都处理过好几次了。这是我的建议:

Use smaller number of timesteps

前一个时间步的隐藏输出被传递给当前步骤并乘以权重。当你相乘数次时,梯度会随着时间步长呈指数级增长或消失。 比方说:

# 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_ihW_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 单元有不同的门(比如 forget gate 减少时间步长无关的依赖)来缓解这些问题,但它会受到时间步长的影响。如何设计用于学习长依赖的网络架构对于时序数据来说仍然是一个大问题。

为避免这些问题,只需将时间步数 (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 时间步长的模型。