Nltk: NgramModel 退避平滑计算错误?

创建于 2013-03-07  ·  18评论  ·  资料来源: nltk/nltk

我相信在 NgramModel 中计算退避平滑的方式存在错误。

  • 考虑“单词”的序列: aaaababaaccbacb 带有单词 ['a','b','c']
  • 建立一个二元模型(n=2)。 为简单起见,使用 LidstoneProbDist 平滑
  • 值得注意的是,这个序列不包含所有以“b”或“c”为前缀的二元组。 因此,需要退避来获得二元组 'bb'、'bc' 和 'ca' 的概率
  • 对于 context = ['a'],model.prob(w,context) 看起来适合所有单词并且总和为 1
  • 对于 context = ['b'],model.prob(w,context) 看起来不正确,总和 > 1

我认为退避计算应该执行以下操作,例如上下文“b”:

  • 计算'b'上下文(即bb和bc)中未见值的总“缺失”概率,在二元级别,称之为Beta2
  • 计算这些看不见的值(即'b'和'c')的总一元概率,称之为Beta1
  • 返回 (Beta2 / Beta1) * backoff.prob()

这实质上交换了在二元上下文中未观察到的单词的一元概率,并适当地缩放,以填充缺失的概率质量。

我错过了什么吗? NgramModel 中的代码看起来有些不同,我无法理解。

language-model

最有用的评论

进入 2016 年,“ngram 模型”问题没有任何进展。

所有18条评论

我相信你是正确的,这确实是一个错误。

如果我们假设这个设置:

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 执行两个重要的任务:

  1. 它重新规范给定上下文的退避模型,以排除当前高阶模型已经考虑的单词。
  2. 它缩放重新归一化的退避模型以“拟合”当前 _model 的“缺失”/折扣概率。

话虽如此,如果 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 问题

夏天!

此页面是否有帮助?
0 / 5 - 0 等级