回答:
関数をベクトル化して、必要になるたびに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])
vectorize
関数の説明にある警告に注意してください。vectorize関数は、パフォーマンスではなく、主に便宜上提供されています。実装は基本的にforループです。したがって、これはプロセスをまったくスピードアップしない可能性が非常に高いです。
vectorize
戻り値の型をどのように決定するかに注意してください。バグが発生しています。 frompyfunc
少し高速ですが、dtypeオブジェクト配列を返します。どちらも行や列ではなく、スカラーをフィードします。
np.vectorize
(RK45を利用する)私の関数を投げるだけで、20倍のスピードアップが得られます
同様の質問は、NumPy配列を適切にマッピングすることです。f()のufuncを見つけることができる場合は、outパラメーターを使用する必要があります。
とを使用している場合は、との間の距離としてfを定義f(A(i,j)) = f(A(j,i))
するscipy.spatial.distance.cdistを使用できます。A(i)
A(j)
私はもっと良い解決策を見つけたと思います。関数をpythonユニバーサル関数に変更するアイデア(ドキュメントを参照)。これにより、内部で並列計算を実行できます。
ufunc
Cでカスタマイズした独自のコードを作成することもできますが、これは確かに効率的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
大きなサンプルもテストしましたが、改善は比例しています。他の方法のパフォーマンスの比較については、この投稿を参照してください
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
場合sin
、cos
numbaは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)',
)
上記のすべての答えはよく比較されますが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_index
i, j, ..