一、概况
在训练语言模型的时候有几个常用词:折扣(discounting)、平滑(smoothing)、插值(interpolate),下面用一个例子简单介绍一下。
我们的训练语料里面有“a、b、c、d”四个词,其中以a、b开头的3gram有以下数据:
a b c 5次
a b d 10次
那么当我们需要计算p(c|ab)的概率的时候,可以使用最大似然估计
p
(
c
∣
a
b
)
=
c
(
a
b
c
)
c
(
a
b
)
p(c|ab)=\frac{c(abc)}{c(ab)}
p(c∣ab)=c(ab)c(abc)
用“a b c”的次数除以“a b”的总个数,上式的概率值为
5
15
\frac{5}{15}
155。但是,按照上面的计算方法,
p
(
a
∣
a
b
)
p(a|ab)
p(a∣ab)的概率会始终为0,这样就会引入问题,由于我们训练语料有限,导致会出现概率为0的ngram。为了解决上面的问题,引入了折扣discounting方法,也叫做平滑smoothing方法。
折扣的含义就是从已有gram的概率中拿出来一部分给未出现的gram,同时保证概率和为1。有**回退(backoff)和插值(interpolate)**两种方法来实现平滑。
(一)、回退
引入回退概率bow(ab),对于没有出现的gram(例子中的aba),可以使用回退概率表示如下:
p
(
a
∣
a
b
)
=
b
o
w
(
a
b
)
∗
p
(
a
∣
b
)
p(a|ab)=bow(ab)*p(a|b)
p(a∣ab)=bow(ab)∗p(a∣b)
与此同时,对于出现过的gram,需要减去一部分值给回退概率,以保证
∑
p
(
∗
∣
a
b
)
=
1
\sum p(*|ab)=1
∑p(∗∣ab)=1,可以表示如下:
p
(
c
∣
a
b
)
=
c
(
a
b
c
)
−
D
c
(
a
b
)
p(c|ab)=\frac{c(abc)-D}{c(ab)}
p(c∣ab)=c(ab)c(abc)−D
p
(
d
∣
a
b
)
=
c
(
a
b
d
)
−
D
c
(
a
b
)
p(d|ab)=\frac{c(abd)-D}{c(ab)}
p(d∣ab)=c(ab)c(abd)−D
由概率和为1的限制:
p
(
c
∣
a
b
)
+
p
(
d
∣
a
b
)
+
p
(
a
∣
a
b
)
+
p
(
b
∣
a
b
)
=
1
p(c|ab)+p(d|ab)+p(a|ab)+p(b|ab)=1
p(c∣ab)+p(d∣ab)+p(a∣ab)+p(b∣ab)=1
可以得出回退概率bow(ab):
b
o
w
(
a
b
)
=
1
−
p
(
c
∣
a
b
)
−
p
(
d
∣
a
b
)
p
(
a
∣
b
)
+
p
(
b
∣
b
)
bow(ab)=\frac{1-p(c|ab)-p(d|ab)}{p(a|b)+p(b|b)}
bow(ab)=p(a∣b)+p(b∣b)1−p(c∣ab)−p(d∣ab)
如果给出折扣变量D的值,那么就可以根据上式计算出bow(ab)。不同的算法计算D的方式不同。
(二)、插值
和上面的回退类似,引入回退概率来实现对没有出现的gram的概率表示,不同之处在于不仅未出现的gram引入回退,已出现的gram概率的计算也要引入回退:
p
(
a
∣
a
b
)
=
b
o
w
(
a
b
)
∗
p
(
a
∣
b
)
p(a|ab)=bow(ab)*p(a|b)
p(a∣ab)=bow(ab)∗p(a∣b)
p
(
b
∣
a
b
)
=
b
o
w
(
a
b
)
∗
p
(
b
∣
b
)
p(b|ab)=bow(ab)*p(b|b)
p(b∣ab)=bow(ab)∗p(b∣b)
p
(
c
∣
a
b
)
=
c
(
a
b
c
)
−
D
c
(
a
b
)
+
b
o
w
(
a
b
)
∗
p
(
c
∣
b
)
p(c|ab)=\frac{c(abc)-D}{c(ab)}+bow(ab)*p(c|b)
p(c∣ab)=c(ab)c(abc)−D+bow(ab)∗p(c∣b)
p
(
d
∣
a
b
)
=
c
(
a
b
d
)
−
D
c
(
a
b
)
+
b
o
w
(
a
b
)
∗
p
(
d
∣
b
)
p(d|ab)=\frac{c(abd)-D}{c(ab)}+bow(ab)*p(d|b)
p(d∣ab)=c(ab)c(abd)−D+bow(ab)∗p(d∣b)
根据经验,插值相比于回退来讲,效果更好。
(三)、总结
参考ngram-discount
假设Z表示全集,Z1表示c(a_z)>0的集合,Z0表示c(a_z)=0的集合
回退方法:
p
(
a
_
z
)
=
(
c
(
a
_
z
)
>
0
)
?
f
(
a
_
z
)
:
b
o
w
(
a
_
)
p
(
_
z
)
p(a\_z)=(c(a\_z)>0) ? f(a\_z) : bow(a\_)p(\_z)
p(a_z)=(c(a_z)>0)?f(a_z):bow(a_)p(_z)
b
o
w
(
a
_
)
=
(
1
−
S
u
m
_
Z
1
f
(
a
_
z
)
)
/
(
1
−
S
u
m
_
Z
1
p
(
_
z
)
)
=
(
1
−
S
u
m
_
Z
1
f
(
a
_
z
)
)
/
(
1
−
S
u
m
_
Z
1
f
(
_
z
)
)
bow(a\_)=(1-Sum\_Z1 f(a\_z)) / (1-Sum\_Z1 p(\_z))=(1-Sum\_Z1 f(a\_z)) / (1-Sum\_Z1 f(\_z))
bow(a_)=(1−Sum_Z1f(a_z))/(1−Sum_Z1p(_z))=(1−Sum_Z1f(a_z))/(1−Sum_Z1f(_z))
插值方法:
p
(
a
_
z
)
=
(
c
(
a
_
z
)
>
0
)
?
g
(
a
_
z
)
+
b
o
w
(
a
_
)
p
(
_
z
)
:
b
o
w
(
a
_
)
p
(
_
z
)
p(a\_z)=(c(a\_z)>0) ? g(a\_z)+bow(a\_)p(\_z) : bow(a\_)p(\_z)
p(a_z)=(c(a_z)>0)?g(a_z)+bow(a_)p(_z):bow(a_)p(_z)
b
o
w
(
a
_
)
=
1
−
S
u
m
_
Z
1
g
(
a
_
z
)
bow(a\_)=1-Sum\_Z1 g(a\_z)
bow(a_)=1−Sum_Z1g(a_z)
二、平滑算法
有很多平滑算法,平滑算法默认都是使用回退的形式,部分算法使用-interpolate参数可以使用插值方式。
(一)、Good-Turing
Good-Turing是ngram-count默认使用的算法,针对gram的出现次数分别处理。次数的限制使用参数-gtnmax控制,默认的1gram的次数大于1,其他gram的次数大于7认为是可信的。f(a_z)的计算方法如下:
- c(a_z) > gtmax
概率的计算使用原始的最大似然估计:
f ( a _ z ) = c ( a _ z ) c ( a _ ) f(a\_z)=\frac{c(a\_z)}{c(a\_)} f(a_z)=c(a_)c(a_z) - 1 <= c(a_z) <= gtmax
这种情况,认为次数是不可信的,需要对该次数做出修正,如下:
A = ( g t m a x + 1 ) n [ g t m a x + 1 ] n [ 1 ] A=(gtmax+1)\frac{n[gtmax+1]}{n[1]} A=(gtmax+1)n[1]n[gtmax+1]
c ′ ( a _ z ) = ( c ( a _ z ) + 1 ) n [ c ( a _ z ) + 1 ] n [ c ( a _ z ) ] c'(a\_z)=(c(a\_z)+1)\frac{n[c(a\_z)+1]}{n[c(a\_z)]} c′(a_z)=(c(a_z)+1)n[c(a_z)]n[c(a_z)+1]
f ( a _ z ) = c ( a _ z ) c ( a _ ) ( c ′ ( a _ z ) / c ( a _ z ) − A ) ( 1 − A ) f(a\_z)=\frac{c(a\_z)}{c(a\_)}\frac{(c'(a\_z)/c(a\_z)-A)}{(1-A)} f(a_z)=c(a_)c(a_z)(1−A)(c′(a_z)/c(a_z)−A)
上式中n[x]代表出现次数是x的gram的个数。
该平滑算法不支持插值,只支持回退,所以最终的概率计算如下:
p ( a _ z ) = ( c ( a _ z ) > 0 ) ? f ( a _ z ) : b o w ( a _ ) p ( _ z ) p(a\_z)=(c(a\_z)>0) ? f(a\_z):bow(a\_)p(\_z) p(a_z)=(c(a_z)>0)?f(a_z):bow(a_)p(_z)
b o w ( a _ ) = ( 1 − S u m _ Z 1 f ( a _ z ) ) / ( 1 − S u m _ Z 1 f ( _ z ) ) bow(a\_)=(1-Sum\_Z1 f(a\_z)) / (1-Sum\_Z1 f(\_z)) bow(a_)=(1−Sum_Z1f(a_z))/(1−Sum_Z1f(_z))
(二)、Kneser-Ney
该算法的思想是修改回退概率的计算方法。
- 对于回退方法来讲,公式修改为如下形式:
f ( a _ z ) = c ( a _ z ) − D 0 c ( a _ ) f(a\_z)=\frac{c(a\_z)-D0}{c(a\_)} f(a_z)=c(a_)c(a_z)−D0高阶gram
f ( _ z ) = n ( ∗ _ z ) − D 1 n ( ∗ _ ∗ ) f(\_z)=\frac{n(*\_z)-D1}{n(*\_*)} f(_z)=n(∗_∗)n(∗_z)−D1低阶gram
其中,n(_)表示满足*_*格式的gram的种类数。 - 对于插值方法来讲,公式修改为如下形式:
g ( a _ z ) = m a x ( 0 , c ( a _ z ) − D ) / c ( a _ ) g(a\_z)=max(0, c(a\_z)-D)/c(a\_) g(a_z)=max(0,c(a_z)−D)/c(a_)高阶gram
b o w ( a _ ) = 1 − S u m _ Z 1 g ( a _ z ) = c ( a _ ) − S u m _ Z 1 c ( a _ z ) + S u m _ Z 1 D c ( a _ ) bow(a\_)=1-Sum\_Z1 g(a\_z)=\frac{c(a\_)-Sum\_Z1c(a\_z)+Sum\_Z1D}{c(a\_)} bow(a_)=1−Sum_Z1g(a_z)=c(a_)c(a_)−Sum_Z1c(a_z)+Sum_Z1D
= S u m _ Z 1 D c ( a _ ) = n ( a _ ∗ ) D c ( a _ ) =\frac{Sum\_Z1D}{c(a\_)}=\frac{n(a\_*)D}{c(a\_)} =c(a_)Sum_Z1D=c(a_)n(a_∗)D高阶gram
假设Z2表示集合n(*_z)>0,那么可以得到低阶gram回退概率:
g ( _ z ) = m a x ( 0 , n ( ∗ _ z ) − D ) / n ( ∗ _ ∗ ) g(\_z)=max(0, n(*\_z)-D)/n(*\_*) g(_z)=max(0,n(∗_z)−D)/n(∗_∗)低阶gram
b o w ( _ ) = 1 − S u m _ Z 2 g ( _ z ) = n ( ∗ _ ∗ ) − S u m _ Z 2 n ( ∗ _ z ) + S u m _ Z 2 D n ( ∗ _ ∗ ) = D n ( _ ∗ ) n ( ∗ _ ∗ ) bow(\_)=1-Sum\_Z2 g(\_z)=\frac{n(*\_*)-Sum\_Z2n(*\_z)+Sum\_Z2D}{n(*\_*)}=\frac{Dn(\_*)}{n(*\_*)} bow(_)=1−Sum_Z2g(_z)=n(∗_∗)n(∗_∗)−Sum_Z2n(∗_z)+Sum_Z2D=n(∗_∗)Dn(_∗)低阶gram - D的计算方法
原始的Knser-Ney方法(-ukndiscount)对于每一阶的ngram使用相同的常数D
D = n 1 n 1 + 2 ∗ n 2 D=\frac{n1}{n1+2*n2} D=n1+2∗n2n1
n1表示出现一次的ngram的个数,n2表示出现两次的ngram的个数
后来,Chen和Goodman修改了常数D的计算方法(-kndiscount),针对每一阶的ngram,都有三个常数:
D1-出现一次的ngram个数
D2-出现两次的ngram个数
D3-出现三次及以上的ngram个数
计算如下:
Y = n 1 n 1 + 2 ∗ n 2 Y=\frac{n1}{n1+2*n2} Y=n1+2∗n2n1
D 1 = 1 − 2 Y n 2 n 1 D1=1-2Y\frac{n2}{n1} D1=1−2Yn1n2
D 2 = 2 − 3 Y n 3 n 2 D2=2-3Y\frac{n3}{n2} D2=2−3Yn2n3
D 3 = 3 − 4 Y n 4 n 3 D3=3-4Y\frac{n4}{n3} D3=3−4Yn3n4
三、tricks
- 效果最好的modified Knser-Ney算法,使用插值方法,即-kndiscount -interpolate
- -gtnmin表示出现次数小于特定值的gram将会被丢弃,对于所有的平滑算法都一样。默认是所有的1元和2元都会保留,其他阶的count>=2的才会保留。
- -gtnmax表示次数小于特定值的count将会被修改,只是针对Good-Turing算法而言。