2016-05-27 12 views
1

私は大きな2D NumPy配列を持っています.5M行と10個の列としましょう。 Numba @jitclassを使用して実装されたステートフルなロジックに従っていくつかの列を作成したいと思います。新しい列を50個作成するとします。この考え方は、Numba @jit関数の10列のすべての行を繰り返し処理することで、各行に対して、それぞれ50個の「フィルタ」を適用してそれぞれ1つの新しいセルを生成します。だから、:Numbaを使って各行に複数の関数を適用する

Source1..Source10 Derived1..Derived50 
[array of 10 inputs] [array of 50 outputs] 
    ... 5 million rows like this ... 

問題は、彼らが均質ではないので、私は、@jit(nopython=True)関数に私の「フィルタ」のリストやタプルを渡すことはできません、次のとおりです。上記

@numba.jit(nopython=True) 
def calc_derived(source, derived, filters): 
    for srcidx, src in enumerate(source): 
     for filtidx, filt in enumerate(filters): # doesn't work 
      derived[srcidx,filtidx] = filt.transform(src) 

はしていませんfiltersはさまざまなクラスの束であるために動作します。私が知る限り、それらを共通の基底クラスから派生させても十分ではありません。

ループの順番を入れ替える可能性が残っていますが、@jit関数の外にある50個のフィルタにループを適用しても、ソースデータセット全体が1回ではなく50回ロードされることになります。非常に無駄です。

Numbaの「同種リストのみ」の要件を回避するテクニックはありますか?

+0

私はこれを素早く見ていて、問題の内容をうまく解決できませんでした。 'source'と' filters'の列挙は、10要素または50要素しかないので、すばやくすべきです。しかし、 'src'は5M要素なので、実際の作業は' filt.transform'(正しく理解すれば)によって行われます。したがって、私は外側のループをどのように変更したかを説明するテストケースを思いつきました.- filt.transformは最適化されています... – DavidW

答えて

2

あなたは、もともとの行をループし、単一の機能で、このことについて尋ねた、とするフィルタのリストを適用します各行。このアプローチの課題は、numbaが各関数の入出力タイプを知っているか、推測できる必要があることです。私はこの状況でnumbaの要件を満たす方法を認識していません(それは存在しないとは限りません)。これを行う方法があれば、もっと良い解決策になるかもしれません(私はそれが何であるか知りたいです)。

代わりに、行をループするコードをフィルタ自体に移動することもできます。フィルタはnumba関数なので、速度を維持する必要があります。フィルタを適用する関数はnumbaを使用する方が長くなります。フィルターのリストをループするだけです。しかし、フィルタの数はデータマトリクスのサイズに比べて小さいので、速度があまりにも厳しくは影響しないことを願っています。この関数はnumbaを使用しないため、 '異種リスト'の問題はもはや問題にはなりません。

このアプローチは、私がそれをテストしたときに機能しました(nopythonモードは問題ありません)。テストケースでは、numba関数として実装されたフィルタは、クラスメソッドとして実装されたフィルタよりも10〜18倍高速でした(numba jitclassesとして実装されていても、何が起こっているのかわかりません)。少しのモジュール性を得るために、フィルタをクロージャとして構築することができます。これにより、異なるパラメータを使用して同様のフィルタを定義できます。

たとえば、ここではパワーの合計を計算するフィルタがあります。行列xが与えられると、フィルタはxの列にわたって作用し、各行に対して出力を与える。これは、複数のフィルタを適用するv[i] = sum(x[i, :] ** power)

# filter constructor 
def sumpow(power): 

    @numba.jit(nopython=True) 
    def run_filter(x): 
     (nrows, ncols) = x.shape 
     result = np.zeros(nrows) 
     for i in range(nrows): 
      for j in range(ncols): 
       result[i] += x[i,j] ** power 
     return result 

    return run_filter 

# define filters 
sum1 = sumpow(1) # sum of elements 
sum2 = sumpow(2) # sum of elements squared 

# apply a single filter 
v = sum2(x) 

関数は次のようになりますベクトルvを返します。各フィルタの出力は、出力の列に積み重ねられます。標準正規分布から引き出さランダムエントリのfloat64 500万行×10列:

def apply_filters(x, filters): 

    result = np.empty((x.shape[0], len(filters))) 

    for (i, f) in enumerate(filters): 
     result[:, i] = f(x) 

    return result 


y = apply_filters(x, [sum1, sum2]) 

タイミング

  • データ行列をもたらします。すべてのメソッドが同じマトリックスを使用してテストされています。
  • フィルタ:上記sum2フィルタは、リスト中の20倍を繰り返す:IPythonの%はtimeit機能を使用して[sum2, sum2, ...]
  • 時限を、すべてのメソッドの3回のランの最高
  • 数値出力は
  • Numbaに合意します(上に示したような)関数フィルタ:2.25s
  • Numba jitclassフィルタ:28.3s
  • 純粋NumPy (ベクトル化OPS、無ループを使用):8.64s

私はNumbaは、より複雑なフィルタのためにnumpyのに比べて得るかもしれないと想像します。

+0

これは良い、有益な答えです。私はキャッシュに収まらないので、データセットを複数回反復することを避けようとしていたので、あなたのソリューションではメインメモリからN回リロードする必要がありましたが、残念です。しかし、これはPythonを使用した代替方法よりもまだ高速です。 –

0

同種のリストを取得するには、すべてのフィルタの関数transformのリストを作成できます。この場合、すべてのリスト要素のタイプはmethodになります。

# filters = list of filters 
transforms = [x.transform for x in filters] 

その後calc_derived()代わりのfilterstransformsを渡します。

編集:私のシステムで 、numbaはこれを受け入れるように見えますが、唯一の偽nopython =場合

+0

"Edit" 'nopython = True'では動作しません。また、 'nopython = True'を削除した場合、プログラムは行を反復するのに5倍の時間がかかります。だから、残念ながらこれはうまくいきません。 –

+0

@JohnZwinck私は、numbaがnopythonモードを使用しているときは、各関数の入出力タイプを知っているか、または推測できる必要があるというのが私の推測です。それが本当であれば、それは問題へのあらゆるアプローチの制約となります(あるいは、どのように進めるべきかのヒントを与えるかもしれません)。うまくいけばそれの周りに道があるが、私はオフハフトを知らない。 – user20160

関連する問題