我正在使用时间序列数据处理lstm,并且发现网络梯度存在问题。我一层有121个lstm单元。对于每个单元格,我都有一个输入值,而我得到一个输出值。我使用的批处理大小为121个值,并使用batch_first = True定义lstm单元,因此我的输出为[batch,timestep,features]。
一旦有了输出(大小为[121,121,1]的张量),我就使用MSELoss()计算损耗,然后反向传播它。这里出现了问题。查看每个单元的梯度,我注意到前100个单元(或多或少)的梯度为零。
理论上,如果我没记错的话,当我向后传播错误时,我会为每个输出计算一个梯度,因此我为每个单元格都有一个梯度。如果是这样,我不明白为什么在第一个单元格中它们为零。
有人知道发生了什么吗?
谢谢!
更新: 正如我之前尝试询问的那样,我仍然对LSTM反向传播有疑问。从下图可以看到,在一个单元格中,除了来自其他单元格的渐变之外,我认为本身也存在另一种渐变形式。
例如,让我们看一下单元格1。得到输出y1,然后计算损耗E1。我对其他单元格也一样。因此,当我在单元1中反向传播时,我得到dE2/dy2 * dy2/dh1 * dh1/dw1 + ...
,它们是与网络(BPTT)中的后续单元相关的渐变,如@ kmario23和@DavidNg所述。而且我还具有与E1(dE1/dy1 * dy1/dw1
)有关的渐变。在流动过程中,第一个梯度可能消失,但这个没有。
总而言之,尽管lstm单元层很长,但据我了解,我的梯度仅与每个单元有关,因此我不明白为什么梯度等于零。与E1相关的错误会如何处理?为什么只计算bptt?
答案 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_ih
和W_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
时间步长模型中的一些长期依赖性。