NumPy配列のすべてのセルでの関数の効率的な評価


124

与えられたnumpyのの配列A、適用する最速/最も効率的な方法何同じ機能を、Fに、すべてのセル?

  1. A(i、j)f(A(i、j))を割り当てると仮定します。

  2. 関数fにはバイナリ出力がないため、マスク()演算は役に立ちません。

「すべてのセルを通る」「明白な」二重ループ反復は最適なソリューションですか?


回答:


165

関数をベクトル化して、必要になるたびにNumpy配列に直接適用することができます。

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

ベクトル化するときは、明示的な出力タイプを直接指定する方がよいでしょう。

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

19
ベクトル化された関数は、 "手動"の二重ループの反復およびすべての配列要素を介した割り当てよりも速くなることはありません。特に、結果は新しく作成された変数に保存れます(初期入力には直接保存れません)。返信ありがとうございます:)
ピーター

1
@ピーター:ああ、今、元の質問で以前の配列に結果を割り当てることについて言及したことがわかりました。それを最初に読んだときにそれを逃したことを残念に思う。ええ、その場合、二重ループはより高速でなければなりません。しかし、アレイの平坦化されたビューで単一のループも試しましたか?ループのオーバーヘッドを少し節約し、Numpyが各反復で乗算と加算(データオフセットを計算するため)を1つ少なくする必要があるため、これは少し高速になる可能性があります。さらに、任意の次元の配列に対して機能します。非常に小さなアレイでは遅くなるかもしれません。
blubberdiblub

45
vectorize関数の説明にある警告に注意してください。vectorize関数は、パフォーマンスではなく、主に便宜上提供されています。実装は基本的にforループです。したがって、これはプロセスをまったくスピードアップしない可能性が非常に高いです。
ガブリエル

vectorize戻り値の型をどのように決定するかに注意してください。バグが発生しています。 frompyfunc少し高速ですが、dtypeオブジェクト配列を返します。どちらも行や列ではなく、スカラーをフィードします。
hpaulj

1
@Gabriel np.vectorize(RK45を利用する)私の関数を投げるだけで、20倍のスピードアップが得られます
。– Suuuehgi



0

私はもっ​​と良い解決策を見つけたと思います。関数をpythonユニバーサル関数に変更するアイデア(ドキュメントを参照)。これにより、内部で並列計算を実行できます。

ufuncCでカスタマイズした独自のコードを作成することもできますが、これは確かに効率的np.frompyfuncです。テスト後、これは以下よりも効率的です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

大きなサンプルもテストしましたが、改善は比例しています。他の方法のパフォーマンスの比較については、この投稿を参照してください


0

2D配列(またはND配列)がCまたはF隣接である場合、2D配列に関数をマッピングするこのタスクは、1D配列に関数をマッピングするタスクと実質的に同じです。そのように、たとえばを介して表示する必要がありますnp.ravel(A,'K')

1d-arrayの可能な解決策は、例えばここで議論されています

ただし、2D配列のメモリが隣接していない場合は、状況が少し複雑になります。これは、軸が誤った順序で処理された場合に起こり得るキャッシュミスを回避したいためです。

Numpyには、軸を可能な限り最高の順序で処理するための機構がすでに用意されています。この機械を使用する1つの可能性はnp.vectorizeです。しかし、numpyのドキュメントにnp.vectorizeは、「パフォーマンスではなく、主に利便性のために提供されている」と記載されています-遅いPython関数は、関連するオーバーヘッド全体で遅いPython関数のままです!別の問題は、その巨大なメモリ消費です-たとえば、このSO-postを参照してください。

C関数のパフォーマンスを望んでいて、numpyの機構を使用したい場合は、numbaを使用してufuncを作成することをお勧めします。次に例を示します。

# 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

それは簡単に打つことができますnp.vectorizeが、同じ関数がnumpy-arrayの乗算/加算として実行される場合、つまり

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

time-measurement-codeについては、この回答の付録を参照してください。

ここに画像の説明を入力してください

Numbaのバージョン(緑)は、python関数(つまりnp.vectorize)よりも約100倍高速です。これは驚くべきことではありません。しかし、numbasバージョンは中間配列を必要としないため、キャッシュをより効率的に使用するため、numpy機能よりも約10倍高速です。


numbaのufuncアプローチは、ユーザビリティとパフォーマンスの間の適切なトレードオフですが、それでも私たちができる最善ではありません。しかし、特効薬も、どのタスクにも最適なアプローチもありません。制限とは何か、どのように軽減できるかを理解する必要があります。

たとえば、超越関数(たとえば、、)のexp場合sincosnumbaはnumpyに勝る利点はありませんnp.exp(一時的な配列が作成されない-高速化の主なソース)。ただし、私のAnacondaのインストールでは、8192より大きいベクターにIntelのVMLを使用しています。メモリが隣接していない場合は実行できません。したがって、IntelのVMLを使用できるようにするには、要素を連続したメモリにコピーする方がよい場合があります。

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) 

比較を公平にするために、VMLの並列化をオフにしました(付録のコードを参照)。

ここに画像の説明を入力してください

ご覧のように、VMLが開始されると、コピーのオーバーヘッドが補われます。しかし、データがL3キャッシュに対して大きすぎると、タスクが再びメモリ帯域幅に制限されるため、利点は最小限になります。

一方、この投稿で説明されているように、numbaはIntelのSVMLも使用できます。

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)

並列化でVMLを使用すると、次の結果が得られます。

ここに画像の説明を入力してください

numbaのバージョンはオーバーヘッドが少ないですが、サイズによっては、コピーのオーバーヘッドが追加されていても、VMLがSVMLを上回ります。


リスト:

A.多項式関数の比較:

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.の比較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

上記のすべての答えはよく比較されますがnumpy.ndarray、マッピングにカスタム関数を使用する必要があり、があり、配列の形状を保持する必要がある場合。

2つだけ比較しましたが、の形状は保持されndarrayます。比較のために100万エントリの配列を使用しました。ここでは二乗関数を使用しています。n次元配列の一般的なケースを示しています。2次元の場合はiter、2Dを作成します。

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)

出力

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

ここでは、numpy.fromiterユーザーの二乗関数を明確に確認できます。任意の関数を使用してください。関数がi, j 配列のインデックスに依存している場合は、のような配列のサイズで反復し、配列の1Dインデックスと形状に基づいて取得するためfor ind in range(arr.size)に使用しますnumpy.unravel_indexnumpy.unravel_indexi, j, ..

この回答は、ここにある他の質問に対する私の回答に触発されています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.