Leifeng.com AI Araştırma Enstitüsü Not: Bu makalenin yazarı, Hugging face'ten bir bilim adamı olan Thomas Wolf'dur.Araştırma alanları arasında makine öğrenimi, doğal dil işleme ve derin öğrenme yer almaktadır. Bu blogda, Python'u doğal dil işleme görevlerinde yüzlerce kat daha hızlı hale getirmek için Cython ve spaCy'nin nasıl kullanılacağını tanıttı. Leifeng.com AI Araştırma Enstitüsü orijinal metni derledi.
SpaceX Falcon Heavy Launcher, telif hakkı SpaceX'e aittir
İpucu: Bu makaledeki tüm örnekler, bu Jupyter not defterinden kaynak kodunu alabilir.
Geçen yıl Python'da uygulanan Neural coreference çözüm paketini (Neural coreference çözüm paketi) yayınladıktan sonra, topluluktan şaşırtıcı miktarda geri bildirim aldık ve birçok kişi çözüm paketini çeşitli uygulamalarda kullanmaya başladı. İçinde, bazı uygulama senaryoları, başlangıçta tasarladığımız iletişim kutusu kullanım durumunu bile aştı.
Daha sonra, bu ayrıştırma paketinin ayrıştırma hızının iletişim mesajları için oldukça yeterli olmasına rağmen, daha büyük makaleleri ayrıştırmanın çok yavaş olduğunu keşfettik.
Bu yüzden çözümü derinlemesine keşfetmeye karar verdim ve sonunda NeuralCoref v3.0'ı geliştirdim. Bu sürüm, öncekinden yüz kat daha hızlıdır (saniyede birkaç bin kelimeyi analiz eder) ve yine de aynı doğruluğu sağlar.Elbette kullanımı kolaydır ve Python kütüphanesinin ekolojik ortamına uygundur.
Bu yazıda, NeuralCoref v3.0'ın geliştirilmesinde öğrendiğim bazı deneyimleri, özellikle aşağıdakilerle ilgili olarak sizlerle paylaşmak istiyorum:
Verimli bir modül tasarlamak için Python'u nasıl kullanabiliriz,
Süper verimli doğal dil işleme işlevleri tasarlamak için spaCy'nin yerleşik veri yapısından en iyi şekilde nasıl yararlanılır.
Başlığım aslında biraz hile yapıyor, çünkü aslında Python hakkında konuşacağım ve ayrıca bazı Cython özelliklerini tanıtacağım. Ama biliyor musun? Cython, Python'un bir üst kümesidir, bu yüzden korkutmasına izin vermeyin!
İpucu: Şu anda yazdığınız Python projesi zaten bir Cython projesidir.
İşte bu hızlandırma stratejisinin gerekli olabileceği bazı senaryolar:
Doğal dil işleme görevleri için uygulama düzeyinde bir modül geliştirmek için Python kullanıyorsunuz
Doğal bir dil işleme görevi için büyük bir veri kümesini analiz etmek için Python kullanıyorsunuz
PyTorch / TensoFlow gibi derin öğrenme çerçeveleri için büyük bir eğitim setini önceden işliyorsunuz veya derin öğrenme modeliniz, eğitim hızınızı ciddi şekilde yavaşlatan karmaşık işleme mantığına sahip bir toplu yükleyici kullanıyor
İpucu: Ayrıca bu makalede tartışılan tüm örnekleri içeren bir Jupyter not defteri yayınladım, indirip hata ayıklamaya hoş geldiniz!
Bilmeniz gereken ilk şey, kodunuzun çoğunun saf bir Python ortamında iyi çalışabileceğidir, ancak bazı darboğaz işlevleri vardır (Darboğazlar işlevleri). Onlara daha fazla "özen" verdikten sonra, Program birkaç büyüklük sırasına göre hızlandırılacaktır.
Bu yüzden, bu verimsiz modülleri bulmak için kendi Python kodunuzu analiz ederek başlamalısınız. Bir yol cProfile kullanmaktır:
cProfile içe aktar
pstat'ları içe aktar
my_slow_module'ümü içe aktar
cProfile.run ('my_slow_module.run', 'restats')
p = pstats.Stats ('restats')
p.sort_stats ('kümülatif'). print_stats (30)
Büyük olasılıkla, verimsizliğin nedeninin bazı döngü kontrollerinden kaynaklandığını göreceksiniz veya sinir ağlarını kullanırken çok fazla Numpy dizisi işlemi başlatmışsınızdır (Numpy'yi tanıtmak için burada zaman harcamayacağım, bu konu çok fazla makalede tartışıldı ).
Peki döngüyü nasıl hızlandırabiliriz?
Bu sorunu basit bir örnekle çözelim. Bir grup dikdörtgen olduğunu varsayarsak, bunları Python nesnelerinin bir listesi (Rectangle nesne örnekleri gibi) olarak saklarız. Modülümüzün ana işlevi, belirlenen eşikten kaç tane dikdörtgenin daha büyük olduğunu saymak için listede yinelemeli işlemler gerçekleştirmektir.
Python modülümüz çok basit:
rastgele ithalattan rastgele
sınıf Dikdörtgen:
def __init __ (öz, w, h):
self.w = w
self.h = h
def alanı (kendi):
dönüş self.w * self.h
def check_rectangles (dikdörtgenler, eşik):
n_out = 0
dikdörtgenlerde dikdörtgen için:
rectangle.area ise > eşik:
n_out + = 1
geri dön n_out
def main:
n_rectangles = 10000000
dikdörtgenler = liste (Aralıktaki i için dikdörtgen (rastgele, rasgele) (n_rectangles))
n_out = check_rectangles (dikdörtgenler, eşik = 0.25)
baskı (n_out)
Check_rectangles işlevi programımızın darboğazını oluşturur! Uzun bir Python nesneleri listesi üzerinde yinelenir ve bu işlem oldukça yavaş olacaktır, çünkü Python yorumlayıcısının her yinelemede çok fazla iş yapması gerekir (sınıfta alan yöntemini bulma, parametreleri paketleme ve açma, Python'u çağırma) API vb.).
Cython'dan döngü işlemini hızlandırmamıza yardım etmesini istemenin zamanı geldi.
Cython dili, Python'un bir üst kümesidir ve iki tür nesne içerir:
Python nesnesi Normal Python'da kullandığımız nesneler mi? Sayılar, dizeler, listeler ve sınıf örnekleri ve daha fazlası
Cython C nesneleri Bunlar C ve C ++ nesneleridir, örneğin Çift hassasiyet, tam sayı, kayan nokta, yapı ve vektör , Cython tarafından süper verimli düşük seviyeli dil kodunda derlenebilirler
Bu döngü Cython ile yeniden üretildiği sürece, daha yüksek yürütme hızına ulaşabilir, ancak Cython'da yalnızca Cython C nesnelerini değiştirebiliriz.
Bu döngüyü tanımlamanın en doğrudan yollarından biri, hesaplama sürecinde kullanmamız gereken tüm nesneleri içeren bir yapı tanımlamaktır. Özellikle, bu durumda dikdörtgenin uzunluğu ve genişliğidir.
Daha sonra dikdörtgen nesnelerin listesini C'nin yapı dizisinde saklayabilir ve ardından diziyi check_rectangles işlevine geçirebiliriz. Bu işlev artık girdi olarak bir C dizisi alacak.Ayrıca, işlevi bir Cython işlevi olarak tanımlamak için def yerine cdef anahtar sözcüğünü kullanıyoruz (not: cdef, Cython C nesnelerini tanımlamak için de kullanılabilir).
İşte modül programının Cython versiyonu:
from cymem.cymem cimport Havuzu
rastgele ithalattan rastgele
cdef struct Dikdörtgen:
yüzer w
yüzer h
cdef int check_rectangles (Dikdörtgen * dikdörtgenler, int n_rectangles, kayan eşik):
cdef int n_out = 0
# C dizileri boyut bilgisi içermez = > açıkça vermemiz gerekiyor
dikdörtgenlerde dikdörtgen için:
dikdörtgen ise .w * dikdörtgen .h > eşik:
n_out + = 1
geri dön n_out
def main:
cdef:
int n_rectangles = 10000000
şamandıra eşiği = 0.25
Havuz mem = Havuz
Dikdörtgen * dikdörtgenler =
aralıktaki i için (n_rectangles):
dikdörtgenler .w = rastgele
dikdörtgenler .h = rastgele
n_out = check_rectangles (dikdörtgenler, n_rectangles, eşik)
baskı (n_out)
Burada yerel C dizi işaretçisini kullanıyoruz, ancak başka seçenekleriniz de var, özellikle vektörler, ikili diziler ve kuyruklar gibi C ++ yapıları. Bu programda, cymem tarafından sağlanan, istenen C dizisi bellek alanını manuel olarak serbest bırakmayı önleyebilen bir Havuz bellek yönetimi nesnesi de kullandım. Havuzdaki nesneye artık ihtiyaç duyulmadığında, nesnenin kapladığı bellek alanını otomatik olarak serbest bırakacaktır.
Ek: spaCy API'sinin Cython standart sayfası, pratik uygulamalarda doğal dil işleme görevlerini uygulamak için Cython'u kullanmak için referans materyalleri sağlar.
Cython kodunu test etmenin, derlemenin ve yayınlamanın birçok yolu vardır. Cython, Python gibi doğrudan Jupyter Notebook'ta bile kullanılabilir.
Cython'u pip install cython komutu ile kurun.
Cython uzantısını Jupyter not defterine yüklemek için% load_ext Cython komutunu kullanın.
Sonra %% cython komutu ile Cython'u Jupyter not defterinde Python gibi kullanabiliriz.
Cython kodunu çalıştırırken derleme hatalarıyla karşılaşırsanız, lütfen Jupyter terminalinin tam çıktısını kontrol edin.
Çoğu durumda, %% cython'dan sonra - + etiketinin çıkarılması olabilir (örneğin, spaCy Cython arayüzünü kullandığınızda). Derleyici Numpy hakkında bir hata bildirirse, içeri aktarma numarası eksiktir.
Başta da belirttiğim gibi, lütfen bu Jupyter defterini okuyun ve bu makale Jupyter'de tartıştığımız tüm örnekleri içerir.
Cython kodunun dosya soneki .pyx'tir, bu dosyalar Cython derleyicisi tarafından C veya C ++ dosyalarına derlenecek ve daha sonra C derleyicisi tarafından bayt kodu dosyalarına derlenecektir. Sonunda Python yorumlayıcısı bu bayt kodu dosyalarını çağırabilecektir.
Bir .pyx dosyasını doğrudan bir Python programına yüklemek için pyximport'u da kullanabilirsiniz:
> > > ithal pyximport; pyximport.install
> > > my_cython_module'ü içe aktar
Ayrıca Cython kodunuzu bir Python paketi olarak oluşturabilir ve ardından normal bir Python paketi gibi içe aktarabilir veya yayınlayabilirsiniz.Daha fazla ayrıntı için lütfen buraya bakın. Ancak, bu yaklaşım, özellikle Cython paketini tüm platformlarda çalıştırmanız gerekiyorsa daha fazla zaman alır. Bir referans örneğine ihtiyacınız varsa, spaCy kurulum komut dosyasına da göz atabilirsiniz.
Doğal dil işleme görevlerini optimize etmeye başlamadan önce, hızlıca def, cdef ve cpdef olmak üzere üç anahtar kelimeyi tanıtalım. Cython'u kullanmayı öğrenmeye başlamadan önce ustalaşmanız gereken en önemli bilgiler bunlar.
Cython programlarında üç tür işlev kullanabilirsiniz:
Python fonksiyonları def anahtar sözcüğü ile tanımlanır ve girdisi ve çıktısı Python nesneleridir. İşlevde Python ve C / C ++ nesneleri kullanılabilir ve Cython ve Python işlevleri çağrılabilir.
Cython işlevleri, girdi nesneleri olarak kullanılabilen ve işlev içindeki Python ve C / C ++ nesnelerini çalıştırabilen veya çıkarabilen cdef anahtar sözcüğü ile tanımlanır. Bu işlevlere Python ortamından (yani, Python yorumlayıcısı ve Cython modüllerini içe aktarabilen diğer saf Python modülleri) erişilemez, ancak diğer Cython modülleri tarafından içe aktarılabilir.
Cpdef anahtar kelimesiyle tanımlanan Cython işlevi, cdef tarafından tanımlanan Cython işlevine çok benzer, ancak cpdef tarafından tanımlanan işlevler de Python dekoratörleri sağlar, böylece doğrudan Python ortamında çağrılabilirler (işlevler girdi ve çıktı olarak Python nesnelerini kullanır) Ek olarak, Cython modülünde çağrılması da desteklenir (işlev, girdi olarak C / C ++ veya Python nesnelerini alır).
Cdef anahtar kelimesinin başka bir kullanımı, kodda bir nesnenin Cython C / C ++ nesnesi olduğunu belirtmektir. Dolayısıyla, kodunuzdaki nesneleri bildirmek için cdef kullanmadığınız sürece, bu nesnelere yorumlayıcı tarafından Python nesneleri olarak davranılacaktır (bu, erişim hızını yavaşlatacaktır).
Bütün bunlar harika görünüyor, ancak ... doğal dil işleme görevlerini optimize etmeye bile başlamadık! Dize manipülasyonu, unicode kodlaması ve doğal dil işlemede kullandığımız bir darbe yoktur.
Ek olarak, Cython'un resmi belgeleri C dili türü dizeleri kullanmamanızı bile önerir:
Genel olarak konuşursak: Ne yaptığınızı tam olarak bilmiyorsanız, C tipi dizeler kullanmaktan kaçınmalı ve bunun yerine Python dize nesneleri kullanmalısınız.
Öyleyse dizeleri manipüle ederken, Cython'da nasıl daha verimli bir döngü tasarlarız?
spaCy dikkatimizi çekti.
spaCy'nin bu soruna yaklaşımı çok akıllıca.
SpaCy'deki tüm unicode dizeleri (etiketli metin, küçük harfli metin, lemma, POS etiket etiketleri, ayrıştırma ağacı bağımlılık etiketleri, adlandırılmış varlık etiketleri vb.) StringStore adlı bir veride depolanır. Yapı, bir geçer 64 bit karma kod C tipi uint64_t gibi dizin.
StringStore nesnesi, Python unicode dizeleri ve 64 bit karma kodlar arasındaki arama eşlemesini uygular.
SpaCy'deki npl.vocab.strings, doc.vocab.strings veya span.doc.vocab.string gibi herhangi bir yerden ve herhangi bir nesneden erişilebilir.
Belirli bir modülün belirli belirteçlerde daha hızlı işlem hızı elde etmesi gerektiğinde, elde etmek için dize yerine C dili türü 64-bit karma kodu kullanabilirsiniz. StringStore arama tablosunu çağırmak, karma kodla ilişkili Python unicode dizesini döndürür.
Ancak spaCy bundan daha fazlasını yapabilir.Ayrıca, belgeler ve kelime dağarcığı ile dolu olan C dili tipi yapılara erişmemizi sağlar.Bu yapıları, kendi yapılarımızı inşa etmek zorunda kalmadan Cython döngülerinde kullanabiliriz.
SpaCy belgesiyle ilgili ana veri yapısı, işlenen dizenin simge sırasına ("sözcükler") ve TokenC yapısı olan doc.c adlı C dili türü nesnesindeki tüm ek açıklamalara sahip olan Doc nesnesidir. Dizi.
TokenC yapısı, her bir token hakkında ihtiyacımız olan tüm bilgileri içerir. Bu bilgiler, az önce gördüğümüz unicode dizesiyle yeniden ilişkilendirilebilen 64 bitlik bir karma kod olarak saklanır.
Bu güzel C yapılarının içeriğini doğru bir şekilde anlamak istiyorsanız, yeni oluşturulan spaCy'nin Cython API belgelerine bakabilirsiniz.
Ardından, basit bir doğal dil işleme örneğine bakalım.
Analiz edilmesi gereken bir metin dokümanı veri kümesi olduğunu varsayalım.
urllib.request'i içe aktar
ithal boşluk
yanıt olarak urllib.request.urlopen ('https://raw.githubusercontent.com/pytorch/examples/master/word_language_model/data/wikitext-2/valid.txt') ile:
text = response.read
nlp = spacy.load ('en')
doc_list = list (nlp (text.decode ('utf8')) aralıktaki i için (10))
Her biri yaklaşık 170.000 kelime içeren 10 belgeden oluşan bir liste oluşturmak için bir komut dosyası yazdım ve bunu analiz etmek için spaCy kullandım. Elbette 170.000 belgeyi de analiz edebiliriz (her belge 10 kelime içerir), ancak bu oluşturma sürecinin çok yavaş olmasına neden olur, bu yüzden yine de 10 belge seçtik.
Bu veri setinde belirli doğal dil işleme görevlerini genişletmek istiyoruz. Örneğin, veri kümesinde "run" kelimesinin kaç kez isim olarak göründüğünü sayabiliriz (örneğin, "NN" konuşma etiketinin bir parçası olarak spaCy tarafından işaretlenmiştir).
Yukarıdaki analiz sürecini uygulamak için Python döngülerini kullanmak çok basit ve sezgiseldir:
def slow_loop (doc_list, kelime, etiket):
n_out = 0
doc_list içindeki doc için:
doc için tok için:
tok.lower_ == word ve tok.tag_ == etiketi ise:
n_out + = 1
geri dön n_out
def main_nlp_slow (doc_list):
n_out = slow_loop (doc_list, 'çalıştır', 'NN')
baskı (n_out)
Ancak kodun bu sürümü çok yavaş çalışıyor! Cevabı almak için bu kodun dizüstü bilgisayarımda çalışması 1,4 saniye sürüyor. Veri setimiz milyonlarca belge içeriyorsa, yanıt almamız bir günden fazla sürebilir.
Hızlanma elde etmek için çoklu okumayı kullanabiliriz, ancak bu yaklaşım Python'da pek akıllıca değildir, çünkü global yorumlayıcı kilidi (GIL) ile de ilgilenmeniz gerekir. Ayrıca Cython'un çoklu okumayı da kullanabileceğini unutmayın! Cython, arka planda doğrudan OpenMP'yi arayabilir. Ancak burada paralelliği tartışacak vaktim yok, bu nedenle daha fazla ayrıntı için lütfen bu bağlantıyı kontrol edin.
Şimdi Python kodunu hızlandırmak için spaCy ve Cython'u kullanmayı deneyelim.
İlk olarak, veri yapısını göz önünde bulundurmalıyız Verileri saklamak için bir C-tipi diziye ve her belgenin TokenC dizisine bir göstericiye ihtiyacımız var. Ayrıca test karakterlerini ("çalıştır" ve "NN") 64 bitlik bir karma koda dönüştürmemiz gerekir.
İşlenmesi gereken tüm veriler C tipi bir nesne haline geldiğinde, veri kümesi üzerinde saf C dili hızında yineleme yapabiliriz.
İşte Cython ve spaCy'ye dönüştürülmüş bu örneğin uygulaması:
%% cython - +
import numpy # Bazen numpy'yi içe aktarmazsak numpy derleme hatası alırız
from cymem.cymem cimport Havuzu
spacy.tokens.doc adresinden cimport Doc
spacy.typedefs'ten cimport hash_t
spacy.structs'tan cimport TokenC
cdef struct DocElement:
TokenC * c
int uzunluk
cdef int fast_loop (DocElement * docs, int n_docs, hash_t word, hash_t etiketi):
cdef int n_out = 0
docs için:
doc.c'deki c için:
c.lex.lower == word ve c.tag == etiketi ise:
n_out + = 1
geri dön n_out
def main_nlp_fast (doc_list):
cdef int i, n_out, n_docs = len (doc_list)
cdef Havuz mem = Havuz
cdef DocElement * docs =
cdef Doc dokümanı
i için, numaralandırmadaki doc (doc_list): # Veritabanı yapımızı doldurun
dokümanlar .c = doc.c
dokümanlar .length = (
word_hash = doc.vocab.strings.add ('çalıştır')
tag_hash = doc.vocab.strings.add ('NN')
n_out = fast_loop (dokümanlar, n_docs, word_hash, tag_hash)
baskı (n_out)
Kod biraz uzun çünkü Cython işlevini çağırmadan önce main_nlp_fast içindeki C yapısını bildirip doldurmalıyız.
Ek: Kodunuzda düşük seviyeli yapıları birden çok kez kullanmanız gerekiyorsa, Python kodunu tasarlamak için C tipi yapının Cython genişletilmiş tip dekoratörünü kullanmak her seferinde C yapısını doldurmaktan daha zariftir. Bu, çoğu spaCy kodu tarafından benimsenen yapıdır.Yüksek verimlilik, düşük bellek maliyeti ve kolay erişim ile çok zariftir.
Bu kod dizisi uzamış olsa da, daha verimli çalışır! Jupyter dizüstü bilgisayarımda, bu Cython kodu yalnızca yaklaşık 20 milisaniye boyunca çalıştı, bu önceki saf Python döngüsünden yaklaşık 80 kat daha hızlı.
Jupyter dizüstü bilgisayar birimlerini kullanarak modül yazma hızı etkileyicidir ve doğal olarak diğer Python modülleri ve işlevlerine bağlanabilir: 20 milisaniyede yaklaşık 1,7 milyon kelime taranır, bu da saniyede 80 milyon kelimeye kadar işleyebileceğimiz anlamına gelir.
Bu, doğal dil işleme hızlandırma için Cython kullanmaya girişin sonudur, umarım herkes bundan hoşlanır.
Cython hakkında tanıtılabilecek birçok şey var, ancak bu makalenin asıl amacının ötesindedir (bu makale sadece bir giriş niteliğindedir). Şu andan itibaren, en iyi bilgi bu toparlama Cython öğreticisi ve spaCy doğal dil işleme hakkındaki Cython sayfası olabilir.
Daha fazla benzer içerik almak istiyorsanız, lütfen bizi sevmeyi unutmayın!
Leifeng.com AI Araştırma Enstitüsü tarafından derlenen Python'da 100 Kat Daha Hızlı Doğal Dil İşleme ile