2016-08-28 9 views
2

最近、numpy配列の反復処理に関するさまざまな手法について多くのことを読んできました。コンセンサスはまったく反復しないようです(たとえば、a comment hereを参照)。あまりにも多くの質問がありますが、 "反復"(反復しない)と以前の値へのアクセスを組み合わせなければならないので、私のケースは少し異なります。複数の配列を繰り返し処理し、現在の要素と前の要素を処理する効率的な方法はありますか?

Xのリスト内にN個(Nは小さく、通常は4個、最大7個まで可能)1D numpy配列float128があり、すべての配列が同じサイズであるとします。あなたに少し洞察を与えるために、これらはPDEの統合によるデータです。各配列は1つの関数を表しています。私はポアンカレセクションを適用したいと思います。残念ながら、アルゴリズムはメモリと時間の両方で効率的でなければなりません。なぜならこれらの配列はそれぞれ〜1Gbであり、ボードには4GbのRAMしかないからです(私はちょうどnumpy配列のmemmapのことを学びました。普通のものの)。

これらの配列のうちの1つは、他の配列を「フィルタリング」するために使用されるので、secaxis = X.pop(idx)から始めます。今度は、(secaxis[i-1] > 0 and secaxis[i] < 0) or (secaxis[i-1] < 0 and secaxis[i] > 0)のインデックスのペアを見つけて、残りの配列に単純な代数変換を適用すると、X(そして結果を保存)する必要があります。言及する価値は、この操作中にデータを無駄にしないでください。

これを行うには複数の方法がありますが、どれも効率的(そして十分にエレガント)ではありません。一つは、あなただけで反復C-ようなアプローチであり、forループ:

import array # better than lists 
res = [ array.array('d') for _ in X ] 
for i in xrange(1,secaxis.size): 
    if condition: # see above 
    co = -secaxis[i-1]/secaxis[i] 
    for j in xrange(N): 
     res[j].append((X[j][i-1] + co*X[j][i])/(1+co)) 

これは明らかに非常に非効率的でないPython的な方法以外にもあります。

もう一つの方法は、numpy.nditerを使用することですが、私はそれが一度に複数のアレイを反復することができますけれども1は、前の値にアクセスする方法をまだ考え出したていない:

# without secaxis = X.pop(idx) 
it = numpy.nditer(X) 
for vec in it: 
    # vec[idx] is current value, how do you get the previous (or next) one? 

第三の可能性は、最初にあります効率的なnumpyスライスで検索されたインデックスを見つけて、それらを一括乗算/加算に使用します。私は今のところこれを好む:

res = [] 
inds, = numpy.where((secaxis[:-1] < 0) * (secaxis[1:] > 0) + 
        (secaxis[:-1] > 0) * (secaxis[1:] < 0)) 
coefs = -secaxis[inds]/secaxis[inds+1] # array of coefficients 
for f in X: # loop is done only N-1 times, that is, 3 to 6 
    res.append((f[inds] + coefs*f[inds+1])/(1+coefs)) 

しかし、これは一見7 + 2 *(N - 1)で行われている渡し、さらに、Iアドレッシングのsecaxis[inds]種類についてはよく分からない(それはスライスし、一般的にされていません最初の方法と同じようにすべての要素をインデックスで見つける必要がありますか?)。

最後に、私はまた、itertoolsを使用してみたのだが、私は関数型プログラミングに精通していないよという事実から生じるかもしれない怪物とあいまいな構造をもたらした:

def filt(x): 
    return (x[0] < 0 and x[1] > 0) or (x[0] > 0 and x[1] < 0) 
import array 
from itertools import izip, tee, ifilter 
res = [ array.array('d') for _ in X ] 
iters = [iter(x) for x in X] # N-1 iterators in a list 
prev, curr = tee(izip(*iters)) # 2 similar iterators, each of which 
           # consists of N-1 iterators 
next(curr, None) # one of them is now for current value 
seciter = tee(iter(secaxis)) 
next(seciter[1], None) 
for x in ifilter(filt, izip(seciter[0], seciter[1], prev, curr)): 
    co = - x[0]/x[1] 
    for r, p, c in zip(res, x[2], x[3]): 
    r.append((p+co*c)/(1+co)) 

だけでなく、このルックス非常に醜い、それはまた、完了するのに非常に多くの時間がかかります。

だから、私は次のような質問している:すべてのこれらのメソッドの

  1. は、3つ目は本当に最高ですか!もしそうなら、最後のものを取り除くために何ができるのですか?
  2. まだ他に優れたものはありますか?
  3. 好奇心から、nditerを使って問題を解決する方法はありますか?
  4. 最後に、numpy配列のmemmapバージョンを使用する方が良いでしょうか、それともおそらく動作が遅くなるのでしょうか?たぶん私はRAMにsecaxisアレイをロードするだけで、ディスク上の他の人を保ち、3番目の方法を使用する必要がありますか?
  5. (ボーナスの質問)等価長の1次元numpy配列のリストは、サイズがあらかじめわかっていない(ただしNは)N .npyのファイルをロードしたものです。 1つの配列を読み取ってから、2次元配列の配列に1つのメモリを割り当て(ここでわずかなメモリオーバヘッド)、その2次元配列に残りの読み取りを行う方が効率的でしょうか?

