chap8 循环神经网络(4) RNN

在上一小节中,我们介绍了 nn 元语法模型,其中单词 xtx_t 在时间步 tt 的条件概率仅取决于前面 n1n-1 个单词。如果我们想将时间步 t(n1)t-(n-1) 之前的单词的可能产生的影响合并到 xtx_t 上就需要增加 nn,然而模型参数的数量也会随之呈指数增长,因为词表 V\mathcal{V} 需要存储 Vn|\mathcal{V}|^n 个数字,因此与其将 P(xtxt1,,xtn+1)P(x_t \mid x_{t-1}, \ldots, x_{t-n+1}) 模型化,不如使用隐变量模型:

P(xtxt1,,x1)P(xtht1),P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}),

其中 ht1h_{t-1}隐藏状态(也称为隐藏变量),其存储了到时间步 t1t-1 的序列信息。通常,可以基于当前输入 xtx_{t} 和先前隐藏状态 ht1h_{t-1} 来计算时间步 tt 处的任何时间的隐藏状态:

ht=f(xt,ht1)h_t = f(x_{t}, h_{t-1})

:eqlabel:eq_ht_xt

对于一个足够强大的函数 ff( :eqref:eq_ht_xt ),隐变量模型不是近似值。毕竟 hth_t 是可以仅仅存储到目前为止观察到的所有数据,然而这样的操作可能会使计算和存储的代价都变得昂贵。

回想一下,我们在 第四章 中讨论过的具有隐藏单元的隐藏层。值得注意的是,隐藏层和隐藏状态指的是两个截然不同的概念。如上所述,隐藏层是在输入到输出的路径上以观测角度来理解的隐藏的层,而隐藏状态则是在给定步骤所做的任何事情以技术角度来定义的 输入,并且这些状态只能通过先前时间步的数据来计算。

循环神经网络(Recurrent neural networks, RNNs)是具有隐藏状态的神经网络。

循环神经网络

循环神经网络指网络的隐含层输出又作为自身的输入。

假设我们在时间步tt有小批量输入XtRn×d\mathbf{X}_t \in \mathbb{R}^{n \times d}。换言之,对于nn个序列样本的小批量,Xt\mathbf{X}_t的每一行对应于来自该序列的时间步tt处的一个样本。接下来,用HtRn×h\mathbf{H}_t \in \mathbb{R}^{n \times h}表示时间步tt的隐藏变量。与多层感知机不同的是,我们在这里保存了前一个时间步的隐藏变量Ht1\mathbf{H}_{t-1},并引入了一个**新的权重参数WhhRh×h\mathbf{W}_{hh} \in \mathbb{R}^{h \times h}**来描述如何在当前时间步中使用前一个时间步的隐藏变量。具体地说,当前时间步隐藏变量的计算由当前时间步的输入与前一个时间步的隐藏变量一起确定:

Ht=ϕ(XtWxh+Ht1Whh+bh).\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h).

:eqlabel:rnn_h_with_state

与 无隐藏状态 相比, :eqref:rnn_h_with_state 多添加了一项Ht1Whh\mathbf{H}_{t-1} \mathbf{W}_{hh},从而实例化了 :eqref:eq_ht_xt从相邻时间步的隐藏变量Ht\mathbf{H}_tHt1\mathbf{H}_{t-1}之间的关系可知,这些变量捕获并保留了序列直到其当前时间步的历史信息,就如当前时间步下神经网络的状态或记忆,因此这样的隐藏变量被称为 隐藏状态(hidden state)。

**由于在当前时间步中隐藏状态使用的定义与前一个时间步中使用的定义相同,因此 :eqref:rnn_h_with_state 的计算是 循环的(recurrent)。**于是基于循环计算的隐状态神经网络被命名为 循环神经网络(recurrent neural networks)。在循环神经网络中执行 :eqref:rnn_h_with_state 计算的层称为 循环层(recurrent layers)。

