当前位置:   article > 正文

《深度学习进阶:自然语言处理(第6章)》-读书笔记_matmul 节点的反向传播

matmul 节点的反向传播

第6章 Gated RNN

6.1 RNN的问题

RNN 之所以不擅长学习时序数据的长期依赖关系,是因为 BPTT 会发生梯度消失和梯度爆炸的问题。

  • 梯度消失和梯度爆炸

考虑长度为 T 的时序数据,关注从第 T 个正确解标签传递出的梯度如何变化。此时,关注时间方向上的梯度,可知反向传播的梯度流经 tanh、“+”和 MatMul(矩阵乘积)运算。“+”的反向传播将上游传来的梯度原样传给下游,因此梯度的值不变。那么,剩下的 tanh 和 MatMul 运算会怎样变化呢?

tanh:当 y = tanh ⁡ ( x ) y = \tanh(x) y=tanh(x) 时,它的导数是 d y d x = 1 − y 2 \frac{dy}{dx} = 1 − y^2 dxdy=1y2 。它的值小于 1.0,并且随着 x 远离 0,它的值在变小。这意味着,当反向传播的梯度经过 tanh 节点时,它的值会越来越小。因此,如果经过 tanh 函数 T 次,则梯度也会减小 T 次。(梯度消失)

MatMul:假定从上游传来梯度 d h d\mathbf{h} dh,此时 MatMul 节点的反向传播通过矩阵乘积 d h W h T d\mathbf{h}\mathbf{W}_h^T dhWhT 计算梯度。之后,根据时序数据的时间步长,将这个计算重复相应次数。梯度的大小随时间步长呈指数级增加,这就是梯度爆炸(exploding gradients)。也可能梯度呈指数级减小,这就是梯度消失(vanishing gradients)。

  • 梯度爆炸的对策

解决梯度爆炸有既定的方法,称为梯度裁剪(gradients clipping)。这是一个非常简单的方法,它的伪代码如下所示:
i f          ∥ g ^ ∥ ≥ t h r e s h o l d : g ^ = t h r e s h o l d ∥ g ^ ∥ g ^

if        g^threshold:g^=thresholdg^g^
if        g^threshold:g^=g^thresholdg^
这里假设可以将神经网络用到的所有参数的梯度整合成一个,并用符号 g ^ \hat{g} g^ 表示。另外,将阈值设置为 threshold。此时,如果梯度的 L2 范数大于或等于阈值,就按上述方法修正梯度,这就是梯度裁剪。

6.2 梯度消失和LSTM

  • LSTM的接口

RNN层与LSTM层的比较

LSTM 与 RNN 的接口的不同之处在于,LSTM 还有路径 c。这个 c 称为记忆单元(或者简称为“单元”),相当于 LSTM 专用的记忆部门。

记忆单元的特点是,仅在 LSTM 层内部接收和传递数据。也就是说,记忆单元在 LSTM 层内部结束工作,不向其他层输出。而 LSTM 的隐藏状态 h 和 RNN 层相同,会被(向上)输出到其他层。

  • 输出门

LSTM层基于记忆单元ct计算隐藏状态ht

LSTM 有记忆单元 c t c_t ct。这个 c t c_t ct 存储了时刻 t 时 LSTM 的记忆,可以认为其中保存了从过去到时刻 t 的所有必要信息(或者以此为目的进行了学习)。然后,基于这个充满必要信息的记忆,向外部的层(和下一时刻的 LSTM)输出隐藏状态 h t h_t ht。上图为 LSTM 输出经 tanh 函数变换后的记忆单元。

添加输出门

隐藏状态 h t h_t ht 对记忆单元 c t c_t ct 仅仅应用了 tanh 函数。这里考虑对 t a n h ( c t ) tanh(c_t) tanh(ct) 施加门。换句话说,针对 t a n h ( c t ) tanh(c_t) tanh(ct) 的各个元素,调整它们作为下一时刻的隐藏状态的重要程度。由于这个门管理下一个隐藏状态 h t h_t ht 的输出,所以称为 输出门(output gate)。由下式计算:
o = σ ( x t W x ( o ) + h t − 1 W h ( o ) + b ( o ) ) \mathbf{o}=\sigma(\mathbf{x}_t\mathbf{W}_x^{(o)}+\mathbf{h}_{t-1}\mathbf{W}_h^{(o)}+\mathbf{b}^{(o)}) o=σ(xtWx(o)+ht1Wh(o)+b(o))
h t h_t ht 可由 o \mathbf{o} o t a n h ( c t ) tanh(c_t) tanh(ct) 的乘积计算出来。这里说的“乘积”是对应元素的乘积,也称为阿达玛乘积。计算如下所示: h t = o ⊙ tanh ⁡ ( c t ) \mathbf{h}_t=\mathbf{o}\odot\tanh(\mathbf{c}_t) ht=otanh(ct)

  • 遗忘门

