自然语言处理中N-Gram模型的Smoothing算法

在之前的文章《自然语言处理中的N-Gram模型详解》里,我们介绍了NLP中的模型。最后谈到,为了解决使用N-Gram模型时可能引入的稀疏数据问题,人们设计了多种平滑算法,本文将讨论其中最为重要的几种。

  • Add-one (Laplace) Smoothing
  • Add-k Smoothing(Lidstone’s law)
  • Backoff
  • Interpolation
  • Absolute Discounting
  • Kneser-Ney Smoothing

欢迎关注白马负金羁的博客 http://blog.csdn.net/baimafujinji,为保证公式、图表得以正确显示,强烈建议你从该地址上查看原版博文。本博客主要关注方向包括:数字图像处理、算法设计与分析、数据结构、机器学习、数据挖掘、统计分析方法、自然语言处理。


Add-one (Laplace) Smoothing

Add-one是最简单、最直观的一种平滑算法。既然希望没有出现过的N-Gram的概率不再是0,那就不妨规定任何一个N-Gram在训练语料至少出现一次(即规定没有出现过的N-Gram在训练语料中出现了一次),则: countnew(n-gram)=countold(n-gram)+1
于是,对于unigram模型而言,会有

Padd1(wi)=C(wi)+1M+|V|

其中, M 是训练语料中所有的 N-Gram的数量(token),而 V 是所有的可能的不同的 N-Gram的数量(type)。

同理,对于bigram模型而言,可得

Padd1(wi|wi1)=C(wi1wi)+1C(wi1)+|V|

推而广之,对于 n-Gram模型而言,可得
Padd1(wi|win+1,,wi1)=C(win+1,,wi)+1C(win+1,,wi1)+|V|

例如,对于句子 <s>theratatethecheese</s> ,我们可以来试着计算一下经add-one平滑后的 P(ate|rat) 以及 P(ate|cheese) ,即
P(ate|rat)=C(ratate)+1C(rat)+|V|=26P(ate|cheese)=C(cheeseate)+1C(cheese)+|V|=16

请注意,前面我们说过 V 是所有的可能的不同的 n-Gram的数量,在这个例子中,它其实就是语料库中的词汇量,而这个词汇量是不包括 <s> 的,但却需要包括 </s> 。 对此可以这样来理解,由于符号 <s> 表示一个句子的开始,所以评估概率 P(<s>|w) 是没有意义的,因为当给定单词 w 的情况下来评估下一个单词可能是 <s> 的概率是没有任何意义的,因为这种情况并不会发生。但是,同样的情况对于结束符则是有意义的。

如此一来,训练语料中未出现的n-Gram的概率不再为 0,而是一个大于 0 的较小的概率值。Add-one 平滑算法确实解决了我们的问题,但显然它也并不完美。由于训练语料中未出现n-Gram数量太多,平滑后,所有未出现的n-Gram占据了整个概率分布中的一个很大的比例。因此,在NLP中,Add-one给训练语料中没有出现过的 n-Gram 分配了太多的概率空间。此外,认为所有未出现的n-Gram概率相等是否合理其实也值得商榷。而且,对于出现在训练语料中的那些n-Gram,都增加同样的频度值,这是否欠妥,我们并不能给出一个明确的答案。


Add-k Smoothing(Lidstone’s law)

由Add-one衍生出来的另外一种算法就是 Add-k。(根据上文中红色字体部分)既然我们认为加1有点过了,不然选择一个小于1的正数 k。此时,概率计算公式就变成了

Paddk(wi|win+1wi1)=C(win+1wi)+kC(win+1wi1)+k|V|

通常,add- k算法的效果会比Add-one好,但是显然它不能完全解决问题。至少在实践中, k 必须人为给定,而这个值到底该取多少却莫衷一是。


回退(Backoff)

通常我们会认为高阶模型更加可靠,我们之前博文中给出的例子也表明,当能够获知更多历史信息时,其实就获得了当前推测的更多约束,这样就更容易得出正确的结论。所以在高阶模型可靠时,尽可能的使用高阶模型。但是有时候高级模型的计数结果可能为0,这时我们就转而使用低阶模型来避免稀疏数据的问题。

如果用公式来定义,即

