如何分批训练 NLTK PunktSentenceTokenizer?

How to train NLTK PunktSentenceTokenizer batchwise?

我正在尝试将财务文件拆分成句子。我有大约 50.000 个包含纯英文文本的文档。文件总大小约为 2.6 GB。

我正在使用 NLTK 的 PunktSentenceTokenizer 和标准的英语 pickle 文件。我还通过提供额外的缩写对其进行了调整,但结果仍然不够准确。

由于 NLTK PunktSentenceTokenizer 基于 Kiss & Strunk (2006) 的无监督算法,我正在尝试根据我的文档训练句子分词器,基于 training data format for nltk punkt

import nltk.tokenize.punkt
import pickle
import codecs

tokenizer = nltk.tokenize.punkt.PunktSentenceTokenizer()
text = codecs.open("someplain.txt", "r", "utf8").read()
tokenizer.train(text)
out = open("someplain.pk", "wb")
pickle.dump(tokenizer, out)
out.close()

不幸的是,当运行代码出现错误时,内存不足。 (主要是因为我先把所有的文件拼成了一个大文件。)

现在我的问题是:

  1. 我如何分批训练算法,这会降低内存消耗吗?
  2. 我可以使用标准的英语 pickle 文件并使用已经训练好的对象进行进一步训练吗?

我在 Core I7 2600K 和 16GB RAM 机器上的 Windows 10 上使用 Python 3.6 (Anaconda 5.2)。

source code所述:

Punkt Sentence Tokenizer

This tokenizer divides a text into a list of sentences by using an unsupervised algorithm to build a model for abbreviation words, collocations, and words that start sentences. It must be trained on a large collection of plaintext in the target language before it can be used.

不太清楚大集合的真正含义。在 paper 中,没有给出有关学习曲线的信息(当足以停止学习过程时,因为看到了足够的数据)。那里提到了《华尔街日报》语料库(大约有 3000 万个单词)。因此,您是否可以简单地 trim 您的训练语料库并减少内存占用是非常不清楚的。

还有一个关于您的主题的 open issue 说了一些关于 200 GB RAM 和更多的东西。如您所见,NLTK 可能没有很好地实现 Kiss & Strunk (2006) 提出的算法。

我看不出如何对其进行批处理,正如您在 train()-方法(NLTK 3.3 版)的函数签名中所见:

def train(self, train_text, verbose=False):
    """
    Derives parameters from a given training text, or uses the parameters
    given. Repeated calls to this method destroy previous parameters. For
    incremental training, instantiate a separate PunktTrainer instance.
    """

但可能还有更多问题,例如如果将给定版本 3.3 的签名与带 git 标记的版本 3.3 进行比较,there 是一个新参数 finalize,它可能会有所帮助并指示可能的批处理或可能的合并一个已经训练好的模型:

def train(self, text, verbose=False, finalize=True):
    """
    Collects training data from a given text. If finalize is True, it
    will determine all the parameters for sentence boundary detection. If
    not, this will be delayed until get_params() or finalize_training() is
    called. If verbose is True, abbreviations found will be listed.
    """

无论如何,如果您想进行超出 playground 级别的句子标记化,我强烈建议您不要使用 NLTK 的 Punkt Sentence Tokenizer。不过,如果你想坚持使用那个分词器,我会简单地建议你也使用给定的模型,而不是训练新模型,除非你有一个具有巨大 RAM 内存的服务器。

我自己在 运行 解决这个问题后发现了这个问题。我想出了如何分批训练分词器的方法,并将这个答案留给其他想要这样做的人。我能够在大约 12 小时内在大约 200GB 的生物医学文本内容上训练 PunktSentenceTokenizer,一次的内存占用不超过 20GB。尽管如此,在大多数情况下,我还是想赞成@colidyre 的建议,在 PunktSentenceTokenizer 上更喜欢其他工具。

您可以使用 class PunktTrainer 以分批方式训练 PunktSentenceTokenizer

from nltk.tokenize.punkt import PunktSentenceTokenizer, PunktTrainer

假设我们有一个生成训练文本流的生成器

texts = text_stream()

在我的例子中,生成器的每次迭代一次查询数据库中的 100,000 条文本,然后将所有这些文本连接在一起。

我们可以实例化一个PunktTrainer然后开始训练

trainer = PunktTrainer()
for text in texts:
    trainer.train(text)
    trainer.freq_threshold()

注意在处理每个文本后对 freq_threshold 方法的调用。这通过清理不太可能影响未来训练的稀有标记的信息来减少内存占用。

完成后,调用定型训练方法。然后您可以使用训练期间找到的参数实例化一个新的分词器。

trainer.finalize_training()
tokenizer = PunktSentenceTokenizer(trainer.get_params())

@colidyre 建议使用带有缩写的 spaCy。但是,可能很难提前知道哪些缩写会出现在您的文本域中。为了两全其美,您可以添加 Punkt 找到的缩写。您可以通过以下方式获取一组这些缩写

params = trainer.get_params()
abbreviations = params.abbrev_types