【问题标题】:LSTM RNN BackpropagationLSTM RNN 反向传播
【发布时间】:2017-05-24 03:50:45
【问题描述】:

有人可以清楚地解释 LSTM RNN 的反向传播吗? 这是我正在使用的类型结构。我的问题不是关于什么是反向传播,我理解它是一种逆序方法,用于计算用于调整神经网络权重的假设和输出的误差。我的问题是 LSTM 反向传播与常规神经网络有何不同。

我不确定如何找到每个门的初始误差。您是否对每个门使用第一个错误(由假设减去输出计算)?或者你是否通过一些计算来调整每个门的误差?我不确定单元状态如何在 LSTM 的反向传播中发挥作用。我已经彻底寻找了 LSTM 的良好来源,但还没有找到。

【问题讨论】:

标签: machine-learning neural-network lstm recurrent-neural-network backpropagation


【解决方案1】:

我认为您的问题无法在简短的回答中得到解答。 Nico 的simple LSTM 有一个来自 Lipton 等人的优秀论文的链接,请阅读此内容。他的简单 python 代码示例也有助于回答您的大部分问题。 如果你明白 Nico 的最后一句话 ds = self.state.o * top_diff_h + top_diff_s 详细的,请给我反馈。目前,我对他的“将所有这些 s 和 h 派生放在一起”有一个最后的问题。

