nltk 更好的PunktTrainer

yuvru6vn  于 4个月前  发布在  其他
关注(0)|答案(7)|浏览(61)

在尝试使用PunktTokenizer重新训练一个句子分词器模型时,NLTK代码占用了超过200GB的RAM,并进行了大量交换操作,似乎在两天的训练后仍未结束。

import pickle
from nltk.tokenize.punkt import PunktSentenceTokenizer

tokenizer = PunktSentenceTokenizer()

with open('wikipedia-en.txt') as fin:
    text = fin.read()

tokenizer.train(text)

with open('wiki-en.pkl') as fout:
    pickle.dump(tokenizer, fout)

对于我们现在需要训练的大规模数据集来说,这段代码似乎效率不高。
让我们将此问题保留以跟踪与Punkt相关的其他问题,直到我们获得该算法的更好版本。

jdg4fx2g

jdg4fx2g1#

_pair_iter 替换为 bigrams
这看起来像是我们在 nltk.util.ngrams 中使用的 ngram 函数:https://github.com/nltk/nltk/blob/develop/nltk/tokenize/punkt.py#L306
我们不应该同时维护 _pair_iter(it)bigrams(it)

counter 替换为 bounter

基于 collections.Counter 的 FreqDist 对象具有较高的内存占用,我们应该用 bounter 替换它。
Punkt 代码中有几个 FreqDist 的使用示例,其中 self._collocation_fdist 应该从具有较低内存需求的 bounter 中受益最大,即:https://github.com/nltk/nltk/blob/develop/nltk/tokenize/punkt.py#L631

常量(魔术数字)应该是参数

PunktTrainer 中有几个示例使用常量值来设置算法的阈值/退避,它们没有暴露给用户,而是作为固定的常量设置。它们应该是可调参数。例如:https://github.com/nltk/nltk/blob/develop/nltk/tokenize/punkt.py#L671

监控和优化 for 循环

_train_tokens() 中的 for 循环通过多遍遍历数据集。
很可能无法避免多遍,但我们应该能够使用一些并行化来优化这些遍历,如果可能的话,不要精确计算计数,而是用一些基于字符串的算法近似计算计数(我们需要研究一下)。
有一些看起来彼此独立的 for 循环,也可以并行化。

清理 Py2 和 Py3 字符串兼容性代码

nltk.token.punkt.py 包含各种文本类型检查和兼容性检查,它们应该统一为使用原生的 nltk.compat 代码或 six:

from six import string_types
from nltk.compat import unicode_repr, python_2_unicode_compatible
8hhllhi2

8hhllhi22#

你忽略了一个事实,那就是PunktTrainer有一个功能来管理由于不常见元素而导致的内存使用。这是一个名为freq_threshold的方法,旨在在训练批次之间进行调用。是的,它应该被更清晰地记录下来。

我们已经用维基百科上的punkt模型训练了好几年,但当我第一次这样做时,它偶尔会使用freq_threshold来修剪无用的统计信息。

bounters应该会受益匪浅,但我认为你的其他建议更改主要是表面文章。像ABBREV这样的参数会在用户仅通过子类化就可以覆盖常量的情况下给接口添加噪声。

avwztpqn

avwztpqn3#

是的,我在阅读代码以了解发生了什么,同时列出可以改进的地方。但 bounter 替换是这里的主要目标=)
我也发现 Punkt 词元分词器很有用,也许将其暴露出来会更好,而不是将其隐藏在 PunktLanguageVars 中。
酷,我不知道还有个 freq_threshold 参数!仍在阅读代码。

ldioqlga

ldioqlga4#

我认为它之前被更多地曝光,并引起了困惑。

xggvc2p6

xggvc2p65#

它完成了我之前用其他正则表达式处理非英语语言时所完成的50%的清洗任务。我想我还需要查看代码历史记录。
有趣的是:#7460b91a71
不幸的是,从 FreqDist 转换到 bounter 并不容易解决。遇到的第一个问题是,FreqDist允许任何对象作为键,而bounter只允许unicode对象或字节缓冲区。

t2a7ltrp

t2a7ltrp6#

Collapsing multiply loops might help. In the current code, PunktTrainer is looping through to:

  1. Find the frequency of each case-normalized type
  • Add/Remove type-based abbreviations based on the word type's heuristics and thresholds](https://github.com/nltk/nltk/blob/develop/nltk/tokenize/punkt.py#L747)
  • requires the global counts from step 1.
  • Creates a first pass annotation to add info about whether a token is a (i) sentbreak , (ii) ellipsis , (iii) period_final
  • requires the updated abbreviation list from step 2
  • Get the orthography features
  • requires step 3 token.ellipsis information from step 3
  • but doesn't need global information so can be computed directly after step 3
  • Find the sentence breaks
  • requires tokens.sentbreak information from step 3
  • but doesn't need global information so can be computed directly after step 3 and 4
  • Iterate the pairs of token bigrams and update (i) token.type_no_period information, (ii) _sent_starter_fdist and (iii) _collocation_fdist distributions
ni65a41a

ni65a41a7#

你忽略了一个事实,PunktTrainer有一个功能可以管理由于不频繁元素导致的内存使用。这个方法叫做freq_threshold,它用于在批次训练之间调用。是的,它应该有更清晰的文档说明。

@alvations@jnothman 我如何使用freq_threshold和批量训练?我想要训练大约6万个TXT文件。
这是我目前的代码:

### TRAINING
text = ''
for file in glob.glob("*.txt"):
	f = io.open("./"+file,"r",encoding="utf8")
	text += f.read()
	f.close()

trainer = PunktTrainer()
trainer.INCLUDE_ALL_COLLOCS = True
trainer.INCLUDE_ABBREV_COLLOCS = True
trainer.train(text) 
tokenizer = PunktSentenceTokenizer(trainer.get_params())

这段代码看起来正确吗?我该如何知道要使用哪个freq_threshold?

text = ''
for file in glob.glob("*.txt"):
	f = io.open("./"+file,"r",encoding="utf8")
	text = f.read()
	trainer = PunktTrainer()
	trainer.INCLUDE_ALL_COLLOCS = True
	trainer.INCLUDE_ABBREV_COLLOCS = True
	trainer.freq_threshold()
        trainer.train(text, finalize=False)
	f.close()

tokenizer = PunktSentenceTokenizer(trainer.get_params())

相关问题