1

我正在学习 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-gram 的方式。那么有没有一种方法来格式化/准备数据,例如,三元组是这样生成的——>( . , </s>, <s> )这样模型会尝试再次开始另一个句子并输出更多的单词?

还是有另一种方法可以避免我上面写的问题?

4

2 回答 2

1

如果您查看用于拟合语言模型的代码,您会发现其核心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>'。坏消息:包含两个不同句子的 ngram 的唯一可能三元组是('</s>', '</s>', '<s>')('</s>', '<s>', '<s>')。所以 generate 现在应该生成多个句子,但是这些句子的内容仍然是完全独立的。

如果你想让上一句的内容影响下一句的内容,那就是事情开始变得复杂的地方。与其将语料库作为一系列句子传递给模型,不如将其作为一系列段落传递,每个段落包含多个句子:

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

那会起作用,但是现在并不意味着句子的开头和结尾,它们意味着段落的开头和结尾'<s>''</s>'并且生成的段落仍然是相互独立的。您还可以将其扩展为生成一系列段落或整本书,而不是段落。这取决于什么最适合您的任务。

于 2020-03-12T20:05:26.227 回答
0

问题是何时从语言模型生成,何时停止生成。

一个简单的生成习惯用法是:

从本教程片段中,可以通过以下方式实现代码:

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上的实现(注意,请参阅代码审查中的隐藏注释)

于 2020-02-19T08:20:34.957 回答