自从去年发布了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加速循环。
假设我们有一个包含许多矩形的大集合,存储为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允许我们直接操作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 =
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代码,可以使用以下步骤:
pip install cython%load_ext Cython%%cython魔法命令编写Cython代码。def、cdef和cpdefCython中有三种函数类型:
- Python函数:由def定义,输入和输出都是Python对象。
- Cython函数:由cdef定义,输入和输出可以是Python对象或C/C++对象。
- 混合函数:由cpdef定义,既可以作为Python函数调用,也可以作为Cython函数调用。
此外,cdef还可以用于定义C/C++对象的类型。
spaCy通过将所有Unicode字符串转换为64位哈希值,实现了高效的字符串处理。这使得Cython可以更快速地处理文本数据。
假设我们有一个文本数据集需要处理。我们可以使用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 =
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的数据结构显著提高了处理速度。
希望这些技巧对你有所帮助!