Nltk: Erreur dans le calcul du lissage du backoff NgramModel ?

Créé le 7 mars 2013  ·  18Commentaires  ·  Source: nltk/nltk

Je pense qu'il y a une erreur dans la façon dont le lissage d'attente est calculé dans NgramModel.

  • Considérez la séquence de "mots": aaaababaaccbacb avec les mots ['a','b','c']
  • Construisez un modèle bigramme (n=2). Pour plus de simplicité, utilisez le lissage LidstoneProbDist
  • Notamment, cette séquence ne contient pas tous les bigrammes préfixés par « b » ou « c ». Ainsi, un backoff est nécessaire pour obtenir la probabilité de bigramme 'bb', 'bc' et 'ca'
  • Pour context = ['a'], model.prob(w,context) semble bon pour tous les mots et somme à 1
  • Pour context = ['b'], model.prob(w,context) ne semble pas correct, les sommes sont > 1

J'ai pensé que le calcul de backoff devrait faire ce qui suit, disons pour le contexte « b » :

  • Calculez la probabilité totale « manquante » pour les valeurs invisibles dans le contexte « b » (c'est-à-dire bb et bc), au niveau du bigramme, appelez cela Beta2
  • Calculez la probabilité totale d'unigramme pour ces valeurs invisibles (c'est-à-dire 'b' et 'c'), appelez cela Beta1
  • return (Beta2 / Beta1) * backoff.prob()

Cela permute essentiellement les probabilités d'unigramme pour les mots qui n'étaient pas observés dans le contexte du bigramme, mis à l'échelle de manière appropriée, pour combler la masse de probabilité manquante.

Est-ce que j'ai raté quelque chose ? Le code dans NgramModel fait quelque chose d'assez différent, semble-t-il, et je n'arrivais pas à le comprendre.

language-model

Commentaire le plus utile

L'entrée en 2016 et le problème du « modèle ngram » n'ont connu aucune avancée.

Tous les 18 commentaires

Je pense que vous avez raison, il s'agit bien d'un bug.

Si nous supposons cette configuration :

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)

On voit assez rapidement les écarts :

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

Lorsque je travaillais sur le NgramModel il y a quelque temps, je me souviens également avoir trouvé la manière dont le back-off était implémentée un peu déroutante. Maintenant que je ne l'ai pas regardé depuis longtemps, j'ai perdu la compréhension intuitive que j'avais de son fonctionnement. Il me semble que nous prétendons implémenter Katz Back-off, mais les calculs sont un peu différents de ceux de Wikipédia .

Je crois que c'est parce que la fonction LidstoneProbDist.discount appelée à partir de NgramModel._beta prend déjà en compte la sommation, mais je vais devoir m'y intéresser davantage.

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)

Il me semble que les calculs bêta sont là où les choses ne vont pas, car le bêta au niveau du bigramme est beaucoup plus important que le bêta au niveau de l'unigramme, ce qui rend le rapport alpha positif.

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

J'ai également exclu que c'est le LidstoneProbDist lui-même qui a un problème :

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

Je vais essayer de comprendre comment toutes ces pièces sont à nouveau interconnectées et voir si je peux résoudre ce problème. Bien que si quelqu'un d'autre veut intervenir (comme @desilinguist), j'apprécierais un autre regard à ce sujet.

Salut, et merci d'avoir vérifié cela. Encore quelques réflexions :

Premièrement, une chose qui prête à confusion, ce sont les différentes notions de « remise ». Il y a l'actualisation obtenue par les différentes méthodes de lissage. Par exemple, un simple lissage laplacien (ajoutez un) réduit la probabilité des mots observés et déplace cette masse vers les mots non observés. La fonction discount () appelée dans la fonction _beta est destinée au lissage effectué par ProbDist et n'est pas (je ne pense pas) pertinente pour le lissage d'attente. Je pense que la notion d'escompte d'actualisation a à voir avec la probabilité du sous-ensemble de mots qui sont "manquants" (non observés) pour différents contextes dans le modèle d'ordre supérieur.

