8.4. 循环神经网络¶ Open the notebook in SageMaker Studio Lab
在 8.3节中, 我们介绍了\(n\)元语法模型, 其中单词\(x_t\)在时间步\(t\)的条件概率仅取决于前面\(n-1\)个单词。 对于时间步\(t-(n-1)\)之前的单词, 如果我们想将其可能产生的影响合并到\(x_t\)上, 需要增加\(n\),然而模型参数的数量也会随之呈指数增长, 因为词表\(\mathcal{V}\)需要存储\(|\mathcal{V}|^n\)个数字, 因此与其将\(P(x_t \mid x_{t-1}, \ldots, x_{t-n+1})\)模型化, 不如使用隐变量模型:
其中\(h_{t-1}\)是隐状态(hidden state), 也称为隐藏变量(hidden variable), 它存储了到时间步\(t-1\)的序列信息。 通常,我们可以基于当前输入\(x_{t}\)和先前隐状态\(h_{t-1}\) 来计算时间步\(t\)处的任何时间的隐状态:
对于 (8.4.2)中的函数\(f\),隐变量模型不是近似值。 毕竟\(h_t\)是可以仅仅存储到目前为止观察到的所有数据, 然而这样的操作可能会使计算和存储的代价都变得昂贵。
回想一下,我们在 4节中 讨论过的具有隐藏单元的隐藏层。 值得注意的是,隐藏层和隐状态指的是两个截然不同的概念。 如上所述,隐藏层是在从输入到输出的路径上(以观测角度来理解)的隐藏的层, 而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入, 并且这些状态只能通过先前时间步的数据来计算。
循环神经网络(recurrent neural networks,RNNs) 是具有隐状态的神经网络。 在介绍循环神经网络模型之前, 我们首先回顾 4.1节中介绍的多层感知机模型。
8.4.1. 无隐状态的神经网络¶
让我们来看一看只有单隐藏层的多层感知机。 设隐藏层的激活函数为\(\phi\), 给定一个小批量样本\(\mathbf{X} \in \mathbb{R}^{n \times d}\), 其中批量大小为\(n\),输入维度为\(d\), 则隐藏层的输出\(\mathbf{H} \in \mathbb{R}^{n \times h}\)通过下式计算:
在 (8.4.3)中, 我们拥有的隐藏层权重参数为\(\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}\), 偏置参数为\(\mathbf{b}_h \in \mathbb{R}^{1 \times h}\), 以及隐藏单元的数目为\(h\)。 因此求和时可以应用广播机制(见 2.1.3节)。 接下来,将隐藏变量\(\mathbf{H}\)用作输出层的输入。 输出层由下式给出:
其中,\(\mathbf{O} \in \mathbb{R}^{n \times q}\)是输出变量, \(\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}\)是权重参数, \(\mathbf{b}_q \in \mathbb{R}^{1 \times q}\)是输出层的偏置参数。 如果是分类问题,我们可以用\(\text{softmax}(\mathbf{O})\) 来计算输出类别的概率分布。
这完全类似于之前在 8.1节中解决的回归问题, 因此我们省略了细节。 无须多言,只要可以随机选择“特征-标签”对, 并且通过自动微分和随机梯度下降能够学习网络参数就可以了。
8.4.3. 基于循环神经网络的字符级语言模型¶
回想一下 8.3节中的语言模型, 我们的目标是根据过去的和当前的词元预测下一个词元, 因此我们将原始序列移位一个词元作为标签。 Bengio等人首先提出使用神经网络进行语言建模 (Bengio et al., 2003)。 接下来,我们看一下如何使用循环神经网络来构建语言模型。 设小批量大小为1,批量中的文本序列为“machine”。 为了简化后续部分的训练,我们考虑使用 字符级语言模型(character-level language model), 将文本词元化为字符而不是单词。 图8.4.2演示了 如何通过基于字符级语言建模的循环神经网络, 使用当前的和先前的字符预测下一个字符。
在训练过程中,我们对每个时间步的输出层的输出进行softmax操作, 然后利用交叉熵损失计算模型输出和标签之间的误差。 由于隐藏层中隐状态的循环计算, 图8.4.2中的第\(3\)个时间步的输出\(\mathbf{O}_3\) 由文本序列“m”“a”和“c”确定。 由于训练数据中这个文本序列的下一个字符是“h”, 因此第\(3\)个时间步的损失将取决于下一个字符的概率分布, 而下一个字符是基于特征序列“m”“a”“c”和这个时间步的标签“h”生成的。
在实践中,我们使用的批量大小为\(n>1\), 每个词元都由一个\(d\)维向量表示。 因此,在时间步\(t\)输入\(\mathbf X_t\)将是一个\(n\times d\)矩阵, 这与我们在 8.4.2节中的讨论相同。
8.4.4. 困惑度(Perplexity)¶
最后,让我们讨论如何度量语言模型的质量, 这将在后续部分中用于评估基于循环神经网络的模型。 一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。 考虑一下由不同的语言模型给出的对“It is raining …”(“…下雨了”)的续写:
“It is raining outside”(外面下雨了);
“It is raining banana tree”(香蕉树下雨了);
“It is raining piouw;kcj pwepoiut”(piouw;kcj pwepoiut下雨了)。
就质量而言,例\(1\)显然是最合乎情理、在逻辑上最连贯的。 虽然这个模型可能没有很准确地反映出后续词的语义, 比如,“It is raining in San Francisco”(旧金山下雨了) 和“It is raining in winter”(冬天下雨了) 可能才是更完美的合理扩展, 但该模型已经能够捕捉到跟在后面的是哪类单词。 例\(2\)则要糟糕得多,因为其产生了一个无意义的续写。 尽管如此,至少该模型已经学会了如何拼写单词, 以及单词之间的某种程度的相关性。 最后,例\(3\)表明了训练不足的模型是无法正确地拟合数据的。
我们可以通过计算序列的似然概率来度量模型的质量。 然而这是一个难以理解、难以比较的数字。 毕竟,较短的序列比较长的序列更有可能出现, 因此评估模型产生托尔斯泰的巨著《战争与和平》的可能性 不可避免地会比产生圣埃克苏佩里的中篇小说《小王子》可能性要小得多。 而缺少的可能性值相当于平均数。
在这里,信息论可以派上用场了。 我们在引入softmax回归 ( 3.4.7节)时定义了熵、惊异和交叉熵, 并在信息论的在线附录 中讨论了更多的信息论知识。 如果想要压缩文本,我们可以根据当前词元集预测的下一个词元。 一个更好的语言模型应该能让我们更准确地预测下一个词元。 因此,它应该允许我们在压缩序列时花费更少的比特。 所以我们可以通过一个序列中所有的\(n\)个词元的交叉熵损失的平均值来衡量:
其中\(P\)由语言模型给出, \(x_t\)是在时间步\(t\)从该序列中观察到的实际词元。 这使得不同长度的文档的性能具有了可比性。 由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量。 简而言之,它是 (8.4.7)的指数:
困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”。 我们看看一些案例。
在最好的情况下,模型总是完美地估计标签词元的概率为1。 在这种情况下,模型的困惑度为1。
在最坏的情况下,模型总是预测标签词元的概率为0。 在这种情况下,困惑度是正无穷大。
在基线上,该模型的预测是词表的所有可用词元上的均匀分布。 在这种情况下,困惑度等于词表中唯一词元的数量。 事实上,如果我们在没有任何压缩的情况下存储序列, 这将是我们能做的最好的编码方式。 因此,这种方式提供了一个重要的上限, 而任何实际模型都必须超越这个上限。
在接下来的小节中,我们将基于循环神经网络实现字符级语言模型, 并使用困惑度来评估这样的模型。
8.4.5. 小结¶
对隐状态使用循环计算的神经网络称为循环神经网络(RNN)。
循环神经网络的隐状态可以捕获直到当前时间步序列的历史信息。
循环神经网络模型的参数数量不会随着时间步的增加而增加。
我们可以使用循环神经网络创建字符级语言模型。
我们可以使用困惑度来评价语言模型的质量。
8.4.6. 练习¶
如果我们使用循环神经网络来预测文本序列中的下一个字符,那么任意输出所需的维度是多少?
为什么循环神经网络可以基于文本序列中所有先前的词元,在某个时间步表示当前词元的条件概率?
如果基于一个长序列进行反向传播,梯度会发生什么状况?
与本节中描述的语言模型相关的问题有哪些?