Nltk: Fehler in der NgramModel-Backoff-Glättungsberechnung?

Erstellt am 7. März 2013  ·  18Kommentare  ·  Quelle: nltk/nltk

Ich glaube, es liegt ein Fehler bei der Berechnung der Backoff-Glättung in NgramModel vor.

  • Betrachten Sie die Folge von "Wörtern": aaaababaaccbacb mit Wörtern ['a','b','c']
  • Erstellen Sie ein Bigramm-Modell (n=2). Der Einfachheit halber verwenden Sie LidstoneProbDist Glättung
  • Bemerkenswerterweise enthält diese Sequenz nicht alle Bigramme mit dem Präfix 'b' oder 'c'. Daher ist ein Backoff erforderlich, um die Wahrscheinlichkeit von Bigramm 'bb', 'bc' und 'ca' zu erhalten.
  • Für context = ['a'] sieht model.prob(w,context) für alle Wörter gut aus und summiert zu 1
  • Für context = ['b'] sieht model.prob(w,context) nicht richtig aus, summiert sich zu > 1

Ich dachte, dass die Backoff-Berechnung Folgendes tun sollte, beispielsweise für den Kontext 'b':

  • Berechnen Sie die gesamte "fehlende" Wahrscheinlichkeit für unsichtbare Werte im 'b'-Kontext (dh bb und bc), auf Bigramm-Ebene, nennen Sie dies Beta2
  • Berechnen Sie die Gesamt-Unigramm-Wahrscheinlichkeit für diese unsichtbaren Werte (dh 'b' und 'c'), nennen Sie dies Beta1
  • zurück (Beta2 / Beta1) * backoff.prob()

Dies tauscht im Wesentlichen die Unigramm-Wahrscheinlichkeiten gegen diejenigen Wörter ein, die im Bigramm-Kontext nicht beobachtet wurden, entsprechend skaliert, um die fehlende Wahrscheinlichkeitsmasse auszufüllen.

Verpasse ich etwas? Der Code in NgramModel macht anscheinend etwas ganz anderes, und ich konnte es nicht verstehen.

language-model

Hilfreichster Kommentar

Der Eintritt in das Jahr 2016 und die Ausgabe des "Ngram-Modells" hat keine Fortschritte gebracht.

Alle 18 Kommentare

Ich glaube, Sie haben Recht, dass dies tatsächlich ein Fehler ist.

Wenn wir dieses Setup annehmen:

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)

Wir können ziemlich schnell die Diskrepanzen sehen:

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)]

Als ich vor einiger Zeit am NgramModel arbeitete, erinnere ich mich, dass ich auch die Art und Weise, wie Back-off implementiert wurde, ein wenig verwirrend fand. Jetzt, wo ich es lange nicht mehr angeschaut habe, habe ich mein intuitives Verständnis dafür verloren, wie es funktioniert. Es scheint mir, dass wir behaupten, dass wir Katz Back-off implementieren, aber die Berechnungen unterscheiden sich ein wenig von denen auf Wikipedia .

Ich glaube, es liegt daran, dass die LidstoneProbDist.discount Funktion, die von NgramModel._beta aufgerufen wird, bereits die Summierung berücksichtigt, aber ich muss mir das genauer ansehen.

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)

Es scheint mir, dass die Beta-Berechnungen der Fehler sind, da Beta auf Bigramm-Ebene viel größer ist als Beta auf Unigramm-Ebene, was das Verhältnis Alpha positiv macht.

model._beta(('b',))
Out[154]: 0.16666666666666669
model._backoff._beta(())
Out[155]: 0.05063291139240506
model._alpha(('b',))
Out[155]: 3.291666666666667

Ich habe auch ausgeschlossen, dass der eigentliche LidstoneProbDist selbst ein Problem hat:

[(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

Ich werde versuchen herauszufinden, wie all diese Teile wieder miteinander verbunden sind und sehen, ob ich das beheben kann. Wenn jedoch jemand anderes einsteigen möchte (wie @desilinguist), würde ich mich über einen weiteren Blick darauf freuen.

Hallo, und danke, dass Sie sich das angeschaut haben. Nur noch ein paar Gedanken:

Verwirrend sind zunächst die unterschiedlichen Begriffe von "Rabatt". Da ist die Diskontierung, die durch die verschiedenen Glättungsverfahren erreicht wird. Zum Beispiel reduziert eine einfache Laplace-Glättung (eins addieren) die Wahrscheinlichkeit für beobachtete Wörter und verschiebt diese Masse auf die unbeobachteten Wörter. Die in der _beta-Funktion aufgerufene Discount()-Funktion dient der Glättung durch ProbDist und ist (glaube ich nicht) für die Backoff-Glättung relevant. Ich denke, der Backoff-Begriff des Diskontierens hat mit der Wahrscheinlichkeit der Teilmenge von Wörtern zu tun, die für verschiedene Kontexte im Modell höherer Ordnung "fehlen" (unbeobachtet).

Also habe ich den Code für meine eigenen Zwecke modifiziert, um das zu tun, was ich für richtig halte, und ich habe unten einige Ausschnitte geteilt. Grundsätzlich identifiziere ich die Teilmenge der Wörter, die im Modell für einen bestimmten Kontext fehlen, und berechne für diese Teilmenge die Gesamtwahrscheinlichkeit für diese "fehlenden" Wörter und die entsprechende Menge im Backoff-Modell. Das Verhältnis ist "Alpha", und beachten Sie, dass dies eine Funktion des Kontexts ist. Ich denke, diese Implementierung entspricht dem, was in dem von Ihnen bereitgestellten Wikipedia-Link steht. Außerdem wird die _beta-Funktion in meinem Fall nicht mehr verwendet.

Hoffe das ist für die Diskussion hilfreich. Danke noch einmal.

    # (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

Hallo zusammen, ich wollte mich nur in diese Diskussion einklinken und darauf hinweisen, dass das Problem viel schlimmer ist, als wenn die Wahrscheinlichkeiten nicht auf 1,0 summieren

Betrachten Sie das folgende Trigramm-Beispiel:

#!/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

Yup -- diese bedingte Wahrscheinlichkeit ist > 1.0

Das Schlimme daran ist, dass die Wahrscheinlichkeiten um so mehr aufgebläht werden, je mehr die Modelle zurückfallen.

Das Problem wird auch noch schlimmer, wenn wir weitere Schulungsbeispiele hinzufügen!

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

Auf das NgramModel kann man sich derzeit nicht verlassen – zumindest nicht mit additiver Lidstone-Glättung.

@afourney : Ich glaube, das ist beabsichtigt (LidstoneProbDist hat das Attribut SUM_TO_ONE = False )

@afourney Ich stimme zu, dass NgramModel nicht wirklich verwendet werden kann, bis dies behoben ist. Leider hatte ich in letzter Zeit einfach keine Zeit, mich damit auseinanderzusetzen.

@kmike SUM_TO_ONE ist für LidstoneProbDist False, denn wenn Sie auf ein Ereignis stoßen, das nicht in der ursprünglichen Verteilung enthalten war, und Sie den bins-Wert nicht auf die Anzahl der möglichen Ereignisse festgelegt haben, wird die Summe nicht zu einem. Aber wenn es richtig verwendet wird, summiert es sich tatsächlich zu einem. Das Problem hier ist die Beta-Berechnung von NgramModel, nicht LidstoneProbDist selbst.

@kmike : Ja, mir ist aufgefallen, dass SUM_TO_ONE falsch war. Meine Sorge war, dass das Modell individuelle bedingte Wahrscheinlichkeiten (für einzelne Ereignisse) zurückgab, die bereits größer als 1 waren – bevor sie in die Summation aufgenommen wurden.

@bcroy Ich denke, Ihre Lösung ist der richtige Ansatz. Einfach gesagt führt _alpha zwei wichtige Aufgaben aus:

  1. Es renormiert das Backoff-Modell für den gegebenen Kontext, um Wörter auszuschließen, die bereits durch das aktuelle Modell höherer Ordnung berücksichtigt werden.
  2. Es skaliert das renormalisierte Backoff-Modell, um die "fehlende" / diskontierte Wahrscheinlichkeit des aktuellen _Modells "anzupassen".

Trotzdem wäre es schön, wenn das NgramModel alternativ zur Backoff-Strategie auch eine Interpolationsstrategie anbieten würde. Dies würde die Unterstützung für Jelinek-Mercer- oder Witten-Bell-Glättung ermöglichen – letzteres fand ich einfach und funktionierte recht gut. Siehe: http://nlp.stanford.edu/~wcmac/papers/20050421-smoothing-tutorial.pdf

Kann jemand bestätigen, dass dies immer noch ein offener Fehler ist?

Ja, ich erhalte immer noch P(foo | bar, baz) = 2,625

Hallo zusammen,

Gibt es Fortschritte bei diesem Problem? Ist es immer noch ein offener Fehler? Ich erhalte P(foo | bar, baz) = 2,625, also bleibt das Problem bestehen.

Ich denke, dies ist ein wichtiges Problem und hätte behoben werden müssen, da Sprachmodelle für fast alle Anwendungen im NLP verwendet werden.

Leider hatte ich keine Zeit, mich mit den zahlreichen Problemen mit NgramModel , und ich kann mir auch nicht vorstellen, dies in absehbarer Zeit tun zu können. Bis jemand diese Fehler behebt, wurde NgramModel aus nltk entfernt.

Dan, danke für die Antwort.

Einfach einchecken, um ein Update zu erhalten. Ich kann sehen, dass einige Probleme geschlossen wurden, aber möchten Sie nur sicherstellen, dass sie noch lange nicht verwendet werden können?

@ZeerakW Leider gab es bei Ngram-Modellen nur wenige Fortschritte, und noch hat sich niemand verpflichtet, dies anzugehen.

Der Eintritt in das Jahr 2016 und die Ausgabe des "Ngram-Modells" hat keine Fortschritte gebracht.

Leute, wir können das endlich schließen :)

Update 2018. Bereits abgeschlossen und angefangen zu arbeiten und das Ngram-Problem existiert immer noch

Sommer!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen