Cython与Spacy合用加速NLP项目
本次报告主要内容参考这篇博客,重点在spacy与自定义数据结构的分析
主要内容
- 如何用 Python 设计一个高速模块
- 如何利用 spaCy 的内部数据结构来有效地设计超高速 NLP 函数。
何时需要加速NLP项目
- 你正在使用 Python 开发一个 NLP 的生产模块
- 你正在使用 Python 计算分析大型 NLP 数据集
- 你正在为深度学习框架,如 PyTorch / TensorFlow,预处理大型训练集,或者你的深度学习批处理加载器中的处理逻辑过于繁重,这会降低训练速度
预热代码
假设我们有一大堆矩形,并将它们存储进一个 Python 对象列表,例如 Rectangle 类的实例。我们的模块的主要工作是迭代这个列表,以便计算有多少矩形的面积大于特定的阈值。
1 | from random import random |
check_rectangles_py 函数是瓶颈部分!它对大量的 Python 对象进行循环,这可能会很慢,因为 Python 解释器在每次迭代时都会做大量工作(寻找类中的求面积方法、打包和解包参数、调用 Python API
接下来用Cython进行改写
- Cython 语言是 Python 的超集,它包含两种对象:
- Python 对象是我们在常规 Python 中操作的对象,如数字、字符串、列表、类实例
- Cython C 对象是 C 或 C ++ 对象,比如 double、int、float、struct、vectors。这些可以由 Cython 在超快速的底层代码中编译
快速循环只是 Cython 程序(只能访问 Cython C 对象)中的一个循环
设计这样一个循环的直接方法是定义 C 结构,它将包含我们在计算过程中需要的所有要素:在我们的例子中,就是矩形的长度和宽度
然后,我们可以将矩形列表存储在这种结构的 C 数组中,并将这个数组传递给我们的 check_rectangle_cy 函数。此函数现在接受一个 C 数组作为输入,因此通过 cdef 关键字而不是 def 将其定义为 Cython 函数
Python 模块的快速 Cython 版
1 | %%cython |
我们在这里使用了原生 C 指针数组,但你也可以选择其他选项,特别是 C ++ 结构,如向量、对、队列等。在这个片段中,我还使用了 cymem 的便利的 Pool()内存管理对象,以避免必须手动释放分配的 C 数组。当 Pool 由 Python 当做垃圾回收时,它会自动释放我们使用它分配的内存。
以上代码事例主要是简单分析了如何自定义数据结构来改写一般的python代码,以实现加速功能,但还没有很好的涉及到NLP中大量的字符串操作
使用 Cython 与 spaCy 来加速 NLP
官方的 Cython 文档甚至建议不要使用 C 字符串.
一般来说,除非你知道自己在做什么,否则应尽可能避免使用 C 字符串,而应使用 Python 字符串对象。
那么我们如何在使用字符串时在 Cython 中设计快速循环?
spaCy 会帮我们.
spaCy 解决这个问题的方式非常聪明。
将所有字符串转换为 64 位哈希码
spaCy 中的所有 unicode 字符串(token 的文本、其小写文本、引理形式、POS 键标签、解析树依赖关系标签、命名实体标签…)都存储在叫 StringStore 的单数据结构中,它们在里面由 64 位散列索引,即 C uint64_t
StringStore 对象实现了 Python unicode 字符串和 64 位哈希码之间的查找表。
它可以通过 spaCy 任意处及任意对象访问(请参阅上图),例如 nlp.vocab.strings、doc.vocab.strings 或 span.doc.vocab.string
当某个模块需要对某些 token 执行快速处理时,仅使用 C 级别的 64 位哈希码而不是字符串。调用 StringStore 查找表将返回与哈希码相关联的 Python unicode 字符串
但是,spaCy 做的远不止这些,它使我们能够访问文档和词汇表的完全覆盖的 C 结构,我们可以在 Cython 循环中使用这些结构,而不必自定义结构
spaCy 的内部数据结构
与 spaCy Doc 对象关联的主要数据结构是 Doc 对象,该对象拥有已处理字符串的 token 序列(「单词」)以及 C 对象中的所有称为 doc.c 的标注,它是一个 TokenC 结构数组。
TokenC 结构包含我们需要的关于每个 token 的所有信息。这些信息以 64 位哈希码的形式存储,可以重新关联到 unicode 字符串,就像我们刚刚看到的那样,要深入了解这些 C 结构中的内容,只需查看SpaCy 的 Cython API doc
使用 spaCy 和 Cython 进行快速 NLP 处理
假设我们有一个需要分析的文本数据集
1 | import urllib.request |
该脚本生成用于 spaCy 解析的 10 份文档的列表,每个文档大约 170k 字。我们也可以生成每个文档 10 个单词的 170k 份文档(比如对话数据集)
我们想要在这个数据集上执行一些 NLP 任务。例如,我们想要统计数据集中单词run作为名词的次数(即用 spaCy 标记为NN词性)
一个简单明了的 Python 循环就可以做到
1 | def slow_loop(doc_list, word, tag): |
Cython进行改写
1 | %%cython -+ |
代码有点长,因为我们必须在调用 Cython 函数之前在 main_nlp_fast 中声明并填充 C 结构。(如果你在代码中多次使用低级结构,使用 C 结构包装的 Cython 扩展类型来设计我们的 Python 代码是比每次填充 C 结构更优雅的选择。这就是大多数 spaCy 的结构,它是一种结合了快速,低内存以及与外部 Python 库和函数接口的简便性的非常优雅的方法。
对上述代码的一个解释
主要是以下6类,详细介绍还是看官方文档吧
- Doc
- Token
- Span
- Lexeme
- Vocab
- StringStore
1 | import spacy |
结束