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

时间:2019-04-27 17:37:14

标签: time-series lstm pytorch recurrent-neural-network

我正在使用时间序列数据处理lstm,并且发现网络梯度存在问题。我一层有121个lstm单元。对于每个单元格,我都有一个输入值,而我得到一个输出值。我使用的批处理大小为121个值,并使用batch_first = True定义lstm单元,因此我的输出为[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 :(得分:0)

我已经多次处理这些问题。这是我的建议:

  

使用较少的时间步长

上一个时间步的隐藏输出将传递到当前步并乘以权重。当您乘以几倍时,渐变将随着时间步长的数量而爆炸或消失。 假设:

# 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激活。

如果我们沿着所有时间步骤K=3展开RNN,我们将得到:

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时间步长模型中的一些长期依赖性。