添加遗忘门

在记忆单元 c t − 1 c_{t−1} ct1 上添加一个忘记不必要记忆的门,这里称为遗忘门(forget gate)

将遗忘门进行的一系列计算表示为 σ \sigma σ,其中有遗忘门专用的权重参数,此时的计算如下:
f = σ ( x t W x ( f ) + h t − 1 W h ( f ) + b ( f ) ) \mathbf{f}=\sigma(\mathbf{x}_t\mathbf{W}_x^{(f)}+\mathbf{h}_{t-1}\mathbf{W}_h^{(f)}+\mathbf{b}^{(f)}) f=σ(xtWx(f)+ht1Wh(f)+b(f))
然后, c t c_t ct 由这个 f \mathbf{f} f 和上一个记忆单元 c t − 1 c_{t−1} ct1 的对应元素的乘积求得: c t = f ⊙ c t − 1 \mathbf{c}_t=\mathbf{f}\odot\mathbf{c}_{t-1} ct=fct1

  • 新的记忆单元

现在我们还想向这个记忆单元添加一些应当记住的新信息,为此我们添加新的 tanh 节点。

向新的记忆单元添加必要信息

基于 tanh 节点计算出的结果被加到上一时刻的记忆单元 c t − 1 c_{t−1} ct1 上。这样一来,新的信息就被添加到了记忆单元中。这个 tanh 节点的作用不是门,而是将新的信息添加到记忆单元中。因此,它不用 sigmoid 函数作为激活函数,而是使用 tanh 函数。tanh 节点进行的计算如下所示:
g = tanh ⁡ ( x t W x ( g ) + h t − 1 W h ( g ) + b ( g ) ) \mathbf{g}=\tanh(\mathbf{x}_t\mathbf{W}_x^{(g)}+\mathbf{h}_{t-1}\mathbf{W}_h^{(g)}+\mathbf{b}^{(g)}) g=tanh(xtWx(g)+ht1Wh(g)+b(g))

  • 输入门

添加输入门

输入门判断新增信息 g 的各个元素的价值有多大。输入门不会不经考虑就添加新信息,而是会对要添加的信息进行取舍。此时进行的计算如下
所示:
i = σ ( x t W x ( i ) + h t − 1 W h ( i ) + b ( i ) ) \mathbf{i}=\sigma(\mathbf{x}_t\mathbf{W}_x^{(i)}+\mathbf{h}_{t-1}\mathbf{W}_h^{(i)}+\mathbf{b}^{(i)}) i=σ(xtWx(i)+ht1Wh(i)+b(i))

为什么LSTM不会引起梯度消失呢?

我们仅关注记忆单元此时,记忆单元的反向传播仅流过“+”和“×”节点。“+”节点将上游传来的梯度原样流出,所以梯度没有变化(退化)。

而“×”节点的计算并不是矩阵乘积,而是对应元素的乘积(阿达玛积)。这里的 LSTM 的反向传播进行的不是矩阵乘积计算,而是对应元素的乘积计算,而且每次都会基于不同的门值进行对应元素的乘积计算。这就是它不会发生梯度消失(或梯度爆炸)的原因。

“×”节点的计算由遗忘门控制(每次输出不同的门值)。遗忘门认为“应该忘记”的记忆单元的元素,其梯度会变小;而遗忘门认为“不能忘记”的元素,其梯度在向过去的方向流动时不会退化。因此,LSTM的记忆单元不会(难以)发生梯度消失,可以期待记忆单元能够保存(学习)长期的依赖关系

6.3 LSTM的实现

