助力 Python 老司机都开火箭了!Cython 实现百倍加速 NLP (助力油和液压油一样吗)

文章编号:41195 资讯动态 2024-12-03 加速CythonPythonNLP

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

spacex 猎鹰重型发射器,版权归

在去年我们发布了用 Python 实现的 基于神经网络的相互引用解析包(Neural coreference resolution package) 之后,在社区中获得了惊人数量的反馈,许多人开始将该解析包用到各种各样的应用中,有一些应用场景甚至已经超出了我们原本设计的对话框用例(Dialog use-case)。

后来我们发现,虽然这个解析包对于对话框消息而言,解析速度完全够用,但如果要解析更大篇幅的文章就显得太慢了。

因此我决定要深入探索解决方案,并最终开发出了 NeuralCoref v3.0 。这个版本比之前(每秒解析几千字)的要快出百倍,同时还保证了相同的准确性,当然,它依然易于使用,也符合 Python 库的生态环境。

在本篇文章中,我想向大家分享我在开发 NeuralCoref v3.0 过程中学到的一些经验,尤其将涉及:

我的标题其实有点作弊,因为我实际上要谈论的是 Python,同时也要介绍一些 Cython 的特性。不过你知道吗?Cython 属于 Python 的超集,所以不要让它吓跑了!

以下给出了一些可能需要采用这种加速策略的场景:

百倍加速第一步:代码剖析

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

你需要知道的第一件事情是,你的大部分代码在纯 Python 环境下可能都运行良好,但是其中存在一些瓶颈函数(Bottlenecks functions),一旦你能给予它们更多的「关照」,你的程序将获得几个数量级的提速。

所以你应该从剖析自己的 Python 代码开始,找出那些低效模块。其中一种方法就是使用:

你很可能会发现低效的原因是因为一些循环控制,或者你使用神经网络时引入了过多的 Numpy 数组操作(我不会花费时间在这里介绍 Numpy,这个问题已经有 太多文章 进行了讨论)。

那么我们该如何来加速循环呢?

在 Pyhthon 中加入 Cython 加速循环计算

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

让我们通过一个简单的例子来解决这个问题。假设有一堆矩形,我们将它们存储成一个由 Python 对象(例如对象实例)构成的列表。我们的模块的主要功能是对该列表进行迭代运算,从而统计出有多少个矩形的面积是大于所设定阈值的。

我们的 Python 模块非常简单:

其中 check_rectangles 函数就是我们程序的瓶颈!它对一个很长的 Python 对象列表进行迭代,而这一过程会相当缓慢,因为 Python 解释器在每次迭代中都需要做很多工作(查找类中的方法、参数的打包和解包、调用 Python API 等等)。

Cython 语言是 Python 的一个超集,它包含有两种类型的对象:

定义这种循环最直接的一种方法就是,定义一个包含有计算过程中我们所需要用到的所有对象的结构体。具体而言,在本例中就是矩形的长度和宽度。

然后我们可以将矩形对象列表存储到 C 的结构数组中,再将数组传递给 check_rectangles 函数。这个函数现在将接收一个 C 数组作为输入,此外我们还使用关键字取代了(注意:也可以用于定义 Cython C 对象)将函数定义为一个 Cython 函数。

这里是 Cython 版本的模块程序:

这里我们使用了原生的 C 数组指针,不过你还有其它选择,特别是 C++ 中诸如向量、二元组、队列这样的结构体 。在这段程序中,我还使用了一个来自提供的内存管理对象,它可以避免手动释放所申请的 C 数组内存空间。当不再需要使用 Pool 中的对象时,它将自动释放该对象所占用的内存空间。

有很多办法来测试、编译和发布 Cython 代码。Cython 甚至可以像 Python 一样 直接用于 Jupyter Notebook 中。

通过 pip install cython 命令安装 Cython。

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

使用 %load_ext Cython 指令在 Jupyter notebook 中加载 Cython 扩展。

然后通过指令,我们就可以像 Python 一样在 Jupyter notebook 中使用 Cython。

如果在执行 Cython 代码的时候遇到了编译错误,请检查 Jupyter 终端的完整输出信息。

