Nltk: ¿Error en el cálculo de suavizado de retroceso de NgramModel?

Creado en 7 mar. 2013  ·  18Comentarios  ·  Fuente: nltk/nltk

Creo que hay un error en la forma en que se calcula el suavizado de retroceso en NgramModel.

  • Considere la secuencia de "palabras": aaaababaaccbacb con palabras ['a','b','c']
  • Construye un modelo de bigrama (n=2). Para simplificar, use el suavizado LidstoneProbDist
  • En particular, esta secuencia no contiene todos los bigramas con el prefijo 'b' o 'c'. Por lo tanto, se requiere un retroceso para obtener la probabilidad del bigrama 'bb', 'bc' y 'ca'
  • Para context = ['a'], model.prob(w,context) se ve bien para todas las palabras y sumas a 1
  • Para context = ['b'], model.prob(w,context) no se ve bien, suma > 1

Pensé que el cálculo de retroceso debería hacer lo siguiente, por ejemplo, para el contexto 'b':

  • Calcule la probabilidad total de "falta" para valores no vistos en el contexto 'b' (es decir, bb y bc), en el nivel de bigrama, llame a esto Beta2
  • Calcule la probabilidad total de unigramo para estos valores invisibles (es decir, 'b' y 'c'), llame a esto Beta1
  • retorno (Beta2 / Beta1) * backoff.prob()

Básicamente, esto cambia las probabilidades del unigrama por aquellas palabras que no se observaron en el contexto del bigrama, escaladas adecuadamente, para completar la masa de probabilidad faltante.

¿Me estoy perdiendo de algo? Parece que el código en NgramModel hace algo bastante diferente, y no pude entenderlo.

language-model

Comentario más útil

Entrando al 2016 y el tema del 'modelo ngram' no ha tenido avances.

Todos 18 comentarios

Creo que tienes razón en que esto es de hecho un error.

Si asumimos esta configuración:

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)

Podemos ver bastante rápidamente las discrepancias:

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

Cuando estaba trabajando en NgramModel hace un tiempo, recuerdo que también encontré un poco confuso la forma en que se implementó el retroceso. Ahora que no lo he mirado en mucho tiempo, he perdido la comprensión intuitiva que tenía de cómo funcionaba. Me parece que afirmamos que estamos implementando Katz Back-off, pero los cálculos son un poco diferentes a los de Wikipedia .

Creo que es porque la función LidstoneProbDist.discount llamada desde NgramModel._beta ya tiene en cuenta la suma, pero tendré que investigar más.

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)

Me parece que los cálculos beta son donde las cosas van mal, porque beta en el nivel de bigrama es mucho más grande que beta en el nivel de unigramo, lo que hace que la relación alfa sea positiva.

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

También descarté que es el propio LidstoneProbDist el que tiene un problema:

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

Intentaré averiguar cómo todas estas partes están interconectadas nuevamente y veré si puedo solucionarlo. Aunque si alguien más quiere participar (como @desilinguist), agradecería otro par de ojos en esto.

Hola, y gracias por revisar esto. Sólo algunos pensamientos más:

Primero, una cosa que es confusa son las diferentes nociones de "descuento". Existe el descuento logrado por los diversos métodos de suavizado. Por ejemplo, el suavizado laplaciano simple (agregar uno) descuenta la probabilidad de las palabras observadas y cambia esa masa a las palabras no observadas. La función de descuento () que se llama en la función _beta es para el suavizado realizado por ProbDist, y no (no creo) relevante para el suavizado de retroceso. Creo que la noción de retroceso de descuento tiene que ver con la probabilidad del subconjunto de palabras que "faltan" (no observadas) para diferentes contextos en el modelo de orden superior.

Por lo tanto, he modificado el código para mis propios fines para hacer lo que creo que es lo correcto, y he compartido algunos fragmentos a continuación. Básicamente, identifico el subconjunto de palabras que faltan en el modelo para un contexto determinado y, para ese subconjunto, calculo la probabilidad total de estas palabras "faltantes" y la cantidad correspondiente en el modelo de retroceso. La proporción es "alfa", y tenga en cuenta que esta es una función del contexto. Creo que esta implementación corresponde a lo que está en el enlace de Wikipedia que proporciona. Además, la función _beta ya no se usa en mi caso.

