Bir NumPy dizisinin her hücresinde bir işlevin verimli değerlendirilmesi


124

Bir Verilen NumPy dizisi A , uygulamak için en hızlı / en verimli yolu nedir aynı işlevi f için, her hücrede?

  1. Biz atayacağı varsayalım A (i, j) f (A (i, j)) .

  2. F fonksiyonunun ikili bir çıktısı yoktur, bu nedenle maskeleme (ing) işlemleri yardımcı olmaz.

"Açık" çift döngü yinelemesi (her hücrede) en uygun çözüm müdür?


Yanıtlar:


165

İşlevi sadece vektörleştirebilir ve ardından her ihtiyacınız olduğunda doğrudan bir Numpy dizisine uygulayabilirsiniz:

import numpy as np

def f(x):
    return x * x + 3 * x - 2 if x > 0 else x * 5 + 8

f = np.vectorize(f)  # or use a different name if you want to keep the original f

result_array = f(A)  # if A is your Numpy array

Vektörize ederken doğrudan açık bir çıktı türü belirtmek muhtemelen daha iyidir:

f = np.vectorize(f, otypes=[np.float])

19
Korkarım ki vektörize edilmiş fonksiyon, "manuel" çift döngü yinelemesinden ve tüm dizi elemanlarında atamadan daha hızlı olamaz. Özellikle, sonucu yeni oluşturulan bir değişkene (ve doğrudan ilk girdiye değil) depoladığı için . Cevabınız için çok teşekkürler :)
Peter

1
@Peter: Ah, şimdi orijinal sorunuzda sonucu eski diziye geri atamaktan bahsetmişsiniz. Üzgünüm, ilk okurken bunu kaçırdım. Evet, bu durumda çift döngü daha hızlı olmalı. Ancak dizinin düzleştirilmiş görünümünde tek bir döngü denediniz mi? Bu biraz daha hızlı olabilir , çünkü biraz döngü yükünden tasarruf edersiniz ve Numpy'nin her yinelemede bir daha az çarpma ve toplama (veri ofsetini hesaplamak için) yapması gerekir. Ayrıca, isteğe bağlı olarak boyutlandırılmış diziler için çalışır. Çok küçük dizilerde daha yavaş olabilir tho.
blubberdiblub

45
vectorizeİşlev açıklamasında verilen uyarıya dikkat edin : Vektörize işlevi, performans için değil, temel olarak kolaylık sağlamak için sağlanmıştır. Gerçekleştirme esasen bir for döngüsüdür. Yani bu muhtemelen süreci hiç hızlandırmayacaktır.
Gabriel

vectorizeİade türünün nasıl belirlendiğine dikkat edin. Böcekler üretti. frompyfuncbiraz daha hızlıdır, ancak bir dtype nesne dizisi döndürür. Satırları veya sütunları değil, her ikisi de skalar besler.
hpaulj

1
@Gabriel Sadece np.vectorize(RK45 kullanan) fonksiyonuma atmak bana ~ 20 kat hız veriyor.
Suuuehgi



0

Daha iyi bir çözüm bulduğuma inanıyorum. İşlevi , başlık altında paralel hesaplamayı uygulayabilen python evrensel işlevine değiştirme fikri ( belgelere bakın ).

Kişi, ufunckesinlikle daha verimli olan C ile veya np.frompyfuncyerleşik fabrika yöntemi olan çağırarak kendi özelleştirilmişini yazabilir . Testten sonra bu, şunlardan daha etkilidir 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 f_arr(arr, arr) # 450ms

Daha büyük örnekleri de test ettim ve gelişme orantılı. Diğer yöntemlerin performanslarının karşılaştırılması için bu gönderiye bakın


0

2d dizisi (veya nd dizisi) C- veya F-bitişik olduğunda, bir işlevi bir 2d dizisine eşleme görevi pratik olarak bir işlevi 1d dizisine eşleme göreviyle aynıdır - biz sadece bu şekilde görüntülemek zorunda, örneğin aracılığıyla np.ravel(A,'K') .

1d dizisi için olası çözüm, örneğin burada tartışılmıştır .

Bununla birlikte, 2d dizisinin belleği bitişik olmadığında, durum biraz daha karmaşıktır, çünkü eksen yanlış sırada işlenirse olası önbellek kayıplarından kaçınmak ister.