class LSTM:
    def __init__(self, Wx, Wh, b):
		"""
		Wx: 输入`x`用的权重参数(整合了4个权重)
        Wh: 隐藏状态`h`用的权重参数(整合了4个权重)
        b: 偏置(整合了4个偏置)
		"""
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.cache = None
    def forward(self, x, h_prev, c_prev):
        Wx, Wh, b = self.params
        N, H = h_prev.shape
        A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b
        f = A[:, :H]
        g = A[:, H:2*H]
        i = A[:, 2*H:3*H]
        o = A[:, 3*H:]
        f = sigmoid(f)
        g = np.tanh(g)
        i = sigmoid(i)
        o = sigmoid(o)
        c_next = f * c_prev + g * i
        h_next = o * np.tanh(c_next)
        self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
        return h_next, c_next
    def backward(self, dh_next, dc_next):
        Wx, Wh, b = self.params
        x, h_prev, c_prev, i, f, g, o, c_next = self.cache
        tanh_c_next = np.tanh(c_next)
        ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)
        dc_prev = ds * f
        di = ds * g
        df = ds * c_prev
        do = dh_next * tanh_c_next
        dg = ds * i
        di *= i * (1 - i)
        df *= f * (1 - f)
        do *= o * (1 - o)
        dg *= (1 - g ** 2)
        dA = np.hstack((df, dg, di, do))
        dWh = np.dot(h_prev.T, dA)
        dWx = np.dot(x.T, dA)
        db = dA.sum(axis=0)
        self.grads[0][...] = dWx
        self.grads[1][...] = dWh
        self.grads[2][...] = db
        dx = np.dot(dA, Wx.T)
        dh_prev = np.dot(dA, Wh.T)
        return dx, dh_prev, dc_prev
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
class TimeLSTM:
    def __init__(self, Wx, Wh, b, stateful=False):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        self.layers = None
        self.h, self.c = None, None
        self.dh = None
        self.stateful = stateful
    def forward(self, xs):
        Wx, Wh, b = self.params
        N, T, D = xs.shape
        H = Wh.shape[0]
        self.layers = []
        hs = np.empty((N, T, H), dtype='f')
        if not self.stateful or self.h is None:
            self.h = np.zeros((N, H), dtype='f')
        if not self.stateful or self.c is None:
            self.c = np.zeros((N, H), dtype='f')
        for t in range(T):
            layer = LSTM(*self.params)
            self.h, self.c = layer.forward(xs[:, t, :], self.h, self.c)
            hs[:, t, :] = self.h
            self.layers.append(layer)
        return hs
    def backward(self, dhs):
        Wx, Wh, b = self.params
        N, T, H = dhs.shape
        D = Wx.shape[0]
        dxs = np.empty((N, T, D), dtype='f')
        dh, dc = 0, 0
        grads = [0, 0, 0]
        for t in reversed(range(T)):
            layer = self.layers[t]
            dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc)
            dxs[:, t, :] = dx
            for i, grad in enumerate(layer.grads):
                grads[i] += grad
        for i, grad in enumerate(grads):
            self.grads[i][...] = grad
        self.dh = dh
        return dxs
    def set_state(self, h, c=None):
        self.h, self.c = h, c
    def reset_state(self):
        self.h, self.c = None, None
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

6.5 进一步改进RNNLM

  • LSTM层的多层化:在使用 RNNLM 创建高精度模型时,加深 LSTM 层(叠加多个 LSTM 层)的方法往往很有效。通过叠加多个层,可以提高语言模型的精度。
  • 基于Dropout抑制过拟合:通过叠加 LSTM 层,可以期待能够学习到时序数据的复杂依赖关系。换句话说,通过加深层,可以创建表现力更强的模型,但是这样的模型往往会发生过拟合(overfitting)。
    • 抑制过拟合已有既定的方法:一是增加训练数据;二是降低模型的复杂度。三是对模型复杂度给予惩罚的正则化。
  • 权重共享:绑定(共享)Embedding 层和 Affine 层的权重的技巧在于权重共享。通过在这两个层之间共享权重,可以大大减少学习的参数数量。尽管如此,它仍能提高精度。

继续阅读:
《深度学习进阶:自然语言处理(第1章)》-读书笔记
《深度学习进阶:自然语言处理(第2章)》-读书笔记
《深度学习进阶:自然语言处理(第3章)》-读书笔记
《深度学习进阶:自然语言处理(第4章)》-读书笔记
《深度学习进阶:自然语言处理(第5章)》-读书笔记
《深度学习进阶:自然语言处理(第6章)》-读书笔记
《深度学习进阶:自然语言处理(第7章)》-读书笔记
《深度学习进阶:自然语言处理(第8章)》-读书笔记

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/667173
推荐阅读
相关标签
  

闽ICP备14008679号