Kneser-Ney使用Python NLTK平滑三卦

Jan*_*rff 7 python nlp nltk smoothing

我正在尝试使用Python NLTK使用Kneser-Ney平滑来平滑一组n-gram概率.不幸的是,整个文档相当稀疏.

我想要做的是:我将一个文本解析为三元组元组列表.从这个列表中我创建了一个FreqDist然后使用该FreqDist来计算KN平滑分布.

我很确定,结果是完全错误的.当我总结个人概率时,我得到的东西超越1.采取这个代码示例:

import nltk

ngrams = nltk.trigrams("What a piece of work is man! how noble in reason! how infinite in faculty! in \
form and moving how express and admirable! in action how like an angel! in apprehension how like a god! \
the beauty of the world, the paragon of animals!")

freq_dist = nltk.FreqDist(ngrams)
kneser_ney = nltk.KneserNeyProbDist(freq_dist)
prob_sum = 0
for i in kneser_ney.samples():
    prob_sum += kneser_ney.prob(i)
print(prob_sum)
Run Code Online (Sandbox Code Playgroud)

输出为"41.51696428571428".根据语料库大小,此值会无限大.这使得任何prob()返回除了概率分布之外的任何东西.

看看NLTK代码我会说实现是有问题的.也许我只是不明白代码应该如何使用.在那种情况下,你能给我一个提示吗?在任何其他情况下:你知道任何有效的Python实现吗?我真的不想自己实现它.

use*_*629 10

我认为你误解了Kneser-Ney的计算方式.

来自维基百科:

归一化常数λ 瓦特I-1 已经选择值小心使条件概率p的总和KN(瓦特我 | W I-1 )等于一.

当然,我们在这里讨论的是bigrams,但对于高阶模型,同样的原则也是如此.基本上这个引用意味着,对于固定的上下文w i-1(或更高阶模型的更多上下文),所有w i的概率必须加起来为1.当你将所有样本的概率相加时,你正在做的是包括多个上下文,这就是为什么你最终得到大于1的"概率".如果你保持上下文固定,如下面的代码示例,你最终数字<= 1.



    from nltk.util import ngrams
    from nltk.corpus import gutenberg

    gut_ngrams = ( ngram for sent in gutenberg.sents() for ngram in ngrams(sent, 3, pad_left = True, pad_right = True, right_pad_symbol='EOS', left_pad_symbol="BOS"))
    freq_dist = nltk.FreqDist(gut_ngrams)
    kneser_ney = nltk.KneserNeyProbDist(freq_dist)

    prob_sum = 0
    for i in kneser_ney.samples():
        if i[0] == "I" and i[1] == "confess":
            prob_sum += kneser_ney.prob(i)
            print "{0}:{1}".format(i, kneser_ney.prob(i))
    print prob_sum


基于NLTK Gutenberg语料库子集的输出如下.



    (u'I', u'confess', u'.--'):0.00657894736842
    (u'I', u'confess', u'what'):0.00657894736842
    (u'I', u'confess', u'myself'):0.00657894736842
    (u'I', u'confess', u'also'):0.00657894736842
    (u'I', u'confess', u'there'):0.00657894736842
    (u'I', u'confess', u',"'):0.0328947368421
    (u'I', u'confess', u'that'):0.164473684211
    (u'I', u'confess', u'"--'):0.00657894736842
    (u'I', u'confess', u'it'):0.0328947368421
    (u'I', u'confess', u';'):0.00657894736842
    (u'I', u'confess', u','):0.269736842105
    (u'I', u'confess', u'I'):0.164473684211
    (u'I', u'confess', u'unto'):0.00657894736842
    (u'I', u'confess', u'is'):0.00657894736842
    0.723684210526

Run Code Online (Sandbox Code Playgroud)

该和(.72)小于1的原因在于,仅在出现在语料库中的三元组中计算概率,其中第一个单词是"I"而第二个单词是"confess".其余的0.28概率是留给W¯¯ 我 S的不按"I"和文集"坦白".这是平滑的全部要点,从语料库中出现的ngrams中重新分配一些概率质量,而不是那样你不会得到一堆0概率ngrams.

也不行



    ngrams = nltk.trigrams("What a piece of work is man! how noble in reason! how infinite in faculty! in \
    form and moving how express and admirable! in action how like an angel! in apprehension how like a god! \
    the beauty of the world, the paragon of animals!")

Run Code Online (Sandbox Code Playgroud)

计算字符三元组?我认为这需要被标记化来计算单词三元组.


use*_*557 5

Kneser -Ney(也可以看看Goodman 和 Chen,对不同的平滑技术进行了很好的调查)是一个相当复杂的平滑,据我所知,只有少数几个包能做到这一点。不知道任何 python 实现,但如果你只需要概率等,你绝对可以尝试SRILM 。

  • 您的样本中很可能包含训练数据中未出现的单词(又称词汇外 (OOV)单词),如果处理不当,可能会扰乱您获得的概率。也许这会导致问题变得非常大且无效?

  • 感谢你的回答。我在上面的代码中所做的只是添加所有学习样本的概率。所以,我根本不检查任何未见过样本的概率。无论如何,未见样本的概率处理方式都很奇怪。如果我搜索未经训练的(w1,w2,w3),那么它会检查该值是否已知。如果不是,它给出 (w2,w3) 的平滑概率。如果这也是未知的,则给出 0.0。所以在几乎所有情况下,当检查未学习的三元组时,你都会得到 0.0,这是荒谬的,因为我不需要为此进行平滑。 (2认同)