本章笔记基于[Neubig2017]第四章和NNMNLP第二章的一部分
上一章提到的N元语法模型实际上就是基于计数和条件概率,而log-linear语言模型(或称对数-线性语言模型)使用了另一种方法。在一些经典的文献中,log-linear语言模型通常被称作为“最大熵语言模型”(maximum entropy language model)
模型简介
Log-linear语言模型的本质是把语言模型的建立看作是一个多元分类问题,核心是从上文中提取特征。形式化地说,假设上文为,log-linear语言模型需要一个特征函数
,其将上文信息作为输入,输出一个
维特征向量(注意这里是feature vector而不是eigenvector)
,这个
就是对上文信息的一个表示(这里的
不是N元语法里面的那个N,不表示上文单词数量!)
最简单的提取特征的方法是将上文中的每个单词都做一个独热编码(one-hot encoding)。例如,假设语言模型的词汇表为,大小记为
,词汇表中每个单词都会被分配一个ID,记作
且有
,那么对某个ID为
的单词,其独热编码以后的结果就是一个
维的向量,这个向量在第
个维度上的值为1,其余都是0。如果用类似二元语法的思想(即只用目标单词的前一个单词做预测)来做log-linear语言模型,这样就够了。但是如果想在上文里包含更多的单词,就需要进行扩展。一种常见的思路是将每个单词的独热向量拼接起来,这样,如果上文中包含了
个单词,得到的特征向量的长度
就是
。当然,除了对上文单词做独热编码,log-linear语言模型还允许灵活加入其它特征,这也是该模型的一大长处。常见的特征还包括
- 上文的语义类别。可以使用聚类方法将相似单词聚类,这样,上文每个单词的独热编码不再是单词表长度,而是聚类得到的类别个数
- 上文单词的其它语义信息,例如词性标注(POS-Tag)信息
- 词袋特征。此时,不止考虑前面少数几个单词,而是考虑前面所有单词,统计它们出现的个数。注意在这种情况下会失去单词的位置信息,不过可以捕捉到单词的共现信息
下文中所使用的特征仍然是前面少数几个单词的独热编码
得到特征以后,模型会使用参数计算出一个得分向量
,对应于词汇表中的所有单词。这里参数
具体包含两个参数:权重矩阵
和偏置向量
,都是训练得出。得到
和
以后,对给定的
,就可以计算得到一个得分向量
,其中 s =Wx+b
这样得到的得分向量每个维度的值都可能是任意的一个实数。为了得到一个更好的,有概率意义的结果,可以做一个softmax计算以达到归一化的效果 p =softmax(s)
具体计算方法为,对Softmax的计算问题
本节来自于花书DLBook第4.1节
然而,真实计算softmax函数的值时,既可能出现上溢的情况,又可能出现下溢的情况。假设最后得到的 ,一般计算
的时候都会上溢,例如使用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
而如果最后得到的,一般计算
的时候又都会下溢,例如
>>> import math
>>> math.exp(-1000)
0.0
>>> import numpy as np
>>> np.exp(-1000)
0.0
此时softmax的分母会被认为是0,进而抛出异常
因此,真正计算时,通常会将原始要计算的向量做一个处理得到向量
,即对
的每个分量
都减去
最大的那个分量。即
z =s−maxi si
模型示例
我们举个例子。假设词汇表的大小只有2,包括A、B两个词。现在已知最后出现的两个词为B和A,log-linear语言模型的作用就是训练一个权重矩阵和偏置向量
,进而推断此时接下来两个词出现的概率。假设
和
已经训练好,有
W =[ 1.2 −1.28.54.5 − 0.6 1.3− 3.5 − 2.7 ] ,b =[ 0.1 − 0.3 ]
可以看出,权重矩阵的每一列都有一定的意义。如果特征向量由若干个独热编码组成,那么 的第
列实际上表明了第
个特征激活时,各个词出现的可能性。这个列向量的分量越大,说明对应的词出现的可能性越大,反之说明越小。例如,
这一列是前一个词为A时,接下来出现A和B的可能性。这意味着此时出现A的可能性远大于出现B的可能性
学习模型参数
损失函数
第一节讲述了在有权重矩阵和偏置向量的情况下,如何使用建立好的模型根据给定的上文预测即将出现的词。那么接下来的问题是如何得到(学习)这些模型参数。为此,需要先定义一个合适的损失函数
。所谓损失函数,其直观意义可以看做是在给定当前模型参数
的情况下,模型的预测值
与真实值
之间差了多远,也就是现在这个模型有多差。因此,训练模型参数的过程,就是要调整参数
使得损失函数
尽量小的过程。通常情况下,损失函数等于似然的负对数 ℓ (E trai
n ,θ)= −logP(E trai n |θ)= −∑ E ∈E train logP(E| θ)
使用随机梯度下降(SGD)进行优化
熟悉logistic回归和其它统计学习方法的读者应该可以马上意识到,log-linear语言模型实际上可以看作是logistic回归变种在NLP领域上的一个应用,而logistic回归通常使用梯度下降法做优化。当样本量太大时,通常使用随机梯度下降。不过这里本文还是准备对随机梯度下降做一个具体的介绍
本部分内容参考了花书4.3小节
大部分机器学习问题的最终形式都是一个优化问题,即给定一个参数为 的函数
,找到一个合适的
,使得
能取得最小值。用数学的语言描述,就是求出
——当然,有些时候需要找到能使
取得最大值的
,在这种情况下,同样的
也会使
取得最小值,所以两者是等价的。这里的这个
也就是前面提到的损失函数
这样一来,问题就被抽象为求最值的问题。在比较简单的,一元函数的情况下,对函数求导并令导数为0,就可以求出最优的。但是,考虑到有些情况下上述方法无法得到解析解,因此还需要使用一种迭代的方法来完成同样的任务。对于给定的函数
,其一阶导数的一个重要性质是,如果其在某个点
的一阶导数
,则函数单调递增;如果一阶导数
,则函数单调下降。在最简单的情况下,假设已知函数是凹函数且处处连续、处处可导,那么当
在某个点
的一阶导数大于0时,由于函数递增,那么为了向极值点(这里是极小值点)靠近,
应该向左移动,慢慢变小;当当
在某个点
的一阶导数小于0时,由于函数递减,那么为了向极值点靠近,
应该向右移动,慢慢变大。综合这两种情况,对足够小的
,总有 f (x− ϵ⋅s ign( f′ (x) ))< f(x )
上述过程就是梯度下降在一元函数情况下的介绍,相对比较容易理解。当函数的输入时多维的向量时,要稍微复杂一些。前面在讲述一元函数一阶导数的时候有一点没有提到,一元函数在
点的导数的另一个意义是变量在该点做微小移动时,函数值的变化率。这个意义可以扩展到多元函数的领域里:假设函数
的参数
,那么当
的
个维度上的分量都不变,仅对第
个维度上的分量做微小移动时,函数值的变化率就是这个函数在
轴上的偏导数,记为
。换句话说,函数
的偏导数是函数沿着某个坐标轴的变化率。在此之上,可以定义梯度为 ∇ x f(x)= [ ∂ f ∂ x1 ⋯ ∂ f ∂ xi ⋯ ∂ f ∂ xn ] T
将这个公式套用到前面“损失函数”一节中的定义,可以知道log-linear语言模型的参数更新方式为 θ t+1 =θ t −ϵ∂ℓ(et t− n+1 ,θ)∂ θ
其中此外,上面给出的更新方式是每看到一个样本更新一次,这种做法通常也称为在线学习法(online learning)。另外一种方法是将所有数据的梯度都计算出来,然后求均值,这种方法称为全批量(full-batch)梯度下降。在线学习的问题是一个样本的梯度方向可能不是最优方向(不过在样本量足够大,迭代次数足够多的情况下也能收敛到比较好的极值点);而全批量梯度下降的问题是深度学习使用的数据集通常比较大,计算起来耗费资源太多。因此真正广泛应用的是小批量(mini-batch)随机梯度下降法,即随机选取若干样本,计算梯度的均值
在SGD之上,还有一些衍生的优化算法,例如动量(momentum)法、AdaGrad和Adam等等。这些算法将在之后的文章中做总结
损失函数对参数的偏导数
通过上面的讲述,可知更新模型参数的关键是要求出损失函数对参数的偏导数。将上面的式子整理可得 s =∑ j: xj ≠0 W.,j xj+b p =s oftma x (s) ℓ =−log pe t
因此有 ∂ ℓ ∂ W. ,j =∂ ℓ ∂ pe t ⋅∂ pe t ∂ s ⋅∂s∂ W., j ∂ ℓ ∂ b =∂ ℓ ∂ pe t ⋅ ∂p e t ∂ s ⋅ ∂s∂ b 可以看出两者都需要求