14.2 RNN 作为语言模型
下面看看如何把 RNN 应用于语言建模任务。回忆第 3 章,语言模型根据给定的前文上下文预测序列中的下一个词。例如,如果前文是“Thanks for all the”,我们想知道下一个词是“fish”的可能性有多大,就会计算:
P ( fish ∣ Thanks for all the ) P(\text{fish} \mid \text{Thanks for all the}) P ( fish ∣ Thanks for all the )
语言模型能够为每一个可能的下一个词赋予这样的条件概率,从而得到整个词表上的概率分布。我们还可以利用链式法则,把这些条件概率组合起来,为完整序列赋予概率:
P ( w 1 : n ) = ∏ i = 1 n P ( w i ∣ w < i ) P (w _ {1: n}) = \prod_ {i = 1} ^ {n} P (w _ {i} | w _ {< i}) P ( w 1 : n ) = i = 1 ∏ n P ( w i ∣ w < i ) 第 3 章的 n n n 元语言模型根据某个词与它前面 n − 1 n-1 n − 1 个词共同出现的次数,计算该词的概率。因此,上下文的大小是 n − 1 n-1 n − 1 。对于第 6 章介绍的前馈语言模型,上下文就是窗口大小。
RNN 语言模型(Mikolov et al., 2010)一次处理一个输入词,试图根据当前词和前一隐藏状态预测下一个词。因此,RNN 没有 n n n 元模型那样的有限上下文问题,也没有前馈语言模型那样固定的上下文,因为隐藏状态原则上可以表示从序列开头一直到当前位置之前所有词的信息。图 14.5 概略展示了 FFN 语言模型与 RNN 语言模型的区别:RNN 语言模型使用前一时间步的隐藏状态 h t − 1 \mathbf{h}_{t-1} h t − 1 来表示过去的上下文。
图 14.5 两种语言模型架构沿文本运行时的简化示意图。图中以三个词元表示上下文:(a)前馈神经语言模型,其上下文输入以固定窗口提供给权重矩阵 W W W ;(b)RNN 语言模型,其中隐藏状态 h t − 1 \mathbf{h}_{t-1} h t − 1 概括先前的上下文。
14.2.1 RNN 语言模型中的前向推理 ¶ 循环语言模型中的前向推理过程与 14.1.1 节所述完全相同。输入序列 X = [ x 1 ; . . . ; x t ; . . . ; x N ] {\pmb{\mathsf{X}}} = [{\pmb{\mathsf{x}}}_1; ...; {\pmb{\mathsf{x}}}_t; ...; {\pmb{\mathsf{x}}}_N] X X = [ x x 1 ; ... ; x x t ; ... ; x x N ] 由一系列词组成,每个词都表示为大小为 ∣ V ∣ × 1 |V| \times 1 ∣ V ∣ × 1 的 one-hot 向量;输出预测 y ^ \hat{\mathbf{y}} y ^ 则表示词表上的概率分布。在每一步,模型使用词嵌入矩阵 E E E 取出当前词的嵌入,将其乘以权重矩阵 W W W ,再加上前一步隐藏层(由权重矩阵 U U U 加权),从而计算新的隐藏层。随后利用该隐藏层生成输出层,并通过 softmax 层得到整个词表上的概率分布。也就是说,在时间 t t t :
e t = E x t (14.4) \mathbf {e} _ {t} = \mathbf {E x} _ {t}\tag{14.4} e t = Ex t ( 14.4 ) h t = g ( U h t − 1 + W e t ) (14.5) \mathbf {h} _ {t} = g \left(\mathbf {U h} _ {t - 1} + \mathbf {W e} _ {t}\right)\tag{14.5} h t = g ( Uh t − 1 + We t ) ( 14.5 ) y ^ t = softmax ( V h t ) (14.6) \hat {\mathbf {y}} _ {t} = \operatorname{softmax} (\mathbf {V h} _ {t})\tag{14.6} y ^ t = softmax ( Vh t ) ( 14.6 ) 在使用 RNN(以及第 7 章将看到的 Transformer)进行语言建模时,方便的做法是假定嵌入维度 d e d_e d e 与隐藏维度 d h d_h d h 相同。因此,我们把两者都称为模型维度 d d d 。于是,嵌入矩阵 E E E 的形状为 [ d × ∣ V ∣ ] [d \times |V|] [ d × ∣ V ∣ ] ,而 x t \mathbf{x}_t x t 是形状为 [ ∣ V ∣ × 1 ] [|V| \times 1] [ ∣ V ∣ × 1 ] 的 one-hot 向量。其乘积 e t \mathbf{e}_t e t 的形状为 [ d × 1 ] [d \times 1] [ d × 1 ] 。W W W 和 U U U 的形状为 [ d × d ] [d \times d] [ d × d ] ,所以 h t \mathbf{h}_t h t 的形状也为 [ d × 1 ] [d \times 1] [ d × 1 ] 。V V V 的形状为 [ ∣ V ∣ × d ] [|V| \times d] [ ∣ V ∣ × d ] ,因此 V h V\mathbf{h} V h 的结果是形状为 [ ∣ V ∣ × 1 ] [|V| \times 1] [ ∣ V ∣ × 1 ] 的向量。给定 h \mathbf{h} h 提供的证据,可以把这个向量看作词表上的一组分数。将这些分数输入 softmax 后,就会把分数归一化为概率分布。词表中的某个词 k k k 是下一个词的概率由 y ^ t [ k ] \hat{\mathbf{y}}_t[k] y ^ t [ k ] 表示,即 y ^ t \hat{\mathbf{y}}_t y ^ t 的第 k k k 个分量:
P ( w t + 1 = k ∣ w 1 , … , w t ) = y ^ t [ k ] (14.7) P (w _ {t + 1} = k | w _ {1}, \dots , w _ {t}) = \hat {\mathbf {y}} _ {t} [ k ]\tag{14.7} P ( w t + 1 = k ∣ w 1 , … , w t ) = y ^ t [ k ] ( 14.7 ) 完整序列的概率就是序列中每个元素概率的乘积。这里用 y ^ i [ w i ] \hat{\mathbf{y}}_i[w_i] y ^ i [ w i ] 表示时间步 i i i 真实词 w i w_i w i 的概率。
P ( w 1 : n ) = ∏ i = 1 n P ( w i ∣ w 1 : i − 1 ) = ∏ i = 1 n y ^ i [ w i ] (14.8) \begin{array}{l} P (w _ {1: n}) = \prod_ {i = 1} ^ {n} P (w _ {i} | w _ {1: i - 1}) \\ = \prod_ {i = 1} ^ {n} \hat {\mathbf {y}} _ {i} [ w _ {i} ] \end{array}\tag{14.8} P ( w 1 : n ) = ∏ i = 1 n P ( w i ∣ w 1 : i − 1 ) = ∏ i = 1 n y ^ i [ w i ] ( 14.8 ) (14.9)
14.2.2 训练 RNN 语言模型 ¶ 为了训练 RNN 作为语言模型,我们使用 7.7 节介绍的相同自监督(或自训练)算法:把文本语料库作为训练材料,并在每个时间步要求模型预测下一个词。我们称这种模型为自监督模型,是因为无需向数据添加特殊的人工金标准标签;词语的自然序列本身就是监督信号!我们只需使用交叉熵作为损失函数,训练模型最小化其对训练序列中真实下一个词的预测误差。回忆一下,交叉熵损失衡量预测概率分布与正确分布之间的差异。
L C E ( y ^ t , y t ) = − ∑ w ∈ V y t [ w ] log y ^ t [ w ] (14.10) L _ {C E} (\hat {\mathbf {y}} _ {t}, \mathbf {y} _ {t}) = - \sum_ {w \in V} \mathbf {y} _ {t} [ w ] \log \hat {\mathbf {y}} _ {t} [ w ]\tag{14.10} L CE ( y ^ t , y t ) = − w ∈ V ∑ y t [ w ] log y ^ t [ w ] ( 14.10 ) 图 14.6 将 RNN 作为语言模型进行训练。
在语言建模中,正确分布 y t \mathbf{y}_t y t 来自对下一个词的了解。它表示为词表对应的 one-hot 向量:真实下一个词所在的位置为 1,其余位置为 0。因此,语言建模中的交叉熵损失由模型赋予真实下一个词的概率决定。也就是说,在时间 t t t ,CE 损失是模型赋予训练序列中下一个词的概率的负对数。
L C E ( y ^ t , y t ) = − log y ^ t [ w t + 1 ] (14.11) L _ {C E} (\hat {\mathbf {y}} _ {t}, \mathbf {y} _ {t}) = - \log \hat {\mathbf {y}} _ {t} [ w _ {t + 1} ]\tag{14.11} L CE ( y ^ t , y t ) = − log y ^ t [ w t + 1 ] ( 14.11 ) 因此,在输入的每个词位置 t t t ,模型把正确词 w t w_t w t 和 h t − 1 h_{t-1} h t − 1 作为输入,其中 h t − 1 h_{t-1} h t − 1 编码了前面 w 1 : t − 1 w_{1:t-1} w 1 : t − 1 的信息;模型据此计算可能的下一个词的概率分布,并计算下一个词元 w t + 1 w_{t+1} w t + 1 的损失。然后我们移到下一个词,忽略模型对下一个词的预测,转而使用正确词 w t + 1 w_{t+1} w t + 1 以及编码好的历史信息,估计词元 w t + 2 w_{t+2} w t + 2 的概率。始终向模型提供正确的历史序列来预测下一个词,而不是把模型上一步最可能的预测作为输入,这一做法称为教师强制(teacher forcing)。
通过梯度下降调整网络中的权重,使训练序列上的平均 CE 损失最小。图 14.6 展示了这一训练过程。
14.2.3 权重绑定 ¶ 细心的读者可能已经注意到,输入嵌入矩阵 E E E 与向最终 softmax 层提供输入的矩阵 V V V 非常相似。
在训练过程中,E E E 的每一列表示词表中一个词的词嵌入;训练目标是让意义和功能相似的词拥有相似的嵌入。而且,由于用 RNN 进行语言建模时假定嵌入维度与隐藏维度相同(即模型维度 d d d ),嵌入矩阵 E E E 的形状为 [ d × ∣ V ∣ ] [d \times |V|] [ d × ∣ V ∣ ] 。最终层矩阵 V V V 则通过计算 V h V\mathbf{h} V h ,根据网络最终隐藏层中的证据,为词表中的每个词的可能性打分。V V V 的形状为 [ ∣ V ∣ × d ] [|V| \times d] [ ∣ V ∣ × d ] 。也就是说,V V V 的行的形状相当于 E E E 的转置,因此 V V V 提供了第二组学习得到的词嵌入。
语言模型不再使用两组嵌入矩阵,而是使用一个同时出现在输入层和 softmax 层的嵌入矩阵。也就是说,计算开始时使用 E E E ,结束时使用 E ⊤ E^\top E ⊤ (因为此处 V V V 的形状是 E E E 的转置),从而不再需要 V V V 。在两个位置使用同一个(转置后的)矩阵称为权重绑定(weight tying)1 。使用权重绑定的 RNN 语言模型方程变为:
e t = E x t (14.12) \mathbf {e} _ {t} = \mathbf {E x} _ {t}\tag{14.12} e t = Ex t ( 14.12 ) h t = g ( U h t − 1 + W e t ) (14.13) \mathbf {h} _ {t} = g \left(\mathbf {U h} _ {t - 1} + \mathbf {W e} _ {t}\right)\tag{14.13} h t = g ( Uh t − 1 + We t ) ( 14.13 ) y ^ t = softmax ( E ⊺ h t ) (14.14) \hat {\mathbf {y}} _ {t} = \operatorname{softmax} \left(\mathbf {E} ^ {\intercal} \mathbf {h} _ {t}\right)\tag{14.14} y ^ t = softmax ( E ⊺ h t ) ( 14.14 ) 除改善模型的困惑度之外,这种方法还显著减少了模型所需的参数数量。