有许多不同的方法可以构建循环神经网络,由 :eqref:rnn_h_with_state 定义的隐藏状态的循环神经网络是非常常见的一种。对于时间步tt,输出层的输出类似于多层感知机中的计算:

Ot=HtWhq+bq.\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q.

循环神经网络的参数包括隐藏层的权重WxhRd×h,WhhRh×h\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}, \mathbf{W}_{hh} \in \mathbb{R}^{h \times h}和偏置bhR1×h\mathbf{b}_h \in \mathbb{R}^{1 \times h},以及输出层的权重WhqRh×q\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}和偏置bqR1×q\mathbf{b}_q \in \mathbb{R}^{1 \times q}。值得一提的是,即使在不同的时间步,循环神经网络也总是使用这些模型参数。因此,循环神经网络的参数开销不会随着时间步的增加而增加。

上图展示了循环神经网络在三个相邻时间步的计算逻辑。

在任意时间步tt,隐藏状态的计算可以被视为:

1、拼接当前时间步tt的输入Xt\mathbf{X}_t和前一时间步t1t-1的隐藏状态Ht1\mathbf{H}_{t-1}

2、将拼接的结果送入带有激活函数ϕ\phi的全连接层。全连接层的输出是当前时间步tt的隐藏状态Ht\mathbf{H}_t。在本例中,模型参数是Wxh\mathbf{W}_{xh}Whh\mathbf{W}_{hh}的拼接,以及bh\mathbf{b}_h的偏置,所有这些参数都来自 :eqref:rnn_h_with_state。当前时间步tt的隐藏状态Ht\mathbf{H}_t将参与计算下一时间步t+1t+1的隐藏状态Ht+1\mathbf{H}_{t+1}。而且Ht\mathbf{H}_t还将送入全连接输出层用于计算当前时间步tt的输出Ot\mathbf{O}_t

困惑度

困惑度是度量语言模型质量的指标。

如果想要压缩文本,我们可以询问根据当前词元集预测的下一个词元。一个更好的语言模型应该能让我们更准确地预测下一个词元。因此,它应该允许我们在压缩序列时花费更少的比特。所以我们可以通过一个序列中所有的nn个词元的交叉熵损失的平均值来衡量:

1nt=1nlogP(xtxt1,,x1)\frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1)

:eqlabel:eq_avg_ce_for_lm

其中PP由语言模型给出,xtx_t 是在时间步tt从该序列中观察到的实际词元【label】。这使得不同长度的文档的性能具有了可比性。由于历史原因,自然语言处理的科学家更喜欢使用一个叫做 困惑度(perplexity)的量。简而言之,它是 :eqref:eq_avg_ce_for_lm 的指数:

exp(1nt=1nlogP(xtxt1,,x1))\exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right)

当我们决定下一个词元是哪个时,困惑度的最好的理解可以是下一个词元的实际选择数的调和平均数。让我们看看一些案例:

  • 在最好的情况下,模型总是完美地估计标签词元的概率为1。在这种情况下,模型的困惑度为1。

  • 在最坏的情况下,模型总是预测标签词元的概率为0。在这种情况下,困惑度是正无穷大。

  • 在基线上,该模型的预测是词汇表的所有可用词元上的均匀分布。在这种情况下,困惑度等于词汇表中唯一词元的数量。事实上,如果我们在没有任何压缩的情况下存储序列,这将是我们能做的最好的编码方式。因此,这种方式提供了一个重要的上限,而任何实际模型都必须超越这个上限。

梯度裁剪

  • 迭代计算这 T 个时间布上的梯度,在反向传播过程中产生 O(T)O(T) 的矩阵乘法链,导致数值不稳定

  • 梯度裁剪能有效预防梯度爆炸

    如果梯度长度超过 θ\theta ,则拖影回长度 θ\theta

gmin(1,θg)gg \gets min(1,\frac{\theta}{||g||} )g

总结

  • 循环神经网络的输出取决于当下输入和前一时间的隐变量
  • 应用到语言模型中时,循环神经网络根据当前词预测下一时刻词
  • 常使用困惑度来衡量语言模型的好坏