Pbackoff(wi|win+1wi1)={P(wi|win+1wi1)α(win+1wi1)Pbackoff(wi|win+2wi1),ifC(win+1wi)>0,otherwise

其中 α P 是归一化因子,以保证 Pbackoff=1


插值(Interpolation)

插值和回退的思想其实非常相像。设想对于一个trigram的模型,我们要统计语料库中 “like chinese food” 出现的次数,结果发现它没出现过,则计数为0。在回退策略中,将会试着用低阶gram来进行替代,也就是用 “chinese food” 出现的次数来替代。

在使用插值算法时,我们把不同阶别的n-Gram模型线形加权组合后再来使用。简单线性插值(Simple Linear Interpolation)可以用下面的公式来定义:

Pinterp(wn|wn2,wn1)=λ1P(wn)+λ2P(wn|wn1)+λ3P(wn|wn2,wn1)

其中, 0λi1 iλi=1 λi 可以根据试验凭经验设定,也可以通过应用某些算法确定,例如EM算法。

在简单单线形插值法中,权值 λi 是常量。显然,它的问题在于不管高阶模型的估计是否可靠(毕竟有些时候高阶的Gram计数可能并无为 0),低阶模型均以同样的权重被加入模型,这并不合理。一个可以想到的解决办法是让 λi 成为历史的函数。如果用递归的形式重写插值法的公式,则有

Pinterp(wi|win+1wi1)=λ(win+1wi1)P(wi|win+1wi1)+(1λ(win+1wi1))Pinterp(wi|win+2wi1)

注意同样需要保证 Pinterp=1 。上面这个公式又称为Jelinek-Mercer Smoothing。而且,正如前面所展示的那样, λ 可以是常数,而我们上式中 λ(win+1wi1) 意思是表示 λ 是历史的一个函数,这通常是更明智的做法。


Absolute Discounting

想想之前的Add-one,以及Add-k算法。我们的策略,本质上说其实是将一些频繁出现的 N-Gram 的概率匀出了一部分,分给那些没有出现的 N-Gram 上。因为所有可能性的概率之和等于1,所以我们只能在各种可能的情况之间相互腾挪这些概率。

既然我们打算把经常出现的一些N-Gram的概率分一些出来,其实也就等同于将它们出现的次数减去(discount)一部分,那到底该discount多少呢?Church & Gale (1991) 设计了一种非常巧妙的方法。首先他们在一个留存语料库(held-out corpus)考察那些在训练集中出现了4次的bigrams出现的次数。具体来说,他们首先在一个有2200万词的留存语料库中检索出所有出现了4次的bigrams (例如: “chinese food”,“good boy”,“want to”等),然后再从一个同样有2200万词的训练集中,分别统计这些bigrams出现的次数(例如:C(“chinese food”)=4,C(“good boy”)=3,C(“want to”)=3)。最终,平均下来,他们发现:在第一个2200万词的语料中出现4次的bigrams,在第二个2200万词的语料中出现了3.23次。下面这张表给出了 c 从0到9取值时(也就是出现了 c 次),统计的bigrams在留存集和训练集中出现的次数。


自然语言处理中N-Gram模型的Smoothing算法_第1张图片

其实你应该已经发现其中的规律了。除了计数为0和为1的bigram之外,held-out set中的平均计数值,都大约相当于training set中的计数值减去0.75。

基于上面这个实验结果所诱发的直觉,Absolute discounting 会从每一个计数中减去一个(绝对的)数值 d 。这样做的道理就在于,对于出现次数比较多的计数我们其实已经得到了一个相对比较好的估计,那么当我们从这个计数值中减去一个较小的数值 d 后应该影响不大。上面的实验结果暗示在实践中,我们通常会对从2到9的计数进行处理。

PAbsDiscount(wi|wi1)=C(wi1wi)dC(wi1)+λ(wi1)P(wi)

从上一篇文章中,我们已经知道,对于bigram model而言, P(wi|wi1)=C(wi1wi)/C(wi1) ,所以上述方程等号右侧第一项即表示经过 discounting 调整的概率值,而第二项则相对于一个带权重 λ 的 unigram 的插值项。通常,你可以把 d 值就设为 0.75,或者你也可以为计数为 1 的 bigram 设立一个单独的等于 0.5 的 d 值(这个经验值从上面的表中也能看出来)。


Kneser-Ney Smoothing

这种算法目前是一种标准的,而且是非常先进的平滑算法,它其实相当于是前面讲过的几种算法的综合。由于这个算法比较复杂,我们从一个直观上的例子来开始。

假设我们使用 bigram 和 unigram 的插值模型来预测下面这个句子中空缺的一个词该填什么

  • I used to eat Chinese food with ______ instead of knife and fork.

直觉上你一定能猜到这个地方应该填 chopsticks(筷子)。但是有一种情况是训练语料库中,Zealand 这个词出现的频率非常高,因为 New Zealand 是语料库中高频词。如果你采用标准的 unigram 模型,那么显然 Zealand 会比 chopsticks 具有更高的权重,所以最终计算机会选择Zealand这个词(而非chopsticks)填入上面的空格,尽管这个结果看起来相当不合理。这其实就暗示我们应该调整一下策略,最好仅当前一个词是 New 时,我们才给 Zealand 赋一个较高的权值,否则尽管在语料库中 Zealand 也是高频词,但我们并不打算单独使用它。

如果说 P(w) 衡量了 w 这个词出现的可能性,那么我们现在想创造一个新的 unigram 模型,叫做 Pcontinuation ,它的意思是将 w 这个词作为一个新的接续的可能性。注意这其实暗示我们要考虑前面一个词(即历史)的影响。或者说,为了评估 Pcontinuation (注意这是一个 unigram 模型),我们其实需要考察使用了 w 这个词来生成的不同 bigram 的数量。注意这里说使用了 w 这个词来生成的不同类型 bigram 的数量,是指当前词为 w ,而前面一个词不同时,就产生了不同的类型。例如:w = “food”, 那么不同的 bigram 类型就可能包括 “chinese food”,“english food”,“japanese food”等。每一个 bigram 类型,当我们第一次遇到时,就视为一个新的接续(novel continuation)。

也就是说 Pcontinuation 应该同所有新的接续(novel continuation)构成的集合之势(cardinality)成比例。所以,可知

Pcontinuation(wi)wi1:C(wi1wi)>0

如果你对此尚有困惑,我再来解释一下上面这个公式的意思。当前词是 wi ,例如“food”,由此构成的不同类型的 bigram 即为 wi1wi ,其中 wi1 表示前一个词(preceding word)。显然,所有由 wi1wi 构成的集合的势,其实就取决于出现在 wi 之前的不同的 wi1 的数量。

然后,为了把上面这个数变成一个概率,我们需要将其除以一个值,这个值就是所有 bigram 类型的数量,即 {(wj1,wj):C(wj1wj)>0} ,这里大于0的意思就是“出现过”。于是有

Pcontinuation(wi)={wi1:C(wi1wi)>0}{(wj1,wj):c(wj1wj)>0}

当然,我们还可以采用下面这种等价的形式
Pcontinuation(wi)={wi1:C(wi1wi)>0}wi{wi1:C(wi1wi)>0}

即所有不同的 bigram 的数量就等于出现在单词 wi 前面的所有不同的词 wi1 的数量。

如此一来,一个仅出现在 New 后面的高频词 Zealand 只能获得一个较低的接续概率(continuation probability)。由此,再结合前面给出的Absolute Discounting 的概率计算公式,就可以得出插值的 Kneser-Ney Smoothing 的公式,即

PKN(wi|wi1)=max(C(wi1wi)d,0)C(wi1)+λ(wi1)Pcontinuation(wi)

其中, max(C(wi1wi)d,0) 的意思是要保证最后的计数在减去一个 d 之后不会变成一个负数。其次,我们将原来的 P(wi) 替换成了 Pcontinuation(wi) 。此外, λ 是一个正则化常量,用于分配之前discount的概率值(也就是从高频词中减去的准备分给那些未出现的低频词的概率值):
λ(wi1)=dC(wi1){w:C(wi1,w)>0}

如果用递归的方式重写出一个更加普适的泛化公式则有:

PKN(wi|win+1wi1)=max(0,CKN(win+1wi)d)CKN(win+1wi1)+λ(win+1wi1)PKN(wi|win+2wi1)

其中,
λ(win+1wi1)=dCKN(win+1wi1){w:CKN(win+1wi1w)>0}

由于采用了上述这种递归的写法,我们需要定义一个计数函数 CKN ,它取决于在插值组合中的各阶 N-Gram 处于哪个层级。例如,假设我们现在所使用的插值模型是trigram,bigram 和 unigram 的组合,那么对于最高阶的 trigram 在计数时并不需要使用接续计数(采用普通计数即可),而其他低阶,即 bigram 和 unigram 则需要使用接续计数。这是因为在 unigram 中,我们遇到了一个 Zealand,我们可以参考它的 bigram,同理在 bigram,我还可以再参考它的 trigram,但是如果我们的插值组合中最高阶就是 trigram,那么现在没有 4-gram来给我们做接续计数。用公式表示即为:
CKN()={count()continuationcount(),forthehighestorder,forallotherlowerorders

我们前面提到Kneser-Ney Smoothing 是当前一个标准的、广泛采用的、先进的平滑算法。这里我们所说的先进的平滑算法,其实是包含了其他以 Kneser-Ney 为基础改进、衍生而来的算法。其中,效果最好的Kneser-Ney Smoothing 算法是由Chen & Goodman(1998)提出的modified Kneser-Ney Smoothing 算法。很多NLP的开发包和算法库中提供有原始的Kneser-Ney Smoothing(也就是我们前面介绍的),以及modified Kneser-Ney Smoothing 算法的实现。有兴趣的读者可以查阅相关资料以了解更多。


推荐阅读和参考文献:

[1] Speech and Language Processing. Daniel Jurafsky & James H. Martin, 3rd. Chapter 4

[2] 本文中的一些例子和描述来自 北京大学 常宝宝 以及 The University of Melbourne “Web Search and Text Analysis” 课程的幻灯片素材

[3] 同时推荐美国斯坦福大学 Dan Jurafsky & Christopher Manning 在Coursera上主讲的自然语言处理公开课(https://class.coursera.org/nlp/lecture)

你可能感兴趣的:(自然语言处理,NLP,N-Gram,Kneser-Ney,自然语言处理与信息检索)