Nltk: Erro no cálculo de suavização de backoff do NgramModel?

Criado em 7 mar. 2013  ·  18Comentários  ·  Fonte: nltk/nltk

Acredito que haja um erro em como a suavização de recuo é calculada no NgramModel.

  • Considere a sequência de "palavras": aaaababaacccbacb com palavras ['a','b','c']
  • Construa um modelo bigrama (n=2). Para simplificar, use a suavização LidstoneProbDist
  • Notavelmente, esta sequência não contém todos os bigramas prefixados por 'b' ou 'c'. Assim, a retirada é necessária para obter a probabilidade de bigrama 'bb', 'bc' e 'ca'
  • Para context = ['a'], model.prob(w,context) parece bom para todas as palavras e somas para 1
  • Para context = ['b'], model.prob(w,context) não parece certo, soma > 1

Eu pensei que o cálculo de backoff deveria fazer o seguinte, digamos, para o contexto 'b':

  • Calcule a probabilidade total de "ausência" para valores não vistos no contexto 'b' (ou seja, bb e bc), no nível do bigrama, chame isso de Beta2
  • Calcule a probabilidade total de unigrama para esses valores não vistos (ou seja, 'b' e 'c'), chame isso de Beta1
  • return (Beta2 / Beta1) * backoff.prob()

Isso essencialmente troca as probabilidades do unigrama por aquelas palavras que não foram observadas no contexto do bigrama, dimensionadas adequadamente, para preencher a massa de probabilidade ausente.

Estou esquecendo de algo? O código em NgramModel faz algo bem diferente, parece, e eu não consegui entender.

language-model

Comentários muito úteis

Entrando em 2016 e a questão do 'modelo ngram' não teve nenhum avanço.

Todos 18 comentários

Eu acredito que você está certo de que isso é realmente um bug.

Se assumirmos esta configuração:

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 rapidamente as discrepâncias:

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

Quando eu estava trabalhando no NgramModel há algum tempo, lembro-me também de achar um pouco confuso a maneira como o back-off foi implementado. Agora que não vejo isso há muito tempo, perdi a compreensão intuitiva que tinha de como funcionava. Parece-me que afirmamos que estamos implementando o Katz Back-off, mas os cálculos são um pouco diferentes dos da Wikipedia .

Acredito que seja porque a função LidstoneProbDist.discount chamada de NgramModel._beta já leva em conta a soma, mas vou ter que analisar mais.

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)

Parece-me que os cálculos beta são onde as coisas estão dando errado, porque beta no nível do bigrama é muito maior que o beta no nível do unigrama, o que torna a proporção, alfa, positiva.

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

Eu também descartei que é o próprio LidstoneProbDist que tem um 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

Vou tentar descobrir como todas essas partes estão interconectadas novamente e ver se consigo consertar isso. Embora, se mais alguém quiser entrar (como @desilinguist), eu apreciaria outro par de olhos nisso.

Olá, e obrigado por verificar isso. Só mais alguns pensamentos:

Primeiro, uma coisa que confunde são as diferentes noções de "desconto". Existe o desconto obtido pelos vários métodos de suavização. Por exemplo, a suavização Laplaciana simples (adicione um) desconta a probabilidade de palavras observadas e desloca essa massa para as palavras não observadas. A função discount() sendo chamada na função _beta é para a suavização feita pelo ProbDist, e não (acho) relevante para a suavização de backoff. Eu acho que a noção de desconto de desconto tem a ver com a probabilidade do subconjunto de palavras que estão "faltando" (não observadas) para diferentes contextos no modelo de ordem superior.

Então, modifiquei o código para meus próprios propósitos para fazer o que acho que é a coisa certa e compartilhei alguns trechos abaixo. Basicamente, eu identifico o subconjunto de palavras que estão faltando no modelo para um determinado contexto, e para esse subconjunto calculo a probabilidade total dessas palavras "faltantes" e a quantidade correspondente no modelo de backoff. A proporção é "alfa", e observe que isso é uma função do contexto. Acho que essa implementação corresponde ao que está no link da Wikipedia que você fornece. Além disso, a função _beta não é mais usada no meu caso.

Espero que isso seja útil para a discussão. Obrigado novamente.

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

Olá a todos, eu só queria entrar nessa discussão e salientar que o problema é muito pior do que simplesmente ter as probabilidades de não somar 1,0

Considere o seguinte exemplo 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

Sim -- esta probabilidade condicional é > 1,0

A parte desagradável é que quanto mais os modelos recuam, mais as probabilidades se tornam infladas.

O problema também se torna pior à medida que adicionamos mais exemplos de treinamento!

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á, o NgramModel não pode ser confiável - pelo menos não com a suavização aditiva do Lidstone.

@afourney : Acredito que seja intencional (LidstoneProbDist tem o atributo SUM_TO_ONE = False )

@afourney Concordo que o NgramModel não pode realmente ser usado até que isso seja corrigido. Infelizmente, eu simplesmente não tive tempo para dar uma facada nisso recentemente.

@kmike SUM_TO_ONE é False para LidstoneProbDist porque se você encontrar um evento que não estava na distribuição inicial e você não definiu o valor de bins para ser o número de eventos possíveis, então ele não será somado a um. Mas, se usado corretamente, de fato somará um. O problema aqui é o cálculo beta do NgramModel, não o próprio LidstoneProbDist.

@kmike : Sim, notei que SUM_TO_ONE era falso. Minha preocupação era que o modelo estava retornando probabilidades condicionais individuais (para eventos únicos) que já eram maiores que 1 - antes de incorporá-las à soma.

@bcroy Acho que sua solução é a abordagem certa. Simplificando, _alpha executa duas tarefas importantes:

  1. Ele renormaliza o modelo de backoff para o contexto fornecido para excluir palavras que já são contabilizadas pelo modelo de ordem superior atual.
  2. Ele dimensiona o modelo de backoff renormalizado para "ajustar" a probabilidade "ausente"/descontada do _model atual.

Dito isto, seria bom se o NgramModel também oferecesse uma estratégia de interpolação como alternativa à estratégia de backoff. Isso permitiria o suporte para suavização de Jelinek-Mercer ou Witten-Bell - o último dos quais achei simples e funciona muito bem. Veja: http://nlp.stanford.edu/~wcmac/papers/20050421-smoothing-tutorial.pdf

Alguém pode confirmar que este ainda é um bug aberto?

Sim, ainda estou recebendo P (foo | bar, baz) = 2,625

Oi pessoal,

Há algum progresso com esse problema? Ainda é um bug aberto? Estou obtendo P (foo | bar, baz) = 2,625, então o problema continua.

Acho que este é um problema importante e deveria ter sido corrigido porque os modelos de linguagem são usados ​​para quase todos os aplicativos em PNL.

Infelizmente, não tive tempo de analisar os inúmeros problemas com NgramModel , e não me prevejo poder fazê-lo tão cedo. Até que alguém resolva esses bugs, NgramModel foi removido do nltk.

Dan, obrigado pela resposta.

Apenas verificando para uma atualização. Vejo que alguns problemas foram encerrados, mas só quero ter certeza de que ainda está longe de ser utilizável?

@ZeerakW Infelizmente, houve pouco progresso nos modelos ngram, e ninguém se comprometeu a resolver isso ainda.

Entrando em 2016 e a questão do 'modelo ngram' não teve nenhum avanço.

Pessoal, finalmente podemos fechar isso :)

Atualização 2018. Já me formei e comecei a trabalhar, mas ainda existe o problema do Ngram

Verão!

Esta página foi útil?
0 / 5 - 0 avaliações