Numpy dizi üzerinden fonksiyonu eşlemenin en etkili yolu


338

Bir işlevi numpy dizisi üzerinden eşlemenin en etkili yolu nedir? Mevcut projemde bunu yapıyorum:

import numpy as np 

x = np.array([1, 2, 3, 4, 5])

# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])

Ancak, bu muhtemelen çok verimsiz gibi görünüyor, çünkü yeni bir dizi tekrar bir numpy diziye dönüştürmeden önce bir Python listesi olarak oluşturmak için bir liste anlama kullanıyorum.

Daha iyisini yapabilir miyiz?


10
neden "kareler = x ** 2" olmasın? Değerlendirmeniz gereken çok daha karmaşık bir fonksiyonunuz var mı?
22degrees

4
Sadece nasıl squarer(x)?
Yaşam

1
Belki bu doğrudan soruyu cevaplamıyor, ancak numba'nın mevcut python kodunu paralel makine talimatlarına derleyebileceğini duydum . Bunu kullanma şansım olduğunda bu yazıyı tekrar gözden geçireceğim ve gözden geçireceğim.
把 友情 留 在 无 盐

x = np.array([1, 2, 3, 4, 5]); x**2işleri
Shark Deng

Yanıtlar:


282

Tüm önerilen yöntemleri artı (küçük bir projem) np.array(map(f, x))ile test ettim perfplot.

İleti # 1: numpy'nin yerel işlevlerini kullanabiliyorsanız bunu yapın.

Zaten vektörize çalışıyorsanız fonksiyonu ise edilmektedir (gibi vectorized x**2yani kullanarak, orijinal sonrası örneğin) çok daha hızlı her şeyden çok (log ölçeği dikkat edin):

resim açıklamasını buraya girin

Aslında vektörleşmeye ihtiyacınız varsa, hangi varyantı kullandığınız gerçekten önemli değildir.

resim açıklamasını buraya girin


Grafikleri yeniden oluşturmak için kod:

import numpy as np
import perfplot
import math


def f(x):
    # return math.sqrt(x)
    return np.sqrt(x)


vf = np.vectorize(f)


def array_for(x):
    return np.array([f(xi) for xi in x])


def array_map(x):
    return np.array(list(map(f, x)))


def fromiter(x):
    return np.fromiter((f(xi) for xi in x), x.dtype)


def vectorize(x):
    return np.vectorize(f)(x)


def vectorize_without_init(x):
    return vf(x)


perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2 ** k for k in range(20)],
    kernels=[f, array_for, array_map, fromiter, vectorize, vectorize_without_init],
    xlabel="len(x)",
)

7
Arsadan f(x)çıkmış gibi görünüyorsun . Her biri için geçerli olmayabilir f, ancak burada uygulanabilir ve uygulanabilir olduğunda en hızlı çözümdür.
user2357112 Monica

2
Ayrıca, planınız vf = np.vectorize(f); y = vf(x)kısa girdiler için kazanan iddianızı desteklemiyor .
user2357112 Monica

Perfplot (v0.3.2) pip () ile yükledikten sonra pip install -U perfplot, şu AttributeError: 'module' object has no attribute 'save'kodu görüyorum: örnek kodu yapıştırırken.
tsherwen

Döngü için bir vanilya ne olacak?
Catiger3331

1
@Vlad yorum olarak sadece math.sqrt kullanın.
Nico Schlömer

138

Kullanmaya ne dersiniz numpy.vectorize?

import numpy as np
x = np.array([1, 2, 3, 4, 5])
squarer = lambda t: t ** 2
vfunc = np.vectorize(squarer)
vfunc(x)
# Output : array([ 1,  4,  9, 16, 25])

36
Bu artık daha verimli değil.
user2357112 Monica

78
Bu dokümanda: The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop. Diğer sorularda vectorizebunun kullanıcı yineleme hızını iki katına çıkarabileceğini buldum . Ancak gerçek hızlanma, gerçek numpydizi işlemleriyle gerçekleşir.
hpaulj

2
Vectorize işleminin en azından 1 boyutlu olmayan diziler için iş yapmasını sağladığını unutmayın
Eric

Ancak squarer(x)1B olmayan diziler için zaten çalışır. vectorizesadece liste kavrayışına göre (sorudaki gibi) herhangi bir avantajı yoktur, bitmez squarer(x).
user2357112 Monica

79

TL; DR

@ User2357112 tarafından belirtildiği gibi , işlevi uygulamak için "doğrudan" bir yöntem her zaman Numpy dizileri üzerinde bir işlevi eşlemenin en hızlı ve en basit yoludur:

import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)

