我相信在 NgramModel 中计算退避平滑的方式存在错误。
我认为退避计算应该执行以下操作,例如上下文“b”:
这实质上交换了在二元上下文中未观察到的单词的一元概率,并适当地缩放,以填充缺失的概率质量。
我错过了什么吗? NgramModel 中的代码看起来有些不同,我无法理解。
我相信你是正确的,这确实是一个错误。
如果我们假设这个设置:
from nltk.model import NgramModel
from nltk.probability import LidstoneProbDist
word_seq = list('aaaababaaccbacb')
words = ['a', 'b', 'c', '']
est = lambda freqdist, bins: LidstoneProbDist(freqdist, 0.2, bins=bins)
model = NgramModel(2, word_seq, True, True, est, 4)
我们可以很快看到差异:
sum(model.prob(w, ['b']) for w in words)
Out[150]: 2.4583333333333335
sum(model.prob(w, ['a']) for w in words)
Out[151]: 1.0
[(w, model.prob(w, ['b'])) for w in words]
Out[152]:
[('a', 0.6666666666666667),
('b', 0.875),
('c', 0.6666666666666667),
('', 0.25)]
[(w, model.prob(w, ['a'])) for w in words]
Out[153]:
[('a', 0.47727272727272724),
('b', 0.25),
('c', 0.25),
('', 0.022727272727272728)]
前段时间我在研究 NgramModel 时,我记得我还发现实现回退的方式有点令人困惑。 现在我已经很久没有看它了,我已经失去了对它如何工作的直观理解。 在我看来,我们声称我们正在实施 Katz Back-off,但计算与 Wikipedia 上的计算有些不同。
我相信这是因为从NgramModel._beta
调用的LidstoneProbDist.discount
函数已经考虑了求和,但我必须更多地研究它。
def _alpha(self, tokens):
return self._beta(tokens) / self._backoff._beta(tokens[1:])
def _beta(self, tokens):
return (self[tokens].discount() if tokens in self else 1)
在我看来,beta 计算是出了问题的地方,因为二元级别的 beta 比一元级别的 beta 大得多,这使得比率 alpha 为正。
model._beta(('b',))
Out[154]: 0.16666666666666669
model._backoff._beta(())
Out[155]: 0.05063291139240506
model._alpha(('b',))
Out[155]: 3.291666666666667
我还排除了实际的 LidstoneProbDist 本身有问题:
[(w, model._model[('b',)].prob(w)) for w in words]
Out[159]:
[('a', 0.6666666666666667),
('b', 0.04166666666666667),
('c', 0.04166666666666667),
('', 0.25)]
sum([model._model[('b',)].prob(w) for w in words])
Out[161]: 1.0
我将尝试弄清楚所有这些部分是如何再次相互连接的,看看我是否能解决这个问题。 虽然如果其他人想加入(比如@desilinguist),我会很感激另一双眼睛。
您好,感谢您检查到这一点。 还有一些想法:
首先,令人困惑的一件事是“折扣”的不同概念。 存在通过各种平滑方法实现的折扣。 例如,简单的拉普拉斯算子(加一个)平滑会降低观察到的单词的概率,并将该质量转移到未观察到的单词上。 在 _beta 函数中调用的 discount() 函数用于由 ProbDist 完成的平滑,而不(我认为)与退避平滑无关。 我认为折扣的退避概念与高阶模型中不同上下文的单词子集“缺失”(未观察到)的概率有关。
因此,我出于自己的目的修改了代码,以做我认为正确的事情,并且我在下面分享了一些片段。 基本上,我确定给定上下文模型中缺失的词的子集,并为该子集计算这些“缺失”词的总概率以及退避模型中的相应数量。 该比率是“alpha”,请注意这是上下文的函数。 我认为此实现对应于您提供的维基百科链接中的内容。 此外,在我的情况下不再使用 _beta 函数。
希望这对讨论有用。 再次感谢。
# (Code fragment for calculating backoff)
# Now, for Katz backoff smoothing we need to calculate the alphas
if self._backoff is not None:
self._backoff_alphas = dict()
# For each condition (or context)
for ctxt in self._cfd.conditions():
pd = self._model[ctxt] # prob dist for this context
backoff_ctxt = ctxt[1:]
backoff_total_pr = 0
total_observed_pr = 0
for word in self._cfd[ctxt].keys(): # this is the subset of words that we OBSERVED
backoff_total_pr += self._backoff.prob(word,backoff_ctxt)
total_observed_pr += pd.prob(word)
assert total_observed_pr <= 1 and total_observed_pr > 0
assert backoff_total_pr <= 1 and backoff_total_pr > 0
alpha_ctxt = (1.0-total_observed_pr) / (1.0-backoff_total_pr)
self._backoff_alphas[ctxt] = alpha_ctxt
# Updated _alpha function, discarded the _beta function
def _alpha(self, tokens):
"""Get the backoff alpha value for the given context
"""
if tokens in self._backoff_alphas:
return self._backoff_alphas[tokens]
else:
return 1
大家好,我只是想加入这个讨论,并指出这个问题比简单地使概率不等于 1.0 更糟糕
考虑以下三元组示例:
#!/usr/bin/python
from nltk.model import NgramModel
from nltk.probability import LidstoneProbDist
word_seq = ['foo', 'foo', 'foo', 'foo', 'bar', 'baz']
# Set up a trigram model, nothing special
est = lambda freqdist, bins: LidstoneProbDist(freqdist, 0.2, bins)
model = NgramModel(3, word_seq, True, True, est, 3)
# Consider the ngram ['bar', 'baz', 'foo']
# We've never seen this before, so the trigram model will fall back
context = ('bar', 'baz',)
word = 'foo'
print "P(foo | bar, baz) = " + str(model.prob(word,context))
# Result:
# P(foo | bar, baz) = 2.625
是的——这个条件概率 > 1.0
令人讨厌的部分是模型回退得越多,概率就越膨胀。
随着我们添加更多的训练示例,问题也会变得更糟!
word_seq = ['foo' for i in range(0,10000)]
word_seq.append('bar')
word_seq.append('baz')
est = lambda freqdist, bins: LidstoneProbDist(freqdist, 0.2, bins)
model = NgramModel(3, word_seq, True, True, est, 3)
# Consider the ngram ['bar', 'baz', 'foo']
# We've never seen this before, so the trigram model will fall back
context = ('bar', 'baz',)
word = 'foo'
print "P(foo | bar, baz) = " + str(model.prob(word,context))
# Result:
P(foo | bar, baz) = 6250.125
就目前而言,不能依赖 NgramModel - 至少不能依赖加性 Lidstone 平滑。
@afourney :我相信这是有意的(LidstoneProbDist 有SUM_TO_ONE = False
属性)
@afourney我同意在解决此问题之前不能真正使用 NgramModel。 不幸的是,我最近没有时间对此进行尝试。
@kmike SUM_TO_ONE 对于 LidstoneProbDist 为 False,因为如果您遇到不在初始分布中的事件并且您没有将 bin 值设置为可能事件的数量,那么它的总和不会为 1。 但如果使用得当,它确实会加起来为一。 这里的问题是 NgramModel 的 beta 计算,而不是 LidstoneProbDist 本身。
@kmike :是的,我注意到 SUM_TO_ONE 是错误的。 我担心的是该模型在将它们合并到求和之前返回已经大于 1 的单个条件概率(对于单个事件)。
@bcroy我认为您的解决方案是正确的方法。 简单地说,_alpha 执行两个重要的任务:
话虽如此,如果 NgramModel 还提供插值策略作为回退策略的替代方案,那就太好了。 这将支持 Jelinek-Mercer 或 Witten-Bell 平滑——我发现后者很简单,并且工作得很好。 见: http :
有人可以确认这仍然是一个开放的错误吗?
是的,我仍然得到 P(foo | bar, baz) = 2.625
大家好你们好,
这个问题有进展吗? 它仍然是一个开放的错误吗? 我得到 P(foo | bar, baz) = 2.625 所以问题继续存在。
我认为这是一个重要的问题,应该已经解决了,因为语言模型用于 NLP 中的几乎所有应用程序。
不幸的是,我没有时间查看NgramModel
的众多问题,而且我预计自己不会很快这样做。 在有人解决这些错误之前, NgramModel
已从 nltk 中删除。
丹,谢谢你的回答。
只是检查更新。 我可以看到一些问题已经解决,但只是想确保它还远不能使用?
@ZeerakW不幸的是,ngram 模型几乎没有进展,而且还没有人承诺解决这个问题。
进入 2016 年,“ngram 模型”问题没有任何进展。
伙计们,我们终于可以关闭这个了:)
2018 年更新。已经毕业并开始工作,但仍然存在 Ngram 问题
夏天!
最有用的评论
进入 2016 年,“ngram 模型”问题没有任何进展。