ネイティブnumpyでは実行できない計算を本当に高速化したい場合は、非慣用的なnumpyコードを記述する必要がある場合があります。
numba
Pythonコードを低レベルのCにコンパイルします。多くのnumpy自体は通常Cと同じくらい高速であるため、問題がnumpyを使用したネイティブのベクトル化に向いていない場合、これはほとんど役に立ちます。これは1つの例です(ここでは、インデックスが隣接してソートされていると想定しましたが、これもサンプルデータに反映されています)。
import numpy as np
import numba
# use the inflated example of roganjosh https://stackoverflow.com/a/58788534
data = [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0, 0, 1, 1, 1, 1, 2, 3, 3]
data = np.array(data * 500) # using arrays is important for numba!
index = np.sort(np.random.randint(0, 30, 4500))
# jit-decorate; original is available as .py_func attribute
@numba.njit('f8[:](f8[:], i8[:])') # explicit signature implies ahead-of-time compile
def diffmedian_jit(data, index):
res = np.empty_like(data)
i_start = 0
for i in range(1, index.size):
if index[i] == index[i_start]:
continue
# here: i is the first _next_ index
inds = slice(i_start, i) # i_start:i slice
res[inds] = data[inds] - np.median(data[inds])
i_start = i
# also fix last label
res[i_start:] = data[i_start:] - np.median(data[i_start:])
return res
そして、IPythonの%timeit
魔法を使用したいくつかのタイミングを次に示します。
>>> %timeit diffmedian_jit.py_func(data, index) # non-jitted function
... %timeit diffmedian_jit(data, index) # jitted function
...
4.27 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
65.2 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
問題の更新されたサンプルデータを使用すると、これらの数値(つまり、Python関数のランタイムとJIT加速機能のランタイムの比較)は次のようになります。
>>> %timeit diffmedian_jit.py_func(data, groups)
... %timeit diffmedian_jit(data, groups)
2.45 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
93.6 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
これは、加速されたコードを使用すると、小さなケースでは65倍のスピードアップ、大きなケースでは26倍のスピードアップになります(もちろん、ルーピーな低速コードと比較して)。もう1つの利点は、(ネイティブnumpyを使用した通常のベクトル化とは異なり)この速度を実現するために追加のメモリを必要としないことです。すべてが最適化され、コンパイルされた低レベルコードが実行されることになります。
上記の関数は、numpy int配列がint64
デフォルトであると想定していますが、これは実際にはWindowsでは当てはまりません。したがって、代替手段は、への呼び出しから署名を削除してnumba.njit
、適切なジャストインタイムコンパイルをトリガーすることです。しかし、これは最初の実行中に関数がコンパイルされ、タイミング結果に干渉する可能性があることを意味します(関数を手動で1回実行して、代表的なデータ型を使用するか、最初のタイミング実行がはるかに遅くなることを受け入れるだけで、無視されます)。これは、シグネチャを指定して事前にコンパイルをトリガーすることで防止しようとしたものとまったく同じです。
とにかく、適切なJITの場合、必要なデコレーターはただ
@numba.njit
def diffmedian_jit(...):
上記のjitコンパイル済み関数のタイミングは、関数がコンパイルされた後にのみ適用されることに注意してください。これは、定義時に(明示的なシグネチャがに渡されるときの熱心なコンパイルでnumba.njit
)、または最初の関数呼び出し中に行われます(遅延コンパイルで、シグネチャが渡されないときnumba.njit
)。関数が1回だけ実行される場合は、コンパイル時間もこのメソッドの速度を考慮する必要があります。通常、コンパイル+実行の合計時間がコンパイルされていないランタイムよりも短い場合にのみ、関数をコンパイルする価値があります(これは、ネイティブpython関数が非常に遅い上記の場合に実際に当てはまります)。これは主に、コンパイルした関数を何度も呼び出すときに発生します。
MAX9111はコメントで述べたように、のいずれかの重要な特徴は、numba
あるcache
キーワードにjit
。cache=True
to numba.jit
を渡すと、コンパイルされた関数がディスクに保存されるため、指定されたpythonモジュールの次回の実行時に、関数は再コンパイルされるのではなく、そこから読み込まれます。これにより、長期的にランタイムを節約できます。
scipy.ndimage.median
リンクされた回答で提案を時間調整しましたか?ラベルごとに同じ数の要素が必要だとは思えません。または私は何かを逃したのですか?