Espero que esto sea útil para la discusión. Gracias de nuevo.

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

Hola a todos, solo quería participar en esta discusión y señalar que el problema es mucho peor que simplemente tener las probabilidades que no suman 1.0

Considere el siguiente ejemplo de trigrama:

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

Sí, esta probabilidad condicional es > 1,0

La parte desagradable es que cuanto más retroceden los modelos, más se inflan las probabilidades.

¡El problema también empeora a medida que agregamos más ejemplos de entrenamiento!

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

Tal como está, no se puede confiar en NgramModel, al menos no con el suavizado aditivo de Lidstone.

@afourney : creo que esto es intencionado (LidstoneProbDist tiene el atributo SUM_TO_ONE = False )

@afourney Estoy de acuerdo en que NgramModel realmente no se puede usar hasta que esto se solucione. Desafortunadamente, no he tenido tiempo de intentar esto recientemente.

@kmike SUM_TO_ONE es False para LidstoneProbDist porque si encuentra un evento que no estaba en la distribución inicial y no estableció el valor de bins como el número de eventos posibles, entonces no sumará uno. Pero si se usa correctamente, de hecho sumará uno. El problema aquí es el cálculo beta de NgramModel, no LidstoneProbDist en sí.

@kmike : Sí, noté que SUM_TO_ONE era falso. Mi preocupación era que el modelo devolvía probabilidades condicionales individuales (para eventos únicos) que ya eran mayores que 1, antes de incorporarlas a la suma.

@bcroy Creo que su solución es el enfoque correcto. En pocas palabras, _alpha realiza dos tareas importantes:

  1. Vuelve a normalizar el modelo de retroceso para el contexto dado a fin de excluir las palabras que ya se tienen en cuenta en el modelo actual de orden superior.
  2. Escala el modelo de retroceso renormalizado para "ajustar" la probabilidad "faltante"/descontada del modelo actual.

Dicho esto, sería bueno que NgramModel también ofreciera una estrategia de interpolación como alternativa a la estrategia de retroceso. Esto habilitaría el soporte para el suavizado de Jelinek-Mercer o Witten-Bell, el último de los cuales encontré que es simple y funciona bastante bien. Ver: http://nlp.stanford.edu/~wcmac/papers/20050421-smoothing-tutorial.pdf

¿Alguien puede confirmar que esto sigue siendo un error abierto?

Sí, sigo obteniendo P(foo | bar, baz) = 2,625

Hola a todos,

¿Hay algún progreso con este problema? ¿Sigue siendo un error abierto? Obtengo P(foo | bar, baz) = 2.625, por lo que el problema continúa.

Creo que este es un problema importante y debería haberse solucionado porque los modelos de lenguaje se usan para casi todas las aplicaciones en NLP.

Desafortunadamente, no he tenido tiempo de analizar los numerosos problemas con NgramModel y no creo que pueda hacerlo pronto. Hasta que alguien aborde estos errores, NgramModel se ha eliminado de nltk.

Dan, gracias por la respuesta.

Solo comprobando si hay una actualización. Puedo ver que se han cerrado algunos problemas, pero solo quiero asegurarme de que todavía está lejos de ser utilizable.

@ZeerakW Desafortunadamente, ha habido pocos avances en los modelos ngram y nadie se ha comprometido a abordar esto todavía.

Entrando al 2016 y el tema del 'modelo ngram' no ha tenido avances.

Amigos, finalmente podemos cerrar esto :)

Actualización 2018. Ya me gradué y comencé a trabajar y aún existe el problema de Ngram

¡Verano!

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

stevenbird picture stevenbird  ·  4Comentarios

jeryini picture jeryini  ·  5Comentarios

talbaumel picture talbaumel  ·  4Comentarios

alvations picture alvations  ·  4Comentarios

libingnan54321 picture libingnan54321  ·  3Comentarios