大多数情况下可能都是因为在之后遗漏了标签(比如当你使用 spaCy Cython 接口时)。如果编译器报出了关于 Numpy 的错误,那就是遗漏了 import numpy

正如我在一开始就提到的,请好好阅读 这份 Jupyter notebook 和这篇文章,它包含了我们在 Jupyter 中讨论到的所有示例。

Cython 代码的文件后缀是,这些文件将被 Cython 编译器编译成 C 或 C++ 文件,再进一步地被 C 编译器编译成字节码文件。最终 Python 解释器将能够调用这些字节码文件。

你也可以使用将一个 .pyx 文件直接加载到 Python 程序中:

你也可以将自己的 Cython 代码作为 Python 包构建,然后像正常的 Python 包一样将其导入或者发布,更多细节请参考 这里 。不过这种做法需要花费更多的时间,特别是你需要让 Cython 包能够在所有的平台上运行。如果你需要一个参考样例,不妨看看 spaCy 的安装脚本 。

在我们开始优化自然语言处理任务之前,还是先快速介绍一下、和这三个关键字。它们是你开始学会使用 Cython 之前需要掌握的最主要的知识。

你可以在 Cython 程序中使用三种类型的函数:

关键字的另一个用途就是,在代码中表明某一个对象是 Cython C/C++ 对象。所以除非你在代码中使用声明对象,否则这些对象都会被解释器当做 Python 对象(这会导致访问速度变慢)。

使用 Cython 和 spaCy 加速自然语言处理

这一切看起来都很好,但是......我们到现在都还没开始涉及优化自然语言处理任务!没有字符串操作,没有 unicode 编码,也没有我们在自然语言处理中所使用的妙招。

此外 Cython 的官方文档甚至 建议不要使用 C 语言类型的字符串:

那么当我们在操作字符串时,要如何在 Cython 中设计一个更加高效的循环呢?

spaCy 处理该问题的做法就非常地明智。

将所有的字符串转换为 64 位哈希码

spaCy 中所有的 unicode 字符串(一个标记的文本、它的小写形式文本、它的引理形式、POS 标记标签、解析树依赖标签、命名实体标签等等)都被存储在一个称为 StringStore 的数据结构中,它通过一个 64 位哈希码 进行索引,例如 C 类型的。

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

StringStore 对象实现了 Python unicode 字符串与 64 位哈希码之前的查找映射。

它可以从 spaCy 的任何地方和任意对象进行访问,例如 npl.vocab.strings doc.vocab.strings 或者 span.doc.vocab.string

当某一个模块需要在某些标记(tokens)上获得更快的处理速度时,你可以使用 C 语言类型的 64 位哈希码代替字符串来实现。调用 StringStore 查找表将返回与该哈希码相关联的 Python unicode 字符串。

但是 spaCy 能做的可不仅仅只有这些,它还允许我们访问文档和词汇表完全填充的 C 语言类型结构,我们可以在 Cython 循环中使用这些结构,而不必去构建自己的结构。

与 spaCy 文档有关的主要数据结构是 Doc 对象,该对象拥有经过处理的字符串的标记序列(“words”)以及 C 语言类型对象中的所有标注,称为,它是一个 TokenC 的结构数组。

TokenC 结构包含了我们需要的关于每个标记的所有信息。这种信息被存储成 64 位哈希码,它可以与我们刚刚所见到的 unicode 字符串进行重新关联。

如果想要准确地了解这些漂亮的 C 结构中的内容,可以查看新建的 spaCy 的 Cython API 文档 。

接下来看一个简单的自然语言处理的例子。

假设现在有一个文本文档的数据集需要分析

我写了一个脚本用于创建一个包含有 10 份文档的列表,每份文档都大概含有 17 万个单词,采用 spaCy 进行分析。当然我们也可以对 17 万份文档(每份文档包含 10 个单词)进行分析,但是这样做会导致创建的过程非常慢,所以我们还是选择了 10 份文档。

我们想要在这个数据集上展开某些自然语言处理任务。例如,我们可以统计数据集中单词「run」作为名词出现的次数(例如,被 spaCy 标记为「NN」词性标签)。

采用 Python 循环来实现上述分析过程非常简单和直观:

但是这个版本的代码运行起来非常慢!这段代码在我的笔记本上需要运行 1.4 秒才能获得答案。如果我们的数据集中包含有数以百万计的文档,为了获得答案,我们也许需要花费超过一天的时间。

我们也许能够采用多线程来实现加速,但是在 Python 中这种做法并不是那么明智 ,因为你还需要处理 全局解释器锁(GIL) 。另外请注意,Cython 也可以 使用多线程 !Cython 在后台可以直接调用 OpenMP。不过我没有时间在这里讨论并行性,所以请查看 此链接 以了解更多详情。

现在让我们尝试使用 spaCy 和 Cython 来加速 Python 代码。

首先需要考虑好数据结构,我们需要一个 C 类型的数组来存储数据,需要指针来指向每个文档的 TokenC 数组。我们还需要将测试字符(「run」和「NN」)转成 64 位哈希码。

当所有需要处理的数据都变成了 C 类型对象,我们就可以以纯 C 语言的速度对数据集进行迭代。

这里展示了这个例子被转换成 Cython 和 spaCy 的实现:

代码有点长,因为我们必须在调用 Cython 函数之前在 main_nlp_fast 中声明和填充 C 结构。

这串代码虽然变长了,但是运行效率却更高!在我的 Jupyter notebook上,这串 Cython 代码只运行了大概 20 毫秒,比之前的纯 Python 循环快了大概 80 倍。

使用 Jupyter notebook 单元编写模块的速度很可观,它可以与其它 Python 模块和函数自然地连接:在 20 毫秒内扫描大约 170 万个单词,这意味着我们每秒能够处理高达 8 千万个单词。

对使用 Cython 进行自然语言处理加速的介绍到此为止,希望大家能喜欢它。

关于 Cython 还有很多其它的东西可以介绍,但是已经超出了这篇文章的初衷(这篇文章只是作为简介)。从现在开始,最好的资料也许是这份综述性的 Cython 教程 和介绍 spaCy 自然语言处理的 Cython 页面 。

如果你还想要获得更多类似的内容,请记得给我们点赞哟!

Via 100 Times Faster Natural Language Processing in Python ,雷锋网 AI 研习社编译整理

版权文章,未经授权禁止转载。详情见 转载须知 。

老司机都开火箭了!Cython 助力 Python NLP 实现百倍加速

全局中部横幅
CNC加工厂家

昆山鼎军机械制造有限公司主营设备有进口数控加工中心及立式加工中心多台、中大型数控车床多台、数控龙门加工中心、车床、铣床、钻床、CNC精密加工、CNC数控加工、数控立车、数控立车加工剪板机、折弯机、并配备多名管理及技术人员。是一家专业生产龙门加工中心厂家,望有意者与我们联系!

无锡宏瑞机器制造有限公司

无锡宏瑞机器制造有限公司创建于80年代初,是一家专业生产软包装成套设备、新材料涂布设备、水处理膜设备以及提供技术培训与调试服务的综合性企业。

【快捷旅游网】机票代理首选机票加盟平台

苏州快捷航空网拥有专业的机票代理一二类资质,专业为飞机票代理人提供国内国际机票代理,机票加盟,机票接口以及机票查询接口等业务,我们的目标是做机票代理行业的专业机票解决方案提供商。

首页

上海万真物联科技有限公司

拜拜导航

拜拜导航网是一站式的专业资源导航网站,汇集了运营、产品、编程、设计、AI技术、短视频等多个领域的顶级资源。无论是设计师寻找创意灵感,程序员寻求编码工具,还是产品经理寻找最新的产品开发策略,拜拜导航导航网提供全面而精准的导航服务,助您快速找到所需的行业资源。

首页

提供各类网站、小程序、APP定制化开发部署方案以及技术支持和培训服务

成都坤舆空间科技有限公司,坤舆空间科技,坤舆空间

成都坤舆空间科技有限公司是新时期,立足于新领域,以“时空大数据+”为引领的高科技信息产业发展公司。公司主要围绕区域规划、规划设计、项目实施,到运维管理为主线展开四大业务板块,开展主营业务。

消泡器

14年专注全自动消泡脱泡机设备研发生产厂家,主营消泡器,脱泡机,节能器,废膜渣脱水机,已成功为业内多家上市企业提供生产环节消泡方案.

全局底部横幅