Python与Cython中的高效文本处理库
你被斯蒂芬·金的建议说服了,他认为副词不是你的朋友,所以你想高亮显示所有副词。我们将使用一个他认为特别糟糕的例子:代码语言:pythonAI代码解释这很简单——但问题是我们也高亮了“back”。虽然“back”无疑是副词,但我们可能不想高亮它。如果我们的目标是标记可疑的文体选择,我们需要完善我们的逻辑。事实证明,只有特定类型的副词才是我们感兴趣的。根据我们想要标记的确切词语,我们有很多方法可以做到
你被斯蒂芬·金的建议说服了,他认为副词不是你的朋友,所以你想高亮显示所有副词。我们将使用一个他认为特别糟糕的例子:
代码语言:python
AI代码解释
>>> import spacy.en
>>> from spacy.parts_of_speech import ADV
>>> # 加载流水线,并用一些文本调用它。
>>> nlp = spacy.en.English()
>>> tokens = nlp(u"‘Give it back,’ he pleaded abjectly, ‘it’s mine.’", tag=True, parse=False)
>>> print u''.join(tok.string.upper() if tok.pos == ADV else tok.string for tok in tokens)
u‘Give it BACK,’ he pleaded ABJECTLY, ‘it’s mine.’
这很简单——但问题是我们也高亮了“back”。虽然“back”无疑是副词,但我们可能不想高亮它。如果我们的目标是标记可疑的文体选择,我们需要完善我们的逻辑。事实证明,只有特定类型的副词才是我们感兴趣的。
根据我们想要标记的确切词语,我们有很多方法可以做到这一点。排除像“back”和“not”这类副词的最简单方法是根据词频:这些词比典型的、文体指南所担心的方式副词要常见得多。
Lexeme.prob 和 Token.prob 属性给出了该词的对数概率估计:
代码语言:python
AI代码解释
>>> nlp.vocab[u'back'].prob
-7.403977394104004
>>> nlp.vocab[u'not'].prob
-5.407193660736084
>>> nlp.vocab[u'quietly'].prob
-11.07155704498291
(概率估计基于一个 30 亿词语料库的计数,并使用 Simple Good-Turing 方法平滑。)
因此,我们可以轻松地从我们的副词标记器中排除英语中最常见的 N 个词。现在让我们试试 N=1000:
代码语言:python
AI代码解释
>>> import spacy.en
>>> from spacy.parts_of_speech import ADV
>>> nlp = spacy.en.English()
>>> # 查找第 N 个最常见词的对数概率
>>> probs = [lex.prob for lex in nlp.vocab]
>>> probs.sort()
>>> is_adverb = lambda tok: tok.pos == ADV and tok.prob < probs[-1000]
>>> tokens = nlp(u"‘Give it back,’ he pleaded abjectly, ‘it’s mine.”)
>>> print u''.join(tok.string.upper() if is_adverb(tok) else tok.string for tok in tokens)
‘Give it back,’ he pleaded ABJECTLY, ‘it’s mine.’
根据我们想要标记的确切词语,我们还有许多其他方法可以完善逻辑。假设我们只想标记修饰类似于“pleaded”的词的副词。这很容易做到,因为 spaCy 为每个词加载了一个向量空间表示(默认是 Levy 和 Goldberg (2014) 生成的向量)。自然地,该向量以 numpy 数组的形式提供:
代码语言:python
AI代码解释
>>> pleaded = tokens[7]
>>> pleaded.vector.shape
(300,)
>>> pleaded.vector[:5]
array([ 0.04229792, 0.07459262, 0.00820188, -0.02181299, 0.07519238], dtype=float32)
我们想根据词汇表中单词与“pleaded”的相似度对它们进行排序。测量两个向量相似度的方法有很多。我们将使用余弦度量:
代码语言:python
AI代码解释
>>> from numpy import dot
>>> from numpy.linalg import norm
>>> cosine = lambda v1, v2: dot(v1, v2) / (norm(v1) * norm(v2))
>>> words = [w for w in nlp.vocab if w.has_vector]
>>> words.sort(key=lambda w: cosine(w.vector, pleaded.vector))
>>> words.reverse()
>>> print('1-20', ', '.join(w.orth_ for w in words[0:20]))
1-20 pleaded, pled, plead, confessed, interceded, pleads, testified, conspired, motioned, demurred, countersued, remonstrated, begged, apologised, consented, acquiesced, petitioned, quarreled, appealed, pleading
>>> print('50-60', ', '.join(w.orth_ for w in words[50:60]))
50-60 counselled, bragged, backtracked, caucused, refiled, dueled, mused, dissented, yearned, confesses
>>> print('100-110', ', '.join(w.orth_ for w in words[100:110]))
100-110 cabled, ducked, sentenced, perjured, absconded, bargained, overstayed, clerked, confided, sympathizes
>>> print('1000-1010', ', '.join(w.orth_ for w in words[1000:1010]))
1000-1010 scorned, baled, righted, requested, swindled, posited, firebombed, slimed, deferred, sagged
>>> print('50000-50010', ', '.join(w.orth_ for w in words[50000:50010]))
50000-50010, fb, ford, systems, puck, anglers, ik, tabloid, dirty, rims, artists
如你所见,这些向量提供的相似性模型非常出色——仅凭一个原型词,我们在 1000 个词处仍然能得到有意义的结果!唯一的问题是列表中确实包含两个词簇:一个与“pleaded”的法律含义相关,另一个则指更一般的意义。理清这些簇是当前的一个活跃研究领域。
一个简单的解决方法是取几个词向量的平均值,并将其作为我们的目标:
代码语言:python
AI代码解释
>>> say_verbs = ['pleaded', 'confessed', 'remonstrated', 'begged', 'bragged', 'confided', 'requested']
>>> say_vector = sum(nlp.vocab[verb].vector for verb in say_verbs) / len(say_verbs)
>>> words.sort(key=lambda w: cosine(w.vector * say_vector))
>>> words.reverse()
>>> print('1-20', ', '.join(w.orth_ for w in words[0:20]))
1-20 bragged, remonstrated, enquired, demurred, sighed, mused, intimated, retorted, entreated, motioned, ranted, confided, countersued, gestured, implored, interceded, muttered, marvelled, bickered, despaired
>>> print('50-60', ', '.join(w.orth_ for w in words[50:60]))
50-60 flaunted, quarrelled, ingratiated, vouched, agonized, apologised, lunched, joked, chafed, schemed
>>> print('1000-1010', ', '.join(w.orth_ for w in words[1000:1010]))
1000-1010 hoarded, waded, ensnared, clamoring, abided, deploring, shriveled, endeared, rethought, berate
这些词看起来确实像是金可能会因为作家为其添加副词而加以训斥的。回想一下,我们最初的副词高亮函数是这样的:
代码语言:python
AI代码解释
>>> import spacy.en
>>> from spacy.parts_of_speech import ADV
>>> # 加载流水线,并用一些文本调用它。
>>> nlp = spacy.en.English()
>>> tokens = nlp("‘Give it back,’ he pleaded abjectly, ‘it’s mine.’", tag=True, parse=False)
>>> print(''.join(tok.string.upper() if tok.pos == ADV else tok.string for tok in tokens))
‘Give it BACK,’ he pleaded ABJECTLY, ‘it’s mine.’
我们想完善逻辑,以便只高亮修饰像“pleaded”这样富有表现力的沟通动词的副词。我们现在已经构建了一个代表这类词的向量,因此现在我们可以基于微妙的逻辑来高亮副词,根据我们的初始假设,聚焦于那些看起来在文体上最有问题的副词:
代码语言:python
AI代码解释
>>> import numpy
>>> from numpy import dot
>>> from numpy.linalg import norm
>>> import spacy.en
>>> from spacy.parts_of_speech import ADV, VERB
>>> cosine = lambda v1, v2: dot(v1, v2) / (norm(v1) * norm(v2))
>>> def is_bad_adverb(token, target_verb, tol):
... if token.pos != ADV:
... return False
... elif token.head.pos != VERB:
... return False
... elif cosine(token.head.vector, target_verb) < tol:
... return False
... else:
... return True
这个例子有些刻意——而且说实话,我从来没有真正接受过副词是严重的文体罪过这个观点。但希望它能传达一个信息:最先进的 NLP 技术非常强大。spaCy 让你能够轻松高效地使用它们,从而构建各种以前不可能实现的有用产品和功能。
独立评估
某机构和某机构的独立评估,将在 ACL 2015 上发表。数值越高越好。
准确度是未标记弧正确的百分比,速度是每秒处理的词元数。
|
系统 |
语言 |
准确度 |
速度 |
|---|---|---|---|
|
spaCy v0.86 |
Cython |
91.9 |
13,963 |
|
ClearNLP |
Java |
91.7 |
10,271 |
|
spaCy v0.84 |
Cython |
90.9 |
13,963 |
|
CoreNLP |
Java |
89.6 |
8,602 |
|
MATE |
Java |
92.5 |
550 |
|
Turbo |
C++ |
92.4 |
349 |
|
Yara |
Java |
92.3 |
340 |
某机构的多位研究者和某机构的一位教授对现有最佳解析器进行了详细比较。以上所有数字均取自他们慷慨提供给我的预印本,除了 spaCy v0.86 的数据。
我特别感谢作者们对结果的讨论,这促成了 v0.84 到 v0.86 之间准确度的提升。来自 Jin-ho(ClearNLP 的开发者)的一个建议尤其有用。
详细速度对比
每文档处理时间。越低越好。
设置:10 万份纯文本文档从 SQLite3 数据库流式传输,并用 NLP 库处理到三个细节级别之一——分词、标注或解析。这些任务是累加的:要解析文本,你必须先对其进行分词和标注。预处理时间未从总时间中减去——我报告的是流水线完成所需的时间。我报告的是每文档的平均时间,单位为毫秒。
硬件:Intel i7-3770 (2012)
绝对(每文档毫秒)
|
系统 |
分词 |
标注 |
解析 |
|---|---|---|---|
|
spaCy |
0.2ms |
1ms |
19ms |
|
CoreNLP |
2ms |
10ms |
49ms |
|
ZPar |
1ms |
8ms |
850ms |
|
NLTK |
4ms |
443ms |
n/a |
相对(相对于 spaCy)
|
系统 |
分词 |
标注 |
解析 |
|---|---|---|---|
|
spaCy |
1x |
1x |
1x |
|
CoreNLP |
10x |
10x |
2.6x |
|
ZPar |
5x |
8x |
44.7x |
|
NLTK |
20x |
443x |
n/a |
效率是 NLP 应用的一个主要关注点。经常听到人们说他们无法承担更详细的处理,因为他们的数据集太大了。这是一个糟糕的处境。如果你不能应用详细处理,通常不得不拼凑各种启发式方法。这通常需要多次迭代,而且你想出来的东西通常是脆弱的,难以推理。spaCy 的解析器比大多数标注器都快,其分词器对于任何工作负载来说都足够快。而且分词器不仅仅给你一个字符串列表。spaCy 的词元是一个指向 Lexeme 结构体的指针,从中你可以访问一系列预先计算的特征,包括嵌入的词表示。
更多推荐


所有评论(0)