【讨论】:

    【解决方案2】:

    这是个好问题。您当然应该查看建议的帖子以了解详细信息,但此处的完整示例也会有所帮助。

    RNN 反向传播

    我认为先讲一个普通的RNN(因为LSTM图特别容易混淆)并理解它的反向传播是有意义的。

    在反向传播方面,关键思想是网络展开,这是将 RNN 中的递归转换为前馈序列的方法(如上图所示)。请注意,抽象 RNN 是永恒的(可以任意大),但是每个特定的实现都是有限的,因为内存是有限的。因此,展开的网络确实一个长的前馈网络,几乎没有复杂性,例如不同层的权重是共享的。

    我们来看一个经典的例子,char-rnn by Andrej Karpathy。这里每个 RNN 单元格通过以下公式产生两个输出 h[t](输入下一个单元格的状态)和 y[t](此步骤的输出),其中 WxhWhhWhy 是共享参数:

    在代码中,就是三个矩阵和两个偏置向量:

    # model parameters
    Wxh = np.random.randn(hidden_size, vocab_size)*0.01 # input to hidden
    Whh = np.random.randn(hidden_size, hidden_size)*0.01 # hidden to hidden
    Why = np.random.randn(vocab_size, hidden_size)*0.01 # hidden to output
    bh = np.zeros((hidden_size, 1)) # hidden bias
    by = np.zeros((vocab_size, 1)) # output bias
    

    前向传播非常简单,这个例子使用了 softmax 和交叉熵损失。注意每次迭代都使用相同的W*h* 数组,但输出和隐藏状态不同:

    # forward pass
    for t in xrange(len(inputs)):
      xs[t] = np.zeros((vocab_size,1)) # encode in 1-of-k representation
      xs[t][inputs[t]] = 1
      hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state
      ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars
      ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars
      loss += -np.log(ps[t][targets[t],0]) # softmax (cross-entropy loss)
    

    现在,向后传递的执行与前馈网络完全一样,但W*h* 数组的梯度会累积所有单元格中的梯度:

    for t in reversed(xrange(len(inputs))):
      dy = np.copy(ps[t])
      dy[targets[t]] -= 1
      dWhy += np.dot(dy, hs[t].T)
      dby += dy
      dh = np.dot(Why.T, dy) + dhnext # backprop into h
      dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity
      dbh += dhraw
      dWxh += np.dot(dhraw, xs[t].T)
      dWhh += np.dot(dhraw, hs[t-1].T)
      dhnext = np.dot(Whh.T, dhraw)
    

    上面的两次传递都是在大小为len(inputs)的块中完成的,它对应于展开的RNN的大小。您可能希望使其更大以捕获输入中更长的依赖关系,但您需要通过存储每个单元格的所有输出和梯度来为此付出代价。

    LSTM 的不同之处

    LSTM 图片和公式看起来很吓人,但是一旦你编写了普通的 RNN,LSTM 的实现几乎是一样的。例如,这里是向后传递:

    # Loop over all cells, like before
    d_h_next_t = np.zeros((N, H))
    d_c_next_t = np.zeros((N, H))
    for t in reversed(xrange(T)):
      d_x_t, d_h_prev_t, d_c_prev_t, d_Wx_t, d_Wh_t, d_b_t = lstm_step_backward(d_h_next_t + d_h[:,t,:], d_c_next_t, cache[t])
      d_c_next_t = d_c_prev_t
      d_h_next_t = d_h_prev_t
    
      d_x[:,t,:] = d_x_t
      d_h0 = d_h_prev_t
      d_Wx += d_Wx_t
      d_Wh += d_Wh_t
      d_b += d_b_t
    
    # The step in each cell
    # Captures all LSTM complexity in few formulas.
    def lstm_step_backward(d_next_h, d_next_c, cache):
      """
      Backward pass for a single timestep of an LSTM.
    
      Inputs:
      - dnext_h: Gradients of next hidden state, of shape (N, H)
      - dnext_c: Gradients of next cell state, of shape (N, H)
      - cache: Values from the forward pass
    
      Returns a tuple of:
      - dx: Gradient of input data, of shape (N, D)
      - dprev_h: Gradient of previous hidden state, of shape (N, H)
      - dprev_c: Gradient of previous cell state, of shape (N, H)
      - dWx: Gradient of input-to-hidden weights, of shape (D, 4H)
      - dWh: Gradient of hidden-to-hidden weights, of shape (H, 4H)
      - db: Gradient of biases, of shape (4H,)
      """
      x, prev_h, prev_c, Wx, Wh, a, i, f, o, g, next_c, z, next_h = cache
    
      d_z = o * d_next_h
      d_o = z * d_next_h
      d_next_c += (1 - z * z) * d_z
    
      d_f = d_next_c * prev_c
      d_prev_c = d_next_c * f
      d_i = d_next_c * g
      d_g = d_next_c * i
    
      d_a_g = (1 - g * g) * d_g
      d_a_o = o * (1 - o) * d_o
      d_a_f = f * (1 - f) * d_f
      d_a_i = i * (1 - i) * d_i
      d_a = np.concatenate((d_a_i, d_a_f, d_a_o, d_a_g), axis=1)
    
      d_prev_h = d_a.dot(Wh.T)
      d_Wh = prev_h.T.dot(d_a)
    
      d_x = d_a.dot(Wx.T)
      d_Wx = x.T.dot(d_a)
    
      d_b = np.sum(d_a, axis=0)
    
      return d_x, d_prev_h, d_prev_c, d_Wx, d_Wh, d_b
    

    总结

    现在,回到你的问题。

    我的问题是 LSTM 反向传播与常规神经网络有何不同

    它们是不同层中的共享权重,还有一些您需要注意的额外变量(状态)。除此之外,没有任何区别。

    您是否对每个门使用第一个错误(通过假设减去输出计算)?还是你通过一些计算来调整每个门的误差?

    首先,损失函数不一定是 L2。在上面的例子中,它是一个交叉熵损失,所以初始误差信号得到它的梯度:

    # remember that ps is the probability distribution from the forward pass
    dy = np.copy(ps[t])  
    dy[targets[t]] -= 1
    

    请注意,它与普通前馈神经网络中的误差信号相同。如果使用 L2 loss,则信号确实等于 ground-truth 减去实际输出。

    在 LSTM 的情况下,它稍微复杂一些:d_next_h = d_h_next_t + d_h[:,t,:],其中d_h 是上游梯度损失函数,这意味着每个单元的误差信号都会累积。但是再一次,如果你展开 LSTM,你会看到与网络布线的直接对应关系。

    【讨论】:

    • 我无法理解您为什么在此处添加这些数量:dh = np.dot(Why.T, dy) + dhnext?据我了解,np.dot(Why.T, dy) 是计算出的梯度,需要反向传播到前一个时间步。 “上游梯度”到底是什么意思,为什么要添加它?谢谢。
    • 我知道这有一段时间了,但我想我会回应,因为我刚刚发现了这一点。向量dhnext 包含从级别 t+1 传播到级别 t 的梯度。由于h_{t+1} 依赖于h_t,当您计算h_t 的反向传播梯度时,有两个分量:步骤t 的误差梯度和步骤t+1 的误差梯度。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 2017-01-20
    • 2016-04-09
    • 2017-07-27
    • 2023-03-29
    相关资源
    最近更新 更多