神经翻译笔记2. Log-linear语言模型
本章笔记基于[Neubig2017]第四章和NNMNLP第二章的一部分
上一章提到的N元语法模型实际上就是基于计数和条件概率,而log-linear语言模型(或称对数-线性语言模型)使用了另一种方法。在一些经典的文献中,log-linear语言模型通常被称作为“最大熵语言模型”(maximum entropy language model)
模型简介
Log-linear语言模型的本质是把语言模型的建立看作是一个多元分类问题,核心是从上文中提取特征。形式化地说,假设上文为 e t − n + 1 t − 1 e_{t-n+1}^{t-1} et−n+1t−1,log-linear语言模型需要一个特征函数 ϕ ( e t − n + 1 t − 1 ) \phi(e_{t-n+1}^{t-1}) ϕ(et−n+1t−1),其将上文信息作为输入,输出一个 N N N维特征向量(注意这里是feature vector而不是eigenvector) x ∈ R N \boldsymbol{x} \in \mathbb{R}^N x∈RN,这个 x \boldsymbol{x} x就是对上文信息的一个表示(这里的 N N N不是N元语法里面的那个N,不表示上文单词数量!)
最简单的提取特征的方法是将上文中的每个单词都做一个独热编码(one-hot encoding)。例如,假设语言模型的词汇表为 V V V,大小记为 ∣ V ∣ |V| ∣V∣,词汇表中每个单词都会被分配一个ID,记作 i i i且有 1 ≤ i ≤ ∣ V ∣ 1 \le i \le |V| 1≤i≤∣V∣,那么对某个ID为 i i i的单词,其独热编码以后的结果就是一个 V V V维的向量,这个向量在第 i i i个维度上的值为1,其余都是0。如果用类似二元语法的思想(即只用目标单词的前一个单词做预测)来做log-linear语言模型,这样就够了。但是如果想在上文里包含更多的单词,就需要进行扩展。一种常见的思路是将每个单词的独热向量拼接起来,这样,如果上文中包含了 M M M个单词,得到的特征向量的长度 N N N就是 M ∣ V ∣ M|V| M∣V∣。当然,除了对上文单词做独热编码,log-linear语言模型还允许灵活加入其它特征,这也是该模型的一大长处。常见的特征还包括
- 上文的语义类别。可以使用聚类方法将相似单词聚类,这样,上文每个单词的独热编码不再是单词表长度,而是聚类得到的类别个数
- 上文单词的其它语义信息,例如词性标注(POS-Tag)信息
- 词袋特征。此时,不止考虑前面少数几个单词,而是考虑前面所有单词,统计它们出现的个数。注意在这种情况下会失去单词的位置信息,不过可以捕捉到单词的共现信息
下文中所使用的特征仍然是前面少数几个单词的独热编码
得到特征以后,模型会使用参数 θ \theta θ计算出一个得分向量 s ∈ R ∣ V ∣ \boldsymbol{s} \in \mathbb{R}^{|V|} s∈R∣V∣,对应于词汇表中的所有单词。这里参数 θ \theta θ具体包含两个参数:权重矩阵 W ∈ R ∣ V ∣ × N \boldsymbol{W} \in \mathbb{R}^{|V| \times N} W∈R∣V∣×N和偏置向量 b ∈ R ∣ V ∣ \boldsymbol{b} \in \mathbb{R}^{|V|} b∈R∣V∣,都是训练得出。得到 W \boldsymbol{W} W和 b \boldsymbol{b} b以后,对给定的 x \boldsymbol{x} x,就可以计算得到一个得分向量 s \boldsymbol{s} s,其中
s = W x + b \boldsymbol{s} = \boldsymbol{Wx} + \boldsymbol{b} s=Wx+b
由于真正应用中词汇表的大小通常都比较大,特征数也比较多,因此权重矩阵 W \boldsymbol{W} W通常会很大(如果只使用前面提到的独热编码的上文信息做特征,假设上文单词数为2,词汇表大小为20000,则权重矩阵的大小就是 20000 × 40000 20000 \times 40000 20000×40000。同时, x \boldsymbol{x} x本身也是一个比较大的向量,两者相乘比较耗时。由于 x \boldsymbol{x} x通常是若干独热编码向量的组合,因此它会特别稀疏,只有少许维度非0(而且可能只取值为1)。所以,可以使用如下操作避免矩阵乘法带来的性能问题:
s = ∑ j : x j ̸ = 0 W . , j x j + b \boldsymbol{s} = \sum_{j: x_j \not= 0}\boldsymbol{W}_{., j}x_j + \boldsymbol{b} s=j:xj̸=0∑W.,jxj+b
其中 W . , j \boldsymbol{W}_{., j} W.,j代表矩阵 W \boldsymbol{W} W的第 j j j列。上一个式子的直接翻译是“对特征向量 x \boldsymbol{x} x,找出其所有不为零的维度,然后找出权重矩阵 W \boldsymbol{W} W对应的列,将这一列向量乘以特征向量对应维度的值,再把所有这样处理得到的向量相加,最后加上偏置向量”
这样得到的得分向量每个维度的值都可能是任意的一个实数。为了得到一个更好的,有概率意义的结果,可以做一个softmax计算以达到归一化的效果
p = s o f t m a x ( s ) \boldsymbol{p} = {\rm softmax}(\boldsymbol{s}) p=softmax(s)
具体计算方法为,对 s \boldsymbol{s} s的每个分量 s j s_j sj,其对应的 p j p_j pj为
p j = exp ( s j ) ∑ i exp ( s i ) p_j = \frac{\exp(s_j)}{\sum_i\exp(s_i)} pj=∑iexp(si)exp(sj)
Softmax的计算问题
本节来自于花书DLBook第4.1节
然而,真实计算softmax函数的值时,既可能出现上溢的情况,又可能出现下溢的情况。假设最后得到的 s = [ 1000 1003 ] \boldsymbol{s} = \left[\begin{matrix}1000 \\ 1003\end{matrix}\right] s=[10001003],一般计算 e 1000 e^{1000} e1000的时候都会上溢,例如使用python计算
>>> import math
>>> math.exp(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: math range error
>>> import numpy as np
>>> np.exp(1000)
__main__:1: RuntimeWarning: overflow encountered in exp
inf
而如果最后得到的 s = [ − 1000 − 1003 ] \boldsymbol{s} = \left[\begin{matrix}-1000 \\ -1003\end{matrix}\right] s=[−1000−1003],一般计算 e − 1000 e^{-1000} e−1000的时候又都会下溢,例如
>>> import math
>>> math.exp(-1000)
0.0
>>> import numpy as np
>>> np.exp(-1000)
0.0
此时softmax的分母会被认为是0,进而抛出异常
因此,真正计算时,通常会将原始要计算的向量 s \boldsymbol{s} s做一个处理得到向量 z \boldsymbol{z} z,即对 s \boldsymbol{s} s的每个分量 s i s_i si都减去 s \boldsymbol{s} s最大的那个分量。即
z = s − max i s i \boldsymbol{z} = \boldsymbol{s} - \max_i s_i z=s−imaxsi
记 max i s i = c \max_i s_i = c maxisi=c,同时 s o f t m a x ( z ) = q , s o f t m a x ( s ) = p \rm{softmax}(\boldsymbol{z}) = \boldsymbol{q},\ \rm{softmax}(\boldsymbol{s}) = \boldsymbol{p} softmax(z)=q, softmax(s)=p,下面证明有 q = p \boldsymbol{q} = \boldsymbol{p} q=p。假设 c \boldsymbol{c} c是维度与 z \boldsymbol{z} z和 s \boldsymbol{s} s的维度均相等的向量,其每个分量 c i = max i s i = c c_i = \max_i s_i = c ci=maxisi=c,即 ∀ i , j , 1 ≤ i , j ≤ d i m c ⇒ c i = c j = c \forall i, j, 1 \le i, j \le {\rm dim}\ \boldsymbol{c} \Rightarrow c_i = c_j = c