Numpy, eksenleri mümkün olan en iyi sırayla işlemek için halihazırda bir makineye sahip. Bu makineyi kullanmanın bir yolu np.vectorize. Bununla birlikte, numpy'nin dokümantasyonu np.vectorize, "performans için değil, öncelikli olarak kolaylık sağlamak için sağlandığını" belirtir - yavaş bir python işlevi, tüm ilişkili ek yük ile yavaş bir python işlevi olarak kalır! Diğer bir sorun da bellek tüketimidir - örneğin bu SO-postasına bakın .

Kişi bir C işlevinin performansına sahip olmak ama numpy'nin makinesini kullanmak istediğinde, ufunc'lar oluşturmak için numba kullanmak iyi bir çözümdür, örneğin:

# 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

Kolayca atar, np.vectorizeancak aynı işlev numpy-array çarpma / toplama olarak gerçekleştirildiğinde, yani

# 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"

Zaman ölçüm kodu için bu cevabın ekine bakınız:

görüntü açıklamasını buraya girin

Numba'nın sürümü (yeşil), np.vectorizeşaşırtıcı olmayan python işlevinden (yani ) yaklaşık 100 kat daha hızlıdır . Ama aynı zamanda numpy işlevselliğinden yaklaşık 10 kat daha hızlıdır, çünkü numbas sürümü ara dizilere ihtiyaç duymaz ve bu nedenle önbelleği daha verimli kullanır.


Numba'nın ufunc yaklaşımı, kullanılabilirlik ve performans arasında iyi bir değiş tokuş olsa da, yine de yapabileceğimizin en iyisi değil. Yine de herhangi bir görev için en iyi sihirli değnek veya yaklaşım yoktur - sınırlamanın ne olduğunu ve nasıl azaltılabileceğini anlamak gerekir.

Örneğin, transandantal fonksiyonlar için (örneğin exp, sin, cos) numba üzerinde herhangi bir avantaja sağlamaz numpy en np.exp(yaratılmış hiçbir geçici diziler vardır - hız-up ana kaynağıdır). Ancak Anaconda kurulumum, 8192'den büyük vektörler için Intel'in VML'sini kullanıyor - bellek bitişik değilse bunu yapamaz. Bu nedenle, Intel'in VML'sini kullanabilmek için öğeleri bitişik bir belleğe kopyalamak daha iyi olabilir:

import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
    return np.exp(x)

def np_copy_exp(x):
    copy = np.ravel(x, 'K')
    return np.exp(copy).reshape(x.shape) 

Karşılaştırmanın adil olması için, VML'nin paralelleştirmesini kapattım (ekteki koda bakın):

görüntü açıklamasını buraya girin

Görüldüğü gibi, VML devreye girdiğinde, kopyalamanın ek yükü telafi edilenden daha fazladır. Yine de, veriler L3 önbelleği için çok büyük hale geldiğinde, görev bir kez daha bellek bant genişliğine bağlı hale geldiğinden avantaj minimumdur.

Öte yandan numba, bu yazıda açıklandığı gibi Intel'in SVML'sini de kullanabilir :

from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')

import numba as nb

@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
    return np.exp(x)

ve paralelleştirme verimiyle VML kullanma:

görüntü açıklamasını buraya girin

numba'nın sürümünün daha az ek yükü vardır, ancak bazı boyutlar için VML, ek kopyalama ek yüküne rağmen SVML'yi yener - bu, numba'nın ufuncları paralelleştirilmediğinden biraz sürpriz değildir.


Listeler:

A. polinom fonksiyonunun karşılaştırılması:

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

B. karşılaştırması exp:

import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        nb_vexp, 
        np.exp,
        np_copy_exp,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )

0

Yukarıdaki yanıtların tümü iyi bir şekilde karşılaştırılır, ancak haritalama için özel işlev kullanmanız gerekiyorsa ve varsa numpy.ndarrayve dizinin şeklini korumanız gerekiyorsa.

Sadece ikisini karşılaştırdım, ancak şeklini koruyacak ndarray. Karşılaştırma için 1 milyon girişli diziyi kullandım. Burada kare işlevini kullanıyorum. N boyutlu dizi için genel durumu sunuyorum. İki boyutlu için sadece iter2D yapın.

import numpy, time

def A(e):
    return e * e

def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((A(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.fromiterkullanıcı kare işlevini açıkça görebilirsiniz , seçiminizden herhangi birini kullanın. Eğer bağlıdır işlev Eğer i, j o yinelerler gibi dizinin boyutuna, dizinin endeksleri ise for ind in range(arr.size), kullanım numpy.unravel_indexalmak için i, j, ..dizi sizin 1D indeksi ve şekline göre numpy.unravel_index

Bu yanıt, buradaki diğer soruya verdiğim yanıttan ilham almıştır.

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.