答えて

3

numpy.where()バージョンが十分に速い場合は、method3()で少し高速化できます。 >の条件が>=に変更できる場合は、method4()も使用できます。

import numpy as np 

a = np.random.randn(100000) 

def method1(a): 
    idx = [] 
    for i in range(1, len(a)): 
     if (a[i-1] > 0 and a[i] < 0) or (a[i-1] < 0 and a[i] > 0): 
      idx.append(i) 
    return idx 

def method2(a): 
    inds, = np.where((a[:-1] < 0) * (a[1:] > 0) + 
         (a[:-1] > 0) * (a[1:] < 0)) 
    return inds + 1 

def method3(a): 
    m = a < 0 
    p = a > 0 
    return np.where((m[:-1] & p[1:]) | (p[:-1] & m[1:]))[0] + 1 

def method4(a): 
    return np.where(np.diff(a >= 0))[0] + 1 

assert np.allclose(method1(a), method2(a)) 
assert np.allclose(method2(a), method3(a)) 
assert np.allclose(method3(a), method4(a)) 

%timeit method1(a) 
%timeit method2(a) 
%timeit method3(a) 
%timeit method4(a) 

%timeit結果:

1 loop, best of 3: 294 ms per loop 
1000 loops, best of 3: 1.52 ms per loop 
1000 loops, best of 3: 1.38 ms per loop 
1000 loops, best of 3: 1.39 ms per loop 
3

私は、より詳細にあなたの記事を読んでする必要がありますが、(前回の反復の質問から)いくつかの一般的な観察を開始します。

Pythonで配列を反復処理する効率的な方法はありませんが、処理が遅くなることがあります。私は反復メカニズム(nditerfor x in A:)とアクション(alist.append(...),x[i+1] += 1)を区別したいと思います。ビッグタイムの消費者は通常、反復メカニズムそのものではなく、何度も実行されるアクションです。

numpyをコンパイルされたコードで反復するのが最も高速です。

xdiff = x[1:] - x[:-1] 

np.nditerがどの速くない

xdiff = np.zeros(x.shape[0]-1) 
for i in range(x.shape[0]: 
    xdiff[i] = x[i+1] - x[i] 

よりもはるかに高速です。

nditerは、コンパイルされたコードの一般的な反復ツールとして推奨されます。しかし、その主な価値は、放送を扱い、いくつかの配列(入力/出力)にわたる反復を調整することにあります。そして、あなたはnditerから最高の速度を得るためにバッファリングとcのコードを使用する必要があります(私は最近のSO質問を調べます)。

https://stackoverflow.com/a/39058906/901925

関連iterationチュートリアルページ(cython例で終わるもの)を勉強せずにnditerを使用しないでください。ただ、経験から判断

=========================

、このアプローチは最速になります。はい、それは多くの回secaxisを反復するつもりですが、それらはすべてコンパイルされたコードで行われ、Pythonのどの繰り返しよりもはるかに高速です。そして、for f in X:の繰り返しはほんの数回です。

res = [] 
inds, = numpy.where((secaxis[:-1] < 0) * (secaxis[1:] > 0) + 
        (secaxis[:-1] > 0) * (secaxis[1:] < 0)) 
coefs = -secaxis[inds]/secaxis[inds+1] # array of coefficients 
for f in X: 
    res.append((f[inds] + coefs*f[inds+1])/(1+coefs)) 

@HYRYwhereステップ速く作るための代替手段を模索しています。しかし、あなたが見ることができるように、違いはそれほど大きくはありません。Xが配列だった場合は他の可能な微調整

inds1 = inds+1 
coefs = -secaxis[inds]/secaxis[inds1] 
coefs1 = coefs+1 
for f in X: 
    res.append((f[inds] + coefs*f[inds1])/coefs1) 

は、resは同様に、配列である可能性があります。

res = (X[:,inds] + coefs*X[:,inds1])/coefs1 

しかし、小さなNのために私はリストresが同じように良いです疑います。配列を必要以上に大きくする必要はありません。微調整は物事を再計算することを避けようとしているだけです。

=================

np.whereのこの使用は、単にnp.nonzeroです。これは実際には配列の2回のパスを行い、一度np.count_nonzeroで戻り値の数を決定し、戻り構造(現在既知の長さの配列のリスト)を作成します。そして、それらの指標を記入する第2のループ。したがって、アクションを簡単に保つならば、複数の繰り返しがうまくいきます。