NLTK MLE уточняющие триграммы и более - PullRequest
1 голос
/ 19 февраля 2020

Я изучаю 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> ), чтобы модель попыталась снова начать другое предложение и вывести больше слов?

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

Ответы [ 2 ]

0 голосов
/ 12 марта 2020

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

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

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

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

Из строки документов для 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>'). Таким образом, генерирование должно теперь генерировать несколько предложений, но содержание этих предложений будет по-прежнему полностью независимым.

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

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

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

0 голосов
/ 19 февраля 2020

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

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

image

From this фрагмент учебника , в коде, который может быть достигнут с помощью:

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 (примечание см. в скрытом комментарии в обзоре кода)

...