Halfish / cs231n

斯坦福 cs231n 作业代码实践
627 stars 226 forks source link

cs231n/assignment3/cs231n/rnn_layers.py/line:147 #4

Open liferlisiqi opened 6 years ago

liferlisiqi commented 6 years ago

请问使用上面写的rnn_step_backward反向传播时,为什么dnext_h参数是dh[:, t, :] + stepdprev_h呢?如下: stepdx, stepdprev_h, stepdWx, stepdWh, stepdb = rnn_step_backward(dh[:, t, :] + stepdprev_h, cache[t])

Halfish commented 6 years ago

这个是 truncate BPTT 算法,因为 RNN 公式中有一个 h_t = tanh(W_x x_t + W_h h_{t-1} + b),所以 t 时刻的交叉熵 L_t 不仅对 ht 有导数,而且对 h{t-1} 也有导数;递归的,又因为 h{t-1} 也包含了 h{t-2} 所以 Lt 对 h{t-2} 实际上也是有导数的。可以参考 Recurrent Neural Networks Tutorial, Part 3 – Backpropagation Through Time and Vanishing Gradients 的前两张图片展示结果。反向传播的结果和前向传播的箭头正好是相反的,可以看出来是有两个方向的梯度回传回来的。

这里的实现是只取 truncate_size = 1,比如 t 时刻交叉熵 L_t 对 h_t 的导数是变量 dh[:, t, :](从图上看,是从上往下的梯度回传),但是 t+1 时刻的交叉熵也对 h_t 有导数(从图上看,是从右往左的梯度回传),这个导数放在了 stepdpred_h 这个变量里。两者加起来才是真正的导数(其实这样讲不严谨,如果 truncate_size = 2,需要把 t+2 时刻的情况也算上了;如果是原始的 BPTT,需要从最后一个时刻开始回传导数,这样才是真正的导数)。

注意到 t 时刻的 rnn_stepbackward 算出了对 h{t-1} 的导数(从右往左的梯度回传),那么暂时存放在 stepdprevh 这个变量里,等到下时刻和交叉熵的导数加在一起(从上往下的梯度回传),才是 h{t-1} 真正的导数。

liferlisiqi commented 6 years ago

你的解释很详细,非常感谢。 你说的第三个点我好像明白了,是说h的导数是来自于下一时刻和自身两个方向是吧 然后第一、二点,你的意思是这里的实现是原始的BPTT的一种情况是吗?