Genellikle np.vectorizeiyi performans göstermediğinden ve bir dizi sorunu olduğu için (veya vardı) kaçının . Başka veri türlerini işliyorsanız, aşağıda gösterilen diğer yöntemleri araştırmak isteyebilirsiniz.

Yöntemlerin karşılaştırılması

Bir işlevi eşlemek için üç yöntemi karşılaştırmak için bazı basit testler, bu örnek Python 3.6 ve NumPy 1.15.4 ile kullanılır. İlk olarak, test için kurulum işlevleri:

import timeit
import numpy as np

f = lambda x: x ** 2
vf = np.vectorize(f)

def test_array(x, n):
    t = timeit.timeit(
        'np.array([f(xi) for xi in x])',
        'from __main__ import np, x, f', number=n)
    print('array: {0:.3f}'.format(t))

def test_fromiter(x, n):
    t = timeit.timeit(
        'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))',
        'from __main__ import np, x, f', number=n)
    print('fromiter: {0:.3f}'.format(t))

def test_direct(x, n):
    t = timeit.timeit(
        'f(x)',
        'from __main__ import x, f', number=n)
    print('direct: {0:.3f}'.format(t))

def test_vectorized(x, n):
    t = timeit.timeit(
        'vf(x)',
        'from __main__ import x, vf', number=n)
    print('vectorized: {0:.3f}'.format(t))

Beş elementle test (en hızlıdan en yavaşa doğru sıralanmış):

x = np.array([1, 2, 3, 4, 5])
n = 100000
test_direct(x, n)      # 0.265
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.865
test_vectorized(x, n)  # 2.906

100'lü elemanlarla:

x = np.arange(100)
n = 10000
test_direct(x, n)      # 0.030
test_array(x, n)       # 0.501
test_vectorized(x, n)  # 0.670
test_fromiter(x, n)    # 0.883

Ve 1000'ler veya daha fazla dizi elemanı ile:

x = np.arange(1000)
n = 1000
test_direct(x, n)      # 0.007
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.516
test_vectorized(x, n)  # 0.945

Python / NumPy ve derleyici optimizasyonunun farklı sürümleri farklı sonuçlara sahip olacaktır, bu nedenle ortamınız için benzer bir test yapın.


2
Eğer kullanırsanız countargüman ve jeneratör ifadesini daha sonra np.fromiterönemli ölçüde daha hızlıdır.
juanpa.arrivillaga

3
Örneğin, kullanın'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
juanpa.arrivillaga


4
f2 değişken varsa ve dizi 2D ise ne olur?
Sigur

2
OP nasıl bir dizi boyunca bir işlevi eşlemek için soruyordu 'f (x)' sürümü ("doğrudan") aslında karşılaştırılabilir olarak nasıl karışık? F (x) = x ** 2 olması durumunda **, eleman başına esasına göre değil, tüm dizi üzerinde numpy tarafından gerçekleştirilir. Örneğin, f (x) 'lambda x: x + x "ise, cevap çok farklıdır, çünkü numpy eleman ekleme başına yapmak yerine dizileri birleştirir. Bu gerçekten amaçlanan karşılaştırma mı? Lütfen açıklayın.
Andrew Mellinger

49

Orada numexpr , numba ve Cython etrafında, bu cevabın amacı dikkate bu olasılıkları almaktır.

Ama önce açık olanı belirtelim: Bir Python işlevini bir numpy dizisine nasıl eşleseniz de, Python işlevi kalır, yani her değerlendirme için:

  • numpy-dizi elemanı bir Python-nesnesine dönüştürülmelidir (örneğin a Float).
  • tüm hesaplamalar Python nesneleriyle yapılır, yani yorumlayıcı yükü, dinamik dağıtım ve değişmez nesneler vardır.

Bu yüzden, dizi boyunca döngü yapmak için hangi makinelerin kullanıldığı yukarıda belirtilen ek yük nedeniyle büyük bir rol oynamaz - numpy'nin yerleşik işlevini kullanmaktan çok daha yavaş kalır.

Aşağıdaki örneğe bakalım:

# numpy-functionality
def f(x):
    return x+2*x*x+4*x*x*x

# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"

np.vectorizesaf-python fonksiyon sınıfının bir temsili olarak seçilir. Kullanarak perfplot(bu cevabın ekindeki koda bakınız) aşağıdaki çalışma sürelerini elde ederiz:

resim açıklamasını buraya girin

Numpy yaklaşımının saf python versiyonundan 10x-100x daha hızlı olduğunu görebiliriz. Daha büyük dizi boyutları için performansın düşmesi, büyük olasılıkla verilerin artık önbelleğe sığmamasıdır.

