n元语法模型, 其中单词xt在时间步t的条件概率仅取决于前面n−1个单词。 对于时间步t−(n−1)之前的单词, 如果我们想将其可能产生的影响合并到xt上, 需要增加n,然而模型参数的数量也会随之呈指数增长, 因为词表V需要存储个数字, 因此与其将P(xt∣xt−1,…,xt−n+1)模型化, 不如使用隐变量模型:
其中ht−1是隐状态(hidden state), 也称为隐藏变量(hidden variable), 它存储了到时间步t−1的序列信息。 通常,我们可以基于当前输入xt和先前隐状态ht−1 来计算时间步t处的任何时间的隐状态:
对于函数f,隐变量模型不是近似值。 毕竟ht是可以仅仅存储到目前为止观察到的所有数据, 然而这样的操作可能会使计算和存储的代价都变得昂贵。
隐藏层是在从输入到输出的路径上(以观测角度来理解)的隐藏的层, 而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入, 并且这些状态只能通过先前时间步的数据来计算。
循环神经网络(recurrent neural networks,RNNs) 是具有隐状态的神经网络。
对隐状态使用循环计算的神经网络称为循环神经网络(RNN)。
循环神经网络的隐状态可以捕获直到当前时间步序列的历史信息。
循环神经网络模型的参数数量不会随着时间步的增加而增加。
我们可以使用循环神经网络创建字符级语言模型。
我们可以使用困惑度来评价语言模型的质量。
目录
1.无隐状态的神经网络
2.有隐状态的循环神经网络
3.基于循环神经网络的字符级语言模型
4.困惑度
1.无隐状态的神经网络
我们可以通过在网络中加入一个或多个隐藏层来克服线性模型的限制, 使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前L−1层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。
2.有隐状态的循环神经网络
与 (8.4.3)相比, (8.4.5)多添加了一项 , 从而实例化了 (8.4.2)。 从相邻时间步的隐藏变量Ht和 Ht−1之间的关系可知, 这些变量捕获并保留了序列直到其当前时间步的历史信息, 就如当前时间步下神经网络的状态或记忆, 因此这样的隐藏变量被称为隐状态(hidden state)。 由于在当前时间步中, 隐状态使用的定义与前一个时间步中使用的定义相同, 因此 (8.4.5)的计算是循环的(recurrent)。 于是基于循环计算的隐状态神经网络被命名为 循环神经网络(recurrent neural network)。 在循环神经网络中执行 (8.4.5)计算的层 称为循环层(recurrent layer)。
有许多不同的方法可以构建循环神经网络, 由 (8.4.5)定义的隐状态的循环神经网络是非常常见的一种。 对于时间步t,输出层的输出类似于多层感知机中的计算:
循环神经网络的参数开销不会随着时间步的增加而增加。
图8.4.1展示了循环神经网络在三个相邻时间步的计算逻辑。 在任意时间步t,隐状态的计算可以被视为:
-
拼接当前时间步t的输入和前一时间步t−1的隐状态;
-
将拼接的结果送入带有激活函数ϕ的全连接层。 全连接层的输出是当前时间步t的隐状态
在本例中,模型参数是和的拼接, 以及的偏置,所有这些参数都来自 (8.4.5)。 当前时间步t的隐状态将参与计算下一时间步t+1的隐状态。 而且还将送入全连接输出层, 用于计算当前时间步t的输出。
我们刚才提到,隐状态中 +的计算, 相当于和的拼接 与和的拼接的矩阵乘法。 虽然这个性质可以通过数学证明, 但在下面我们使用一个简单的代码来说明一下。 首先,我们定义矩阵X
、W_xh
、H
和W_hh
, 它们的形状分别为,(3,1)、,(1,4)、,(3,4)和,(4,4)。 分别将X
乘以W_xh
,将H
乘以W_hh
, 然后将这两个乘法相加,我们得到一个形状为,(3,4)的矩阵。
pip install mxnet==1.7.0.post1
pip install d2l==0.17.6
from mxnet import np, npx
from d2l import mxnet as d2l
npx.set_np()
X, W_xh = np.random.normal(0, 1, (3, 1)), np.random.normal(0, 1, (1, 4))
H, W_hh = np.random.normal(0, 1, (3, 4)), np.random.normal(0, 1, (4, 4))
np.dot(X, W_xh) + np.dot(H, W_hh)
array([[-0.21952915, 4.256434 , 4.5812645 , -5.344988 ], [ 3.447858 , -3.0177274 , -1.6777471 , 7.535347 ], [ 2.2390068 , 1.4199957 , 4.744728 , -8.421293 ]])
现在,我们沿列(轴1)拼接矩阵X
和H
, 沿行(轴0)拼接矩阵W_xh
和W_hh
。 这两个拼接分别产生形状(3,5)和形状(5,4)的矩阵。 再将这两个拼接的矩阵相乘, 我们得到与上面相同形状(3,4)的输出矩阵。
np.dot(np.concatenate((X, H), 1), np.concatenate((W_xh, W_hh), 0))
array([[-0.21952918, 4.256434 , 4.5812645 , -5.344988 ], [ 3.4478583 , -3.0177271 , -1.677747 , 7.535347 ], [ 2.2390068 , 1.4199957 , 4.744728 , -8.421294 ]])
3.基于循环神经网络的字符级语言模型
语言模型, 我们的目标是根据过去的和当前的词元预测下一个词元, 因此我们将原始序列移位一个词元作为标签。 Bengio等人首先提出使用神经网络进行语言建模 (Bengio et al., 2003)。 接下来,我们看一下如何使用循环神经网络来构建语言模型。
设小批量大小为1,批量中的文本序列为“machine”。 为了简化后续部分的训练,我们考虑使用 字符级语言模型(character-level language model), 将文本词元化为字符而不是单词。 图8.4.2演示了 如何通过基于字符级语言建模的循环神经网络, 使用当前的和先前的字符预测下一个字符。
softmax是个非常常用而且比较重要的函数,尤其在多分类的场景中使用广泛。他把一些输入映射为0-1之间的实数,并且归一化保证和为1,因此多分类的概率之和也刚好为1。
交叉熵能够衡量同一个随机变量中的两个不同概率分布的差异程度,在机器学习中就表示为真实概率分布与预测概率分布之间的差异。
交叉熵的值越小,模型预测效果就越好。
交叉熵经常搭配softmax使用,将输出的结果进行处理,使其多个分类的预测值和为1,再通过交叉熵来计算损失。
我们可以从两方面来考虑交叉熵分类目标: (i)最大化观测数据的似然;(ii)最小化传达标签所需的惊异。
在训练过程中,我们对每个时间步的输出层的输出进行softmax操作, 然后利用交叉熵损失计算模型输出和标签之间的误差。 由于隐藏层中隐状态的循环计算, 图8.4.2中的第3个时间步的输出O3 由文本序列“m”“a”和“c”确定。 由于训练数据中这个文本序列的下一个字符是“h”, 因此第3个时间步的损失将取决于下一个字符的概率分布, 而下一个字符是基于特征序列“m”“a”“c”和这个时间步的标签“h”生成的。
在实践中,我们使用的批量大小为n>1, 每个词元都由一个d维向量表示。 因此,在时间步t输入Xt将是一个n×d矩阵。
4.困惑度
最后,让我们讨论如何度量语言模型的质量, 这将在后续部分中用于评估基于循环神经网络的模型。 一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。 考虑一下由不同的语言模型给出的对“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由语言模型给出, xt是在时间步t从该序列中观察到的实际词元。 这使得不同长度的文档的性能具有了可比性。 由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量。 简而言之,它是 (8.4.7)的指数:
困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”。 我们看看一些案例。
-
在最好的情况下,模型总是完美地估计标签词元的概率为1。 在这种情况下,模型的困惑度为1。
-
在最坏的情况下,模型总是预测标签词元的概率为0。 在这种情况下,困惑度是正无穷大。
-
在基线上,该模型的预测是词表的所有可用词元上的均匀分布。 在这种情况下,困惑度等于词表中唯一词元的数量。 事实上,如果我们在没有任何压缩的情况下存储序列, 这将是我们能做的最好的编码方式。 因此,这种方式提供了一个重要的上限, 而任何实际模型都必须超越这个上限。