5

同じ長さの 2 つの配列があり、1 つはデータを保持し、もう 1 つは結果を保持しますが、最初はゼロに設定されています。

a = numpy.array([1, 0, 0, 1, 0, 1, 0, 0, 1, 1])
b = numpy.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

a の 3 つの隣接する要素のすべての可能なサブセットの合計を計算したいと思います。合計が 0 または 1 の場合、b の対応する 3 つの要素は変更されません。合計が 1 を超える場合にのみ、b の対応する 3 つの要素が 1 に設定されるため、計算後に b は次のようになります。

array([0, 0, 0, 1, 1, 1, 0, 1, 1, 1])

単純なループでこれを実現します。

for x in range(len(a)-2):
    if a[x:x+3].sum() > 1:
        b[x:x+3] = 1

この後、b は目的の形式になります。

大量のデータに対してこれを行う必要があるため、速度が問題になります。上記の操作をNumPyで実行するためのより高速な方法はありますか?

(これは畳み込みに似ていることは理解していますが、まったく同じではありません)。

4

3 に答える 3

6

畳み込みから始めて、1 を超える値を選択し、最後に「拡張」を使用できます。

b = numpy.convolve(a, [1, 1, 1], mode="same") > 1
b = b | numpy.r_[0, b[:-1]] | numpy.r_[b[1:], 0]

これはPythonループを回避するため、あなたのアプローチよりも高速になるはずですが、タイミングは取りませんでした.

別の方法は、拡張するために 2 番目の畳み込みを使用することです。

kernel = [1, 1, 1]
b = numpy.convolve(a, kernel, mode="same") > 1
b = numpy.convolve(b, kernel, mode="same") > 0

SciPy を利用できる場合、拡張のさらに別のオプションは次のとおりです。

b = numpy.convolve(a, [1, 1, 1], mode="same") > 1
b = scipy.ndimage.morphology.binary_dilation(b)

編集:いくつかのタイミングを実行することで、このソリューションが大規模な配列で最も高速であるように思われることがわかりました:

b = numpy.convolve(a, kernel) > 1
b[:-1] |= b[1:]  # Shift and "smearing" to the *left* (smearing with b[1:] |= b[:-1] does not work)
b[:-1] |= b[1:]  # … and again!
b = b[:-2]

100 万個のエントリの配列の場合、私のマシンでの元のアプローチよりも 200 倍以上高速でした。コメントで EOL が指摘しているように、このソリューションは NumPy の実装の詳細に依存するため、少し脆弱であると見なされる可能性があります。

于 2012-04-02T11:46:23.993 に答える
2

「畳み込み」の合計は、次の方法で効率的に計算できます。

>>> a0 = a[:-2]
>>> a1 = a[1:-1]
>>> a2 = a[2:]
>>> a_large_sum = a0 + a1 + a2 > 1

b次に、「隣接する3つの値の少なくとも1つがTrueである」という意味の何かを記述することで、更新を効率的に行うことができます。最初に、配列を同じ数の要素にa_large_sum拡張します(右、左、右、そして左へ):a_large_suma

>>> a_large_sum_0 = np.hstack([a_large_sum, [False, False]])
>>> a_large_sum_1 = np.hstack([[False], a_large_sum, [False]])
>>> a_large_sum_2 = np.hstack([[False, False], a_large_sum])

次にb、効率的な方法で次の情報を取得します。

>>> b = a_large_sum_0 | a_large_sum_1 | a_large_sum_2

これにより、NumPyの内部高速ループを活用することで、非常に効率的な方法で得られる結果が得られます。

PS:このアプローチは、基本的にSvenの最初のソリューションと同じですが、Svenのエレガントなコードよりもはるかに歩行者です。ただし、同じくらい高速です。Svenの2番目のソリューション(double convolve())はさらにエレガントで、2倍高速です。

于 2012-04-02T12:02:31.317 に答える
1

NumPy のstride_tricks. Sven のタイミング設定 (Sven の回答のリンクを参照) を使用すると、(非常に) 大きい配列の場合、これは必要なことを行うための高速な方法でもあることがわかりました (つまり、 の定義を使用a):

shape = (len(a)-2,3)
strides = a.strides+a.strides
a_strided = numpy.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
b = np.r_[numpy.sum(a_strided, axis=-1) > 1, False, False]
b[2:] |= b[1:-1] | b[:-2]

編集後 (以下のコメントを参照)、もはや最速の方法ではありません。

これにより、元のアレイに特別なストライド ビューが作成されます。のデータaはコピーされませんが、単に新しい方法で表示されます。基本的には、合計するサブ配列 (合計する 3 つの要素) が最後のインデックスに含まれる新しい配列を作成します。このようにして、最後のコマンドで最後に簡単に合計できます。

したがって、この新しい形状の最後の要素は である必要が3あり、最初の要素は古い長さaから 2 を引いたものになります (合計できるのは-2nd 要素までだけだからです)。

ストライド リストには、新しい配列a_stridedが形状の各次元の次の要素に到達するために必要なストライドがバイト単位で含まれています。これらを等しく設定すると、と の両方が になるa_strided[0,1]ことを意味します。これは、まさに私たちが望んでいることです。通常の配列では、これは当てはまりません (最初のストライドは、「最初の次元のサイズに配列の最初の次元の長さ (= shape[0])」を掛けたもの) になりますが、この場合は次のことができます。うまく活用してください。a_strided[1,0]a[1]

これをすべてうまく説明したかどうかはわかりませんが、a_strided を出力するだけで、結果がどうなるか、操作がいかに簡単かがわかります。

于 2012-04-03T20:22:25.620 に答える