Триграммы уточнения модели NLTK MLE и выше

Я изучаю NLTK и у меня есть вопрос о предварительной обработке данных и модели MLE. В настоящее время я пытаюсь генерировать слова с помощью модели MLE. Проблема в том, что когда я выбираю n>=3. Моя модель будет создавать слова совершенно нормально, пока не дойдет до точки ('.'). После этого он будет выводить только отступы в конце предложения.

Это, по сути, то, что я делаю.


tokenized_text = [list(map(str.lower, word_tokenize(sent))) 
                  for sent in sent_tokenize(MYTEXTINPUT)]

n = 3
train_data, padded_sents = padded_everygram_pipeline(n, tokenized_text)
model = MLE(n)
model.fit(train_data, padded_sents)
model.generate(20)

# OUTPUT: 
eg:  
blah beep bloop . </s> </s> </s> </s> </s> </s> </s> </s> (continues till 20 words reached)

Я подозреваю, что ответ на мою проблему заключается в том, как мои n-граммы подготовлены для модели. Итак, есть ли способ отформатировать/подготовить данные, чтобы, например, триграммы генерировались так --> ( . , </s>, <s> ), чтобы модель снова пыталась начать другое предложение и выводить больше слов?

Или есть другой способ избежать моей проблемы, написанной выше?


person arm.u    schedule 19.02.2020    source источник
comment
Ах да, проблема поколения. Хороший вопрос!! знак равно   -  person alvas    schedule 19.02.2020


Ответы (2)


Вопрос в том, когда генерировать из языковой модели, когда прекращать генерацию.

Простая идиома для генерации была бы:

Из этого фрагмента руководства в коде, который можно достигается с помощью:

detokenize = TreebankWordDetokenizer().detokenize

def generate_sent(model, num_words, random_seed=42):
    """
    :param model: An ngram language model from `nltk.lm.model`.
    :param num_words: Max no. of words to generate.
    :param random_seed: Seed value for random.
    """
    content = []
    for token in model.generate(num_words, random_seed=random_seed):
        if token == '<s>':
            continue
        if token == '</s>':
            break
        content.append(token)
    return detokenize(content)

Но на самом деле аналогичная функция generate() уже есть в NLTK из https://github.com/nltk/nltk/blob/develop/nltk/lm/api.py#L182

def generate(self, num_words=1, text_seed=None, random_seed=None):
    """Generate words from the model.
    :param int num_words: How many words to generate. By default 1.
    :param text_seed: Generation can be conditioned on preceding context.
    :param random_seed: A random seed or an instance of `random.Random`. If provided,
    makes the random sampling part of generation reproducible.
    :return: One (str) word or a list of words generated from model.
    Examples:
    >>> from nltk.lm import MLE
    >>> lm = MLE(2)
    >>> lm.fit([[("a", "b"), ("b", "c")]], vocabulary_text=['a', 'b', 'c'])
    >>> lm.fit([[("a",), ("b",), ("c",)]])
    >>> lm.generate(random_seed=3)
    'a'
    >>> lm.generate(text_seed=['a'])
    'b'
    """
    text_seed = [] if text_seed is None else list(text_seed)
    random_generator = _random_generator(random_seed)
    # This is the base recursion case.
    if num_words == 1:
        context = (
            text_seed[-self.order + 1 :]
            if len(text_seed) >= self.order
            else text_seed
        )
        samples = self.context_counts(self.vocab.lookup(context))
        while context and not samples:
            context = context[1:] if len(context) > 1 else []
            samples = self.context_counts(self.vocab.lookup(context))
        # Sorting samples achieves two things:
        # - reproducible randomness when sampling
        # - turns Mapping into Sequence which `_weighted_choice` expects
        samples = sorted(samples)
        return _weighted_choice(
            samples,
            tuple(self.score(w, context) for w in samples),
            random_generator,
        )
    # We build up text one word at a time using the preceding context.
    generated = []
    for _ in range(num_words):
        generated.append(
            self.generate(
                num_words=1,
                text_seed=text_seed + generated,
                random_seed=random_generator,
            )
        )
    return generated

Подробнее о реализации на https://github.com/nltk/nltk/pull/2300 (обратите внимание, см. скрытые комментарии в обзоре кода)

person alvas    schedule 19.02.2020

Если вы посмотрите на код для подбора языковой модели вы можете видеть, что по своей сути fit() обновляет счетчики на основе документов в train_data:

self.counts.update(self.vocab.lookup(sent) for sent in text)

Однако обратите внимание, что он обновляет эти значения по одному предложению за раз. Каждое предложение полностью независимо друг от друга. Модель не знает, что было до этого предложения и что следует за ним. Кроме того, помните, что вы тренируете модель триграммы, поэтому последние два слова в каждом предложении — ('</s>', '</s>'). Таким образом, модель узнает, что за '</s>' следует '</s>' с очень высокой вероятностью, но она никогда не узнает, что за '</s>' иногда может следовать '<s>'.

Таким образом, самое простое решение вашей проблемы — просто вручную начинать новое предложение (т. е. снова вызывать generate()) каждый раз, когда вы видите '</s>'. Но допустим, вы не хотите этого делать и хотите, чтобы модель генерировала несколько предложений за один раз.

Из строки документации для padded_everygram_pipeline:

Creates two iterators:
- sentences padded and turned into sequences of `nltk.util.everygrams`
- sentences padded as above and chained together for a flat stream of words

Таким образом, в отличие от train_data, padded_sents содержит все ваши предложения как одну запись:

>>> tokenized_text= [['this', 'is', 'sentence', 'one'],
                     ['this', 'is', 'sentence', 'two']
                     ]
>>> train_data, padded_sents = padded_everygram_pipeline(n, tokenized_text)
>>> padded_sents = list(padded_sents) #we need to do this because padded_sents is a generator and can only be gone through once
>>> print(padded_sents)
['<s>', '<s>', 'this', 'is', 'sentence', 'one', '</s>', '</s>', '<s>', '<s>', 'this', 'is', 'sentence', 'two', '</s>', '</s>']
>>> model = MLE(n)
>>> model.fit(padded_sents, padded_sents) #notice that I'm not using train_data

Хорошие новости: теперь у нас есть пример '<s>' после '</s>'. Плохая новость: единственные возможные триграммы, содержащие энграммы для двух разных предложений, — это ('</s>', '</s>', '<s>') и ('</s>', '<s>', '<s>'). Таким образом, generate теперь должен генерировать несколько предложений, но содержание этих предложений по-прежнему будет полностью независимым.

Если вы хотите, чтобы содержание предыдущего предложения влияло на содержание следующего, здесь все становится сложнее. Вместо того, чтобы передавать корпус в модель в виде серии предложений, вы можете передать его в виде серии абзацев с несколькими предложениями в каждом:

tokenized_text = [['this', 'is', 'sentence', 'one', '.', 'this', 'is', 'sentence', 'two', '.'],
                  ['this', 'is', 'a', 'second', 'paragraph', '.']
                  ]

Это сработало бы, но теперь '<s>' и '</s>' означают не начало и конец предложения, а начало и конец абзаца. И сгенерированные абзацы по-прежнему будут независимы друг от друга. Вы также можете расширить это, чтобы вместо абзацев создавать серии абзацев или целые книги. Это зависит от того, что лучше всего подходит для вашей задачи.

person acattle    schedule 12.03.2020