Ayrıca vectorizeçok fazla bellek kullanan , ayrıca bellek kullanımı şişe boynudur (ilgili SO sorusuna bakın ). Ayrıca, numpy'nin np.vectorize"performans için değil öncelikle kolaylık için sağlandığını" belirten belgelere dikkat edin .

Diğer araçlar kullanılmalıdır, performans istendiğinde, sıfırdan bir C uzantısı yazmanın yanı sıra, aşağıdaki olasılıklar vardır:


Sık sık, numpy performansının elde ettiği kadar iyi olduğunu duyarız, çünkü kaputun altında saf C'dir. Yine de iyileştirilmesi gereken çok şey var!

Vektörize edilmiş numpy sürümü çok fazla ek bellek ve bellek erişimi kullanır. Numexp kütüphanesi, numpy dizilerini döşemeye çalışır ve böylece daha iyi bir önbellek kullanımı elde eder:

# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
    return ne.evaluate("x+2*x*x+4*x*x*x")

Aşağıdaki karşılaştırmaya yönlendirir:

resim açıklamasını buraya girin

Yukarıdaki tablodaki her şeyi açıklayamıyorum: başlangıçta numexpr-kütüphane için daha büyük bir ek yük görüyoruz, ancak önbelleği daha iyi kullandığından daha büyük diziler için yaklaşık 10 kat daha hızlı!


Başka bir yaklaşım, fonksiyonu jit-derlemek ve böylece gerçek bir saf C UFunc elde etmektir. Bu numba'nın yaklaşımı:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x

Orijinal numpy yaklaşımından 10 kat daha hızlıdır:

resim açıklamasını buraya girin


Bununla birlikte, görev utanç verici bir şekilde paralelleştirilebilir, bu nedenle prangedöngüyü paralel olarak hesaplamak için de kullanabiliriz :

@nb.njit(parallel=True)
def nb_par_jitf(x):
    y=np.empty(x.shape)
    for i in nb.prange(len(x)):
        y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y

Beklendiği gibi, paralel işlev daha küçük girişler için daha yavaş, ancak daha büyük boyutlar için daha hızlı (neredeyse faktör 2):

resim açıklamasını buraya girin


Numba, numpy dizileriyle işlemleri optimize etme konusunda uzmanlaşırken, Cython daha genel bir araçtır. Numba ile aynı performansı elde etmek daha karmaşıktır - genellikle yerel derleyiciye (gcc / MSVC) karşı llvm (numba) değerine düşer:

%%cython -c=/openmp -a
import numpy as np
import cython

#single core:
@cython.boundscheck(False) 
@cython.wraparound(False) 
def cy_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef Py_ssize_t i
    cdef double[::1] y=y_out
    for i in range(len(x)):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

#parallel:
from cython.parallel import prange
@cython.boundscheck(False) 
@cython.wraparound(False)  
def cy_par_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef double[::1] y=y_out
    cdef Py_ssize_t i
    cdef Py_ssize_t n = len(x)
    for i in prange(n, nogil=True):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

Cython, biraz daha yavaş fonksiyonlarla sonuçlanır:

resim açıklamasını buraya girin


Sonuç

Açıkçası, sadece bir işlev için test yapmak hiçbir şey kanıtlamaz. Ayrıca, seçilen işlev örneği için, belleğin bant genişliğinin 10 ^ 5 öğeden daha büyük boyutlar için şişe boynu olduğu akılda tutulmalıdır - bu nedenle bu bölgedeki numba, numexpr ve cython için aynı performansa sahibiz.

Sonunda, nihai cevap fonksiyonun tipine, donanıma, Python dağılımı ve diğer faktörlere bağlıdır. Örnek Anaconda-dağıtım için numpy en işlevleri için Intel'in VML'yi kullanmaktadır ve böylece Numba Mağazasından (o SVML kullandığı sürece bu bkz SO-yazı transandantal fonksiyonlar gibi kolayca için) exp, sin, cosve benzeri - örneğin aşağıdakilere bakın SO-posta .

Yine de bu araştırmadan ve şimdiye kadar edindiğim deneyimlerden yola çıkarak, aşkın işlevlerin dahil olmadığı sürece numba'nın en iyi performansa sahip en kolay araç gibi göründüğünü söyleyebilirim.


Perfplot -package ile çalışma sürelerini çizme :

import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2**k for k in range(0,24)],
    kernels=[
        f, 
        vf,
        ne_f, 
        nb_vf, nb_par_jitf,
        cy_f, cy_par_f,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    )

1
Numba, genellikle Intel VML ile karşılaştırıldığında oldukça karşılaştırılabilir zamanlamalarla sonuçlanan Intel SVML'yi kullanabilir, ancak uygulama sürümde biraz hatalıdır (0.43-0.47). Cy_expsum'unuzla karşılaştırma için stackoverflow.com/a/56939240/4045774 performans grafiği ekledim.
max9111