J'ai donc modifié le code à mes propres fins pour faire ce que je pense être la bonne chose, et j'ai partagé quelques extraits ci-dessous. Fondamentalement, j'identifie le sous-ensemble de mots qui manquent dans le modèle pour un contexte donné, et pour ce sous-ensemble, je calcule la probabilité totale pour ces mots « manquants » et la quantité correspondante dans le modèle d'attente. Le ratio est « alpha », et notez qu'il est fonction du contexte. Je pense que cette implémentation correspond à ce qui se trouve dans le lien Wikipedia que vous fournissez. De plus, la fonction _beta n'est plus utilisée dans mon cas.

J'espère que cela sera utile pour la discussion. Merci encore.

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

Salut à tous, je voulais juste intervenir sur cette discussion et souligner que le problème est bien pire que le simple fait que les probabilités ne parviennent pas à totaliser 1,0

Considérons l'exemple de trigramme suivant :

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

Oui -- cette probabilité conditionnelle est > 1,0

Le pire, c'est que plus les modèles reculent, plus les probabilités deviennent gonflées.

Le problème s'aggrave également à mesure que nous ajoutons plus d'exemples de formation !

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

Dans l'état actuel des choses, on ne peut pas se fier au NgramModel - du moins pas avec le lissage additif Lidstone.

@afourney : Je crois que c'est voulu (LidstoneProbDist a l'attribut SUM_TO_ONE = False )

@afourney Je suis d'accord pour dire que NgramModel ne peut pas vraiment être utilisé tant que cela n'est pas résolu. Malheureusement, je n'ai tout simplement pas eu le temps de m'y atteler récemment.

@kmike SUM_TO_ONE est False pour LidstoneProbDist car si vous rencontrez un événement qui n'était pas dans la distribution initiale et que vous n'avez pas défini la valeur des bacs comme étant le nombre d'événements possibles, la somme ne sera pas un. Mais s'il est utilisé correctement, il s'élèvera en effet à un. Le problème ici est le calcul bêta de NgramModel, pas LidstoneProbDist lui-même.

@kmike : Ouais, j'ai remarqué que SUM_TO_ONE était faux. Ma préoccupation était que le modèle renvoyait des probabilités conditionnelles individuelles (pour des événements uniques) qui étaient déjà supérieures à 1 - avant de les incorporer dans la sommation.

@bcroy Je pense que votre solution est la bonne approche. En termes simples, _alpha effectue deux tâches importantes :

  1. Il renormalise le modèle de backoff pour le contexte donné afin d'exclure les mots qui sont déjà pris en compte par le modèle d'ordre supérieur actuel.
  2. Il met à l'échelle le modèle de temporisation renormalisé pour « ajuster » la probabilité « manquante » / actualisée du _modèle actuel.

Cela étant dit, ce serait bien si le NgramModel proposait également une stratégie d'interpolation comme alternative à la stratégie de backoff. Cela permettrait la prise en charge du lissage Jelinek-Mercer ou Witten-Bell - ce dernier que j'ai trouvé simple et fonctionnant assez bien. Voir : http://nlp.stanford.edu/~wcmac/papers/20050421-smoothing-tutorial.pdf

Quelqu'un peut-il confirmer qu'il s'agit toujours d'un bug ouvert ?

Oui, j'obtiens toujours P(foo | bar, baz) = 2,625

Salut tout le monde,

Y a-t-il des progrès avec ce problème? Est-ce toujours un bug ouvert ? J'obtiens P(foo | bar, baz) = 2,625 donc le problème persiste.

Je pense que c'est un problème important et qu'il aurait dû être corrigé car les modèles de langage sont utilisés pour presque toutes les applications en PNL.

Malheureusement, je n'ai pas eu le temps d'examiner les nombreux problèmes avec NgramModel , et je ne pense pas pouvoir le faire de si tôt. Jusqu'à ce que quelqu'un s'attaque à ces bogues, NgramModel a été supprimé de nltk.

Dan, merci pour la réponse.

Je me connecte juste pour une mise à jour. Je peux voir que certains problèmes ont été résolus, mais je veux juste m'assurer qu'il est encore loin d'être utilisable ?

@ZeerakW Malheureusement, il y a eu peu de progrès dans les modèles ngram, et personne ne s'est encore engagé à s'y attaquer.

L'entrée en 2016 et le problème du « modèle ngram » n'ont connu aucune avancée.

Les amis, nous pouvons enfin fermer ceci :)

Mise à jour 2018. Déjà diplômé et commencé à travailler et le problème Ngram existe toujours

Été!

Cette page vous a été utile?
0 / 5 - 0 notes