快 100 倍,Python 为自然言语处理加速度!

图灵汇官网

应用spaCy和Cython加速NLP

自从去年发布了Python的指代消解包后,许多用户利用它构建了许多应用程序,而这些应用程序与我们的对话应用有所不同。

我们发现,尽管该包在处理对话时速度很快,但在处理大型任务时却表现不佳。因此,我决定研究这个问题,从而产生了NeuralCoref v3.0(https://github.com/huggingface/neuralcoref/)。这个版本比前一版快100倍(每秒可分析数千个单词),同时保持了准确性、易用性,并且仍然位于Python库的生态系统中。

在本文中,我将分享一些在这个项目中学到的经验,特别是如何使用Python设计高速模块,以及如何利用spaCy的外部数据结构高效地设计高速的NLP函数。

尽管我们在讨论Python,但还需要借助Cython的力量。Cython是Python的一个超集,因此不必担心其复杂性。

何时需要加速

以下是几种可能需要加速的情况: - 使用Python为生产环境开发NLP模块; - 使用Python在大型NLP数据集上进行计算分析; - 为深度学习框架(如pyTorch或TensorFlow)预处理大型数据集; - 在深度学习的批处理加载器中处理复杂逻辑,导致训练速度变慢。

在我们开始之前,最后一件事:本文中的所有示例都已上传至Jupyter Notebook(https://github.com/huggingface/100-times-faster-nlp)。你可以尝试一下!

第一步:功能分析

要了解性能瓶颈,首先需要识别代码中的慢点。可以使用cProfile(https://docs.python.org/3/library/profile.html)进行分析:

```python import cProfile import pstats import myslowmodule

cProfile.run('myslowmodule.run()', 'restats') p = pstats.Stats('restats') p.sortstats('cumulative').printstats(30) ```

通常,慢点可能出现在循环中,尤其是涉及到Numpy数组操作的循环。接下来,我们将讨论如何使用Cython加速循环。

利用Cython加速循环

示例:矩形计数

假设我们有一个包含许多矩形的大集合,存储为Python对象(即Rectangle类的实例)的列表。模块的主要功能是遍历该列表,统计面积超过阈值的矩形数量。

原始的Python代码如下:

```python from random import random

class Rectangle: def init(self, w, h): self.w = w self.h = h

def area(self):
    return self.w * self.h

def checkrectangles(rectangles, threshold): nout = 0 for rectangle in rectangles: if rectangle.area() > threshold: nout += 1 return nout

def main(): nrectangles = 10000000 rectangles = [Rectangle(random(), random()) for _ in range(nrectangles)] nout = checkrectangles(rectangles, threshold=0.25) print(n_out) ```

瓶颈在于check_rectangles函数,因为它需要遍历大量Python对象。为了加速,我们可以使用Cython。

Cython的高速循环

Cython允许我们直接操作C/C++对象,从而显著提高速度。在这个例子中,我们将使用C结构来表示矩形,并使用C数组来存储它们。

以下是Cython版本的代码:

```python from cymem.cymem cimport Pool from random import random

cdef struct Rectangle: float w float h

cdef int checkrectangles(Rectangle* rectangles, int nrectangles, float threshold): cdef int nout = 0 for rectangle in rectangles[:nrectangles]: if rectangle.w * rectangle.h > threshold: nout += 1 return nout

def main(): cdef: int nrectangles = 10000000 float threshold = 0.25 Pool mem = Pool() Rectangle* rectangles = mem.alloc(nrectangles, sizeof(Rectangle))

for i in range(n_rectangles):
    rectangles[i].w = random()
    rectangles[i].h = random()

n_out = check_rectangles(rectangles, n_rectangles, threshold)
print(n_out)

```

这段代码通过使用C结构和C数组显著提高了速度。

在Jupyter中测试Cython代码

要在Jupyter中测试Cython代码,可以使用以下步骤:

  1. 安装Cython:pip install cython
  2. 在Jupyter中加载Cython扩展:%load_ext Cython
  3. 使用%%cython魔法命令编写Cython代码。

Cython的defcdefcpdef

Cython中有三种函数类型: - Python函数:由def定义,输入和输出都是Python对象。 - Cython函数:由cdef定义,输入和输出可以是Python对象或C/C++对象。 - 混合函数:由cpdef定义,既可以作为Python函数调用,也可以作为Cython函数调用。

此外,cdef还可以用于定义C/C++对象的类型。

使用spaCy和Cython加速NLP

spaCy的数据结构

spaCy通过将所有Unicode字符串转换为64位哈希值,实现了高效的字符串处理。这使得Cython可以更快速地处理文本数据。

示例:NLP处理

假设我们有一个文本数据集需要处理。我们可以使用spaCy和Cython来加速处理过程。

```python import urllib.request import spacy

with urllib.request.urlopen('https://raw.githubusercontent.com/pytorch/examples/master/wordlanguagemodel/data/wikitext-2/valid.txt') as response: text = response.read()

nlp = spacy.load('en') doc_list = [nlp(text[:800000].decode('utf8')) for _ in range(10)]

def slowloop(doclist, word, tag): nout = 0 for doc in doclist: for tok in doc: if tok.lower_ == word and tok.tag_ == tag: nout += 1 return nout

def mainnlpslow(doclist): nout = slowloop(doclist, 'run', 'NN') print(n_out)

def mainnlpfast(doclist): cdef int i, nout, ndocs = len(doclist) cdef Pool mem = Pool() cdef DocElement* docs = mem.alloc(n_docs, sizeof(DocElement))

for i, doc in enumerate(doc_list):
    docs[i].c = doc.c
    docs[i].length = doc.length

word_hash = doc.vocab.strings.add('run')
tag_hash = doc.vocab.strings.add('NN')

n_out = fast_loop(docs, n_docs, word_hash, tag_hash)
print(n_out)

```

这段代码通过使用Cython和spaCy的数据结构显著提高了处理速度。

相关资源

  • Cython入门教程:http://cython.readthedocs.io/en/latest/src/tutorial/index.html
  • spaCy的Cython页面:https://spacy.io/api/cython

希望这些技巧对你有所帮助!

本文来源: 图灵汇 文章作者: 邓丽靖