29
squares = squarer(x)

Diziler üzerindeki aritmetik işlemler, Python düzeyindeki bir döngü veya kavrama için geçerli olacak tüm yorumlayıcı ek yükünü önleyen verimli C düzeyi döngülerle otomatik olarak öğe şeklinde uygulanır.

NumPy dizisine elementwise uygulamak istediğiniz işlevlerin çoğu işe yarayacaktır, ancak bazılarının değiştirilmesi gerekebilir. Örneğin,if öğe şeklinde çalışmaz. Bunları aşağıdaki gibi yapıları kullanacak şekilde dönüştürmek istersiniz numpy.where:

def using_if(x):
    if x < 5:
        return x
    else:
        return x**2

olur

def using_where(x):
    return numpy.where(x < 5, x, x**2)

8

Ben numpy yeni sürümü (1.13 kullanın) inanıyorum sadece numpy dizi skaler tip için yazdığınız fuction geçerek fonksiyonu çağırabilir, otomatik olarak numpy dizi üzerinde her öğeye işlev çağrısı uygular ve size dönecektir başka bir numpy dizisi

>>> import numpy as np
>>> squarer = lambda t: t ** 2
>>> x = np.array([1, 2, 3, 4, 5])
>>> squarer(x)
array([ 1,  4,  9, 16, 25])

3
Bu uzaktan yeni değil - her zaman böyle olmuştur - numpy'nin temel özelliklerinden biridir.
Eric

8
It **her bir elemanı t hesaplama uygulama operatör t. Bu sıradan bir numpy. İçine sarmak lambdafazladan bir şey yapmaz.
hpaulj

Şu anda gösterildiği gibi if ifadeleri ile çalışmaz.
TriHard8

8

Çoğu durumda, numpy.apply_along_axis en iyi seçim olacaktır. Performansı diğer yaklaşımlara kıyasla yaklaşık 100 kat artırır - ve sadece önemsiz test fonksiyonları için değil, aynı zamanda numpy ve scipy'den daha karmaşık fonksiyon kompozisyonları için de.

Yöntemi eklediğimde:

def along_axis(x):
    return np.apply_along_axis(f, 0, x)

perfplot kodu için, aşağıdaki sonuçları elde: resim açıklamasını buraya girin


Çoğu insanın yıllardır bu basit, ölçeklenebilir ve yerleşik no-brainer'ın farkında olmadığı gerçeği konusunda son derece şok oldum ....
Bill Huang

7

Görünüşe göre hiç kimse ufuncnumpy paketinde yerleşik bir fabrika yönteminden bahsetmedi : np.frompyfunctekrar test ettim np.vectorizeve yaklaşık% 20 ~ 30 daha iyi performans gösterdim. Tabii ki reçete edilen C kodu veya hatta numba(test etmedim) gibi iyi performans gösterecektir , ancak daha iyi bir alternatif olabilir.np.vectorize

f = lambda x, y: x * y
f_arr = np.frompyfunc(f, 2, 1)
vf = np.vectorize(f)
arr = np.linspace(0, 1, 10000)

%timeit f_arr(arr, arr) # 307ms
%timeit vf(arr, arr) # 450ms

Daha büyük örnekleri de test ettim ve iyileşme orantılı. Ayrıca belgelere bakın burada


1
Yukarıdaki zamanlama testlerini tekrarladım ve ayrıca yaklaşık% 30'luk bir performans artışı (np.vectorize üzerinde) buldum
Julian - BrainAnnex.org

2

Bahsedildiği gibi bu yazı sadece kullanım jeneratör ifadeler öylesine istiyorum:

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)

2

Yukarıdaki tüm cevaplar iyi karşılaştırılır, ancak eşleme için özel işlev kullanmanız gerekiyorsa ve sahipseniz numpy.ndarrayve dizinin şeklini korumanız gerekir.

Sadece iki tane karşılaştırdım, ama şeklini koruyacak ndarray. Karşılaştırma için 1 milyon girişli dizi kullandım. Burada aynı zamanda numpy içinde yerleşik olan ve büyük bir performans artışı olan kare işlevini kullanıyorum, çünkü bir şeye ihtiyaç olduğu için, seçtiğiniz işlevi kullanabilirsiniz.

import numpy, time
def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([x * x for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((x * x for x in y.reshape(-1)), y.dtype).reshape(y.shape)
    print(time.time() - now)
    now = time.time()
    numpy.square(y)  
    print(time.time() - now)

Çıktı

>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function

burada numpy.fromiterbasit yaklaşımı göz önünde bulundurarak harika işleri açıkça görebilirsiniz ve dahili işlev varsa lütfen bunu kullanın.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.