python求加速度_如何利用Python 為自然語言處理加速度

自去年發布 Python 的指代消解包(coreference resolution package)之后,很多用戶開始用它來構建許多應用程序,而這些應用與我們最初的對話應用完全不同。

利用 spaCy 和一點點 Cython 給 NLP 加速。

自去年發布 Python 的指代消解包(coreference resolution package)之后,很多用戶開始用它來構建許多應用程序,而這些應用與我們最初的對話應用完全不同。

我們發現,盡管在處理對話時這個包的速度完全沒問題,但在處理較大的問題時卻非常慢。

我決定調查一下這個問題,于是就產生了 NeuralCoref v3.0(https://github.com/huggingface/neuralcoref/)這一項目,它比上一個版本快 100 倍(每秒能分析幾千個單詞),同時保持準確度、易用性,并且依然在 Python 庫的生態系統中。

在本文中我想分享一些在這個項目中學習到的經驗,具體來說包括:

? ?怎樣用 Python 設計高速的模塊;

? ?怎樣利用 spaCy 的內部數據結構來有效地設計高速的 NLP 函數。

? ?所以其實這里有點耍花招,雖然我們是在討論 Python,但還要用一些 Cython的魔法。但別忘了,Cython 是 Python 的超集(http://cython.org/),所以別被它嚇住了!

你現在的 Python 程序已經是 Cython 程序了。

幾種情況下你可能會需要這種加速,例如:

? ?用 Python 為生產環境開發 NLP 模塊;

? ?用 Python 在大型 NLP 數據集上計算分析結果;

為 pyTorch 或 TensorFlow 等深度學習框架預處理一個大型數據集,或者在深度學習的批次加載器中有個很復雜的處理邏輯使得訓練變慢。

在我們開始前要說的最后一件事:這篇文章里的例子我都放在了Jupyter Notebook(https://github.com/huggingface/100-times-faster-nlp)上。試試看吧!

加速的第一步:性能分析

首先要明確一點,絕大部分純 Python 的代碼是沒有問題的,但有幾個瓶頸函數如果能夠解決,就能給速度帶來數量級上的提升。

因此首先應該用分析工具分析 Python 代碼,找出哪里慢。一個辦法是使用cProfile(https://docs.python.org/3/library/profile.html):

import cProfile

import pstats

import my_slow_module

cProfile.run('my_slow_module.run()', 'restats')

p = pstats.Stats('restats')

p.sort_stats('cumulative').print_stats(30)

也許你會發現有幾個循環比較慢,如果用神經網絡的話,可能有幾個 Numpy 數組操作會很慢(但這里我不會討論如何加速 NumPy,已經有很多文章討論這個問題了:http://cython.readthedocs.io/en/latest/src/userguide/numpy_tutorial.html)。

那么,應該如何加快循環的速度?

利用 Cython 實現更快的循環

用個簡單的例子來說明。假設我們一個巨大的集合里包含許多長方形,保存為 Python 對象(即 Rectangle 類的實例)的列表。模塊的主要功能就是遍歷該列表,數出有多少個長方形超過了某個閾值。

我們的 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 check_rectangles(rectangles, threshold):

n_out = 0

for rectangle in rectangles:

if rectangle.area() > threshold:

n_out += 1

return n_out

def main():

n_rectangles = 10000000

rectangles = list(Rectangle(random(), random()) for i in range(n_rectangles))

n_out = check_rectangles(rectangles, threshold=0.25)

print(n_out)

這里 check_rectangles 函數就是瓶頸!它要遍歷大量 Python 對象,而由于每次循環中 Python 解釋器都要在背后進行許多工作(如在類中查找 area 方法、打包解包參數、調用 Python API 等),這段代碼就會非常慢。

這里 Cython 能幫我們加快循環。

Cython 語言是 Python 的一個超集,它包含兩類對象:

? ?Python 對象是在正常的 Python 中操作的對象,如數字、字符串、列表、類實例等。

? ?Cython C 對象是 C 或 C++ 對象,如 dobule、int、float、struct、vectors,這些可以被 Cython 編譯成超級快的底層代碼。

? ?高速循環就是 Cython 程序中只訪問 Cython C 對象的循環。

設計這種高速循環最直接的辦法就是,定義一個 C 結構,它包含計算過程需要的一切。在這個例子中,該結構需要包含長方形的長和寬。

然后我們就可以將長方形列表保存在一個 C 數組中,傳遞給 check_rectangle 函數。現在該函數就需要接收一個 C 數組作為輸入,因此它應該用 cdef 關鍵字(而不是 def)定義為 Cython 函數。(注意 cdef 也被用來定義 Cython C 對象。)

下面是 Cython 高速版本的模塊:

from cymem.cymem cimport Pool

from random import random

cdef struct Rectangle:

float w

float h

cdef int check_rectangles(Rectangle* rectangles, int n_rectangles, float threshold):

cdef int n_out = 0

# C arrays contain no size information => we need to give it explicitly

for rectangle in rectangles[:n_rectangles]:

if rectangle[i].w * rectangle[i].h > threshold:

n_out += 1

return n_out

def main():

cdef:

int n_rectangles = 10000000

float threshold = 0.25

Pool mem = Pool()

Rectangle* rectangles = mem.alloc(n_rectangles, 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 指針數組,不過你也可以用別的方式,如 vectors、pairs、queues 等 C++ 結構(http://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html#standard-library)。在這段代碼中,我還使用了cymem(https://github.com/explosion/cymem)提供的方便的 Pool() 內存管理對象,這樣就不用手動釋放 C 數組了。在 Python 對 Pool 進行垃圾回收時,就會自動釋放所有通過 Pool 分配的內存。

關于在 NLP 中使用 Cython 的指南請參考 spaCy API 的 Cython Conventions:https://spacy.io/api/cython#conventions。

試一下這段代碼

有許多方法可以測試、編譯并發布 Cython 代碼!Cython 甚至可以像 Python 一樣直接用在 Jupyter Notebook 中(http://cython.readthedocs.io/en/latest/src/reference/compilation.html#compiling-notebook)。

首先用 pip install cython 安裝 Cython:

在 Jupyter 中測試

在 Jupyter notebook 中通過 %load_ext Cython 加載 Cython 擴展。

現在,只需使用魔術命令(http://cython.readthedocs.io/en/latest/src/reference/compilation.html#compiling-with-a-jupyter-notebook)%%cython 就可以像寫 Python 代碼一樣寫 Cython 代碼了。

如果在執行 Cython 單元的時候遇到編譯錯誤,可以在 Jupyter 的終端輸出上看到完整的錯誤信息。

一些常見的錯誤:如果要編譯成 C++(比如使用 spaCy Cython API),需要在 %%cython 后面加入 -+ 標記;如果編譯器抱怨 NumPy,需要加入 import numpy 等。

編寫、使用并發布 Cython 代碼

Cython 代碼保存在 .pyx 文件中。這些文件會被 Cython 編譯器編譯成 C 或 C++ 文件,然后再被系統的 C 編譯器編譯成字節碼。這些字節碼可以直接被 Python 解釋器使用。

可以在 Python 中使用 pyximport 直接加載 .pyx 文件:

>>> import pyximport; pyximport.install()

>>> import my_cython_module

也可以將Cython代碼構建成Python包,并作為正常的Python包導入或發布(詳細說明在此:http://cython.readthedocs.io/en/latest/src/tutorial/cython_tutorial.html#)。這項工作比較花費時間,主要是要處理所有平臺上的兼容性問題。如果需要示例的話,spaCy 的安裝腳本(https://github.com/explosion/spaCy/blob/master/setup.py)就是個很好的例子。

在進入 NLP 之前,我們先快速討論下 def、cdef 和 cpdef 關鍵字,這些是學習 Cython 時最關鍵的概念。

Cython 程序中包含三種函數:

? ?Python 函數,由關鍵字 def 定義。它的輸入和輸出都是 Python 對象。內部可以使用 Python 對象,也可以使用 C/C++ 對象,也可以調用 Cython 函數和 Python 函數。

? ?Cython 函數,用 cdef 關鍵字定義。Python 對象和 Cython 對象都可以作為它的輸入、輸出和內部對象使用。這些函數無法在 Python 空間(即 Python 解釋器,和其他需要導入 Cython 模塊的純 Python 模塊)中直接訪問,但可以被其他 Cython 模塊導入。

? ?用 cpdef 定義的 Cython 函數,類似于用 cdef 定義的 Cython 函數,但它們還提供了 Python 封裝,因此可以直接在 Python 空間中調用(用 Python 對象作為輸入和輸出),也可以在其他 Cython 模塊中調用(用 C/C++ 或 Python 對象作為輸入)。

cdef 關鍵字還有個用法,就是在代碼中給 Cython C/C++ 對象定義類型。沒有用 cdef 定義類型的對象會被當做 Python 對象處理(因此會降低訪問速度)。

通過 spaCy 使用 Cython 加速 NLP

前面說的這些都很好……但這跟 NLP 還沒關系呢!沒有字符串操作,沒有 Unicode 編碼,自然語言處理中的難點都沒有支持啊!

而且 Cython 的官方文檔甚至還反對使用 C 語言級別的字符串(http://cython.readthedocs.io/en/latest/src/tutorial/strings.html):

? ?一般來說,除非你知道你在做什么,否則盡量不要使用 C 字符串,而應該使用 Python 字符串對象。

? ?那在處理字符串時怎樣才能設計高速的 Cython 循環?

這就輪到 spaCy 出場了。

spaCy 解決這個問題的辦法特別聰明。

將所有字符串轉換成 64 比特 hash

在 spaCy 中,所有 Unicode 字符串(token 的文本,token 的小寫形式,lemma 形式,詞性標注,依存關系樹的標簽,命名實體標簽……)都保存在名為 StringStore 的單一數據結構中,字符串的索引是 64 比特 hash,也就是 C 語言層次上的 unit64_t。

StringStore 對象實現了在 Python unicode 字符串和 64 比特 hash 之間的查找操作。

StringStore 可以從 spaCy 中的任何地方、任何對象中訪問,例如可以通過 nlp.vocab.string、doc.vocab.strings 或 span.doc.vocab.string 等。

當模塊需要在某些 token 上進行快速處理時,它只會使用 C 語言層次上的 64 比特 hash,而不是使用原始字符串。調用 StringStore 的查找表就會返回與該 hash 關聯的 Python unicode 字符串。

但是 spaCy 還做了更多的事情,我們可以通過它訪問完整的 C 語言層次上的文檔和詞匯表結構,因此可以使用 Cython 循環,不需要再自己構建數據結構。

spaCy 的內部數據結構

spaCy 文檔的主要數據結構是 Doc 對象,它擁有被處理字符串的 token 序列(稱為 words)及所有注解(annotation),這些被保存在一個 C 語言對象 doc.c 中,該對象是個 TokenC 結構的數組。

TokenC(https://github.com/explosion/spaCy/blob/master/spacy/structs.pxd)結構包含關于 token 的所有必要信息。這些信息都保存為 64 比特 hash 的形式,可以通過上面的方法重新構成 unicode 字符串。

看看 spaCy 的 Cython API 文檔,就知道這些 C 結構的好處在哪里了。

我們通過一個簡單例子看看它在 NLP 處理中的實際應用。

通過 spaCy 和 Cython 進行快速 NPL 處理

假設我們有個文本文檔的數據集需要分析。

import urllib.request

import spacy

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

text = response.read()

nlp = spacy.load('en')

doc_list = list(nlp(text[:800000].decode('utf8')) for i in range(10))

上面的腳本建立了一個由10個spaCy解析過的文檔組成的列表,每個文檔大約有17萬個詞。也可以使用17萬個文檔,每個文檔有10個詞(比如對話的數據集),但那樣創建速度就會慢很多,所以還是繼續使用10個文檔好了。

我們要在這個數據集上做一些NLP的處理。比如,我們需要計算“run”這個詞在數據集中作為名詞出現的次數(即被spaCy的詞性分析(Part-Of-Speech)標記為“NN”的詞)。

Python 循環的寫法很直接:

def slow_loop(doc_list, word, tag):

n_out = 0

for doc in doc_list:

for tok in doc:

if tok.lower_ == word and tok.tag_ == tag:

n_out += 1

return n_out

def main_nlp_slow(doc_list):

n_out = slow_loop(doc_list, 'run', 'NN')

print(n_out)

但也非常慢!在我的筆記本上這段代碼大概需要1.4秒才能得到結果。如果有100萬個文檔,那就要超過一天的時間。

我們可以使用多任務處理,但在Python中通常并不是個好主意(https://youtu.be/yJR3qCUB27I?t=19m29s),因為你得處理GIL(全局解釋器鎖,https://wiki.python.org/moin/GlobalInterpreterLock)!而且,別忘了Cython也支持多線程(https://cython.readthedocs.io/en/latest/src/userguide/parallelism.html)!而且實際上多線程才是Cython最精彩的部分,因為GIL鎖已經被釋放,代碼可以全速運行了。基本上,Cython會直接調用OpenMP。這里不會介紹并行,更多的細節可以參考這里(https://cython.readthedocs.io/en/latest/src/userguide/parallelism.html)。

現在試著用 spaCy 和一點 Cython 加速 Python 代碼吧。

首先需要考慮下數據結構。我們需要個C層次的數組來保存數據集,其中的指針指向每個文檔的TokenC數組。還需要將測字符串(“run"和“NN”)轉換成64比特hash。

下面是用spaCy編寫的Cython代碼:

%%cython -+

import numpy # Sometime we have a fail to import numpy compilation error if we don't import numpy

from cymem.cymem cimport Pool

from spacy.tokens.doc cimport Doc

from spacy.typedefs cimport hash_t

from spacy.structs cimport TokenC

cdef struct DocElement:

TokenC* c

int length

cdef int fast_loop(DocElement* docs, int n_docs, hash_t word, hash_t tag):

cdef int n_out = 0

for doc in docs[:n_docs]:

for c in doc.c[:doc.length]:

if c.lex.lower == word and c.tag == tag:

n_out += 1

return n_out

def main_nlp_fast(doc_list):

cdef int i, n_out, n_docs = len(doc_list)

cdef Pool mem = Pool()

cdef DocElement* docs = mem.alloc(n_docs, sizeof(DocElement))

cdef Doc doc

for i, doc in enumerate(doc_list): # Populate our database structure

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函數之前,在main_nlp_fast中定義并填充C結構。(注:如果在代碼中多次使用低級結構,就不要每次填充C結構,而是設計一段Python代碼,利用Cython擴展類型(http://cython.readthedocs.io/en/latest/src/userguide/extension_types.html)來封裝C語言的低級結構。spaCy的絕大部分數據結構都是這么做的,能優雅地結合速度、低內存占用,以及與外部Python庫和函數的接口的簡單性。)

但它也快得多!在我的Jupyter notebook上,這段Cython代碼只需要大約20毫秒,比純Python循環快大約80倍。

要知道它只是Jupyter notebook單元中的一個模塊,還能給其他Python模塊和函數提供原生的接口,考慮到這一點,它的絕對速度也相當出色:20毫秒內掃描1700萬詞,意味著每秒能掃描八千萬詞。

這就是在 NLP 中使用 Cython 的方法,希望你能喜歡。

想要了解學習大數據的可以加群,群號: 834325294,群里有免費的學習資料和視頻。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455350.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

druid.properties文件的配置

# druid.properties文件的配置 driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://127.0.0.1:3306/plan usernameroot password # 初始化連接數量 initialSize5 # 最大連接數 maxActive10 # 最大超時時間 maxWait3000

jquery+easyui開發、培訓文檔

目 錄 1.... Accordion(可折疊標簽)....................................................................................... 2 1.1 實例.............................................................................................…

HAProxy用法詳解 全網最詳細中文文檔

一、HAProxy簡介(1)HAProxy 是一款提供高可用性、負載均衡以及基于TCP(第四層)和HTTP(第七層)應用的代理軟件,支持虛擬主機,它是免費、快速并且可靠的一種解決方案。 HAProxy特別適用…

mp4文件格式系列

mp4文件格式系列1 - 綜述Overview and Introduction Core Concepts MP4文件格式中,所有的內容存在一個稱為movie的容器中。一個movie可以由多個tracks組成。每個track就是一個隨時間變化的媒體序列,例如,視頻幀序列。track里的每個時間單…

JDBC筆記-李偉杰版

JDBC 03 2019/8/1 9:51:41 筆記網站 全球加速: http://zaixianke.com 北京節點: http://itdage.cnJDBC 事務 *** 在dos命令行操作oracle時 , 執行DML , 需要結束事務 (commit提交 或 rollback回退) 在JDBC中, 事務是自動提交的, 每執行一條DML語句, 事務就自動提交一次…

局域網網絡風暴檢測工具_【思唯網絡學院】從原理到配置,最全的VLAN說明就在這了!...

有關VLAN的技術標準IEEE 802.1Q早在1999年6月份就由IEEE委員正式頒布實施了,而且最早的VLNA技術早在1996年Cisco(思科)公司就提出了。隨著幾年來的發展,VLAN技術得到廣泛的支持,在大大小小的企業網絡中廣泛應用,成為當前最為熱門的…

SQL server 基本語句

--查詢數據庫是否存在 if exists ( select * from sysdatabases where [name]TestDB) print Yes, the DB exists else print No, need a new one? --新建一個數據庫 create database TestDB on ( name TestData, filename G:\DBS\KeyTest.mdf, size 3, filegrowth 2 ) log…

pythonselenium模擬登陸爬取信息_python3 使用selenium模擬登陸天眼查抓取數據

由于之前用Scrapy 抓了一些公司的名稱,但是沒有準確的聯系方式,所以就自己就學習了一下使用selenium自動化工具,速度比較慢,網上也有很多這方面的代碼,但是大部分的網頁解析部分都出錯了,可能是這種網站定時…

mp4格式

下面的軟件下載地址:http://download.csdn.net/source/2607382 ftyp: 這是一個筐,可以裝mdat等其他Box。 例:00 00 00 14 66 74 79 70 69 73 6F 6D 00 00 02 00 6D 70 34 31 語義為:ftyp: Major brand: isom Minor version: 5…

PyQt5案例匯總(簡潔版)

01菜單欄 import sys from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication from PyQt5.QtGui import QIconclass Example(QMainWindow):def __init__(self):super().__init__()self.initUI()def initUI(self):exitAct QAction(QIcon(exit.png), &Exit,…

個人關于浮動的理解

浮動帶來的影響: ①. 能夠讓浮動之后的元素布局產生混亂 (元素浮動后脫離了標準文檔流,它的布局也就不再遵循標準文檔流的規則,多個浮動元素在一起的時候就會隨著窗口的變化而變化,也就失去了穩定的布局) ②…

接口測試 rest-assured 使用指南

轉載:https://testerhome.com/topics/7060 原文:https://github.com/rest-assured/rest-assured/wiki/Usage本文github地址:https://github.com/RookieTester/rest-assured-doc 注意,如果您正在使用1.9.0或者更早的版本請參考舊文…

python中格式化字符串的作用_python中字符串格式化的意義(化妝)

格式 描述%%百分號標記 #就是輸出一個%%c字符及其ASCII碼%s字符串%d有符號整數(十進制)%u無符號整數(十進制)%o無符號整數(八進制)%x無符號整數(十六進制)%X無符號整數(十六進制大寫字符)%e浮點數字(科學計數法)%E浮點數字(科學計數法,用E代替e)%f浮點數字(用小數點…

MongoDB的快速手動安裝

就是關于MongoDB主從庫的安裝配置和啟動。網上關于MongoDB的安裝有大量的文章供大家學習。我這里提供一個Windows環境下MongoDB主從庫的快速手動安裝的方法,只需要三步即可。 先下載的安裝包,解壓縮后找到bin文件夾,將bin文件夾拷貝至你自己的…

MP4音頻解碼信息

文章轉載自:http://blog.csdn.net/flyingqr/archive/2010/02/02/5282600.aspx 版權歸原作者,編輯:小乙哥 MP4文件格式分為頭部和數據兩部分,頭部是由許多被稱作Atom的結構單元嵌套或排列而成,數據部分則完全為實際數據…

時序圖 分支_UML用例圖

UML用例圖用例圖有以下東東:用例參與者關聯系統邊界用例使用橢圓來表示,橢圓里邊寫上用例的名稱:這里的用例可以理解為一個動作或行為,或者一個對象。參與者用一個小人兒,在小人兒下面寫上參與者名稱,例如學生:關聯用一條線表示:把很多個用例放到一個大的矩形框里。…

Python腳本實現漢子轉拼音

起步 中華文化博大精深,是中華民族的財富,吸收和繼承發揚中 華文化,是現代每個炎黃子孫無可推卸的天職。 今天小編就交大家用python寫一個腳本,實現漢子和拼音之間的轉換 pinyin.py 漢字轉拼音,With Python Example: from pinyin impor…

MySQL innodb_table_stats表不存在的解決方法

在做實驗時,使用mysqldump命令報錯[rootlinux-mysql02 3306]# mysqldump -uroot -p123456 -S /u02/data/3306/mysql.sock -A -B --events | gzip > /opt/rep.sql.gzmysqldump: Got error: 1146: Table mysql.innodb_index_stats doesnt exist when using LOCK TA…

自定義封裝 banner 組件

1. 效果圖預覽 2.基本功能 一個簡單方便的輪播圖組件,基于viewpager 基礎上進行的封裝。可設置 項目中圖片,網絡圖片, View;支持循環自動播放,手勢滑動切換,item點擊事件,可設置 點點的樣式寬高、顏色、大小…

vb.net服務器啟動后cpu占用了70_記一次服務器被異常程序占用的解決過程(懷疑黑客攻擊)...

最近在跑實驗,但是突然發現程序運行變慢,然后top命令查看程序運行情況,發現有異常進程,名字叫 bash,占用 2400% CPU計算資源。剛開始懷疑是挖礦程序,因實驗室網絡IP為教育網公網,懷疑被攻擊&…