web-dev-qa-db-ja.com

M行N列の形状のスライディングウィンドウnumpy.ndarray

私は形状のnumpy配列を持っています(6,2)

[[00,01],
 [10,11],
 [20,21],
 [30,31],
 [40,41],
 [50,51]]

ステップサイズ1のスライドウィンドウが必要で、ウィンドウサイズ3は次のようになります。

[[00,01,10,11,20,21],
 [10,11,20,21,30,31],
 [20,21,30,31,40,41],
 [30,31,40,41,50,51]]

私はnumpyソリューションを探しています。ソリューションが、ウィンドウサイズとステップサイズだけでなく、元の配列の形状をパラメーター化できる場合、それは素晴らしいことです。

この関連する答えを見つけました 効率的な移動平均フィルターにストライドを使用 しかし、そこでステップサイズを指定する方法と、3Dから連続2D配列にウィンドウを折りたたむ方法がわかりません。また、これは Pythonでのローリングまたはスライディングウィンドウイテレータ ですが、Pythonであり、それがどれほど効率的かはわかりません。また、要素をサポートしますが、それらを結合しません。最後に、各要素に複数の機能がある場合は一緒に。

28
siamii
In [1]: import numpy as np

In [2]: a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])

In [3]: w = np.hstack((a[:-2],a[1:-1],a[2:]))

In [4]: w
Out[4]: 
array([[ 0,  1, 10, 11, 20, 21],
       [10, 11, 20, 21, 30, 31],
       [20, 21, 30, 31, 40, 41],
       [30, 31, 40, 41, 50, 51]])

次のように関数としてこれを書くことができます。

def window_stack(a, stepsize=1, width=3):
    n = a.shape[0]
    return np.hstack( a[i:1+n+i-width:stepsize] for i in range(0,width) )

a.ndim = 2である限り、これは実際には元の配列の形状に依存しません。インタラクティブバージョンではどちらの長さも使用しないことに注意してください。形状の2番目の次元は無関係です。各行は好きなだけ長くすることができます。 @Jaimeの提案のおかげで、形状をまったくチェックせずに実行できます。

def window_stack(a, stepsize=1, width=3):
    return np.hstack( a[i:1+i-width or None:stepsize] for i in range(0,width) )
26
askewchan

ファンシーインデックスを使用して、numpyでベクトル化されたスライディングウィンドウを実行できます。

_>>> import numpy as np

>>> a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])

>>> a
array([[ 0,  1],
       [10, 11],
       [20, 21],                      #define our 2d numpy array
       [30, 31],
       [40, 41],
       [50, 51]])

>>> a = a.flatten()

>>> a
array([ 0,  1, 10, 11, 20, 21, 30, 31, 40, 41, 50, 51])    #flattened numpy array

>>> indexer = np.arange(6)[None, :] + 2*np.arange(4)[:, None]

>>> indexer
array([[ 0,  1,  2,  3,  4,  5],
       [ 2,  3,  4,  5,  6,  7],            #sliding window indices
       [ 4,  5,  6,  7,  8,  9],
       [ 6,  7,  8,  9, 10, 11]])

>>> a[indexer]
array([[ 0,  1, 10, 11, 20, 21],
       [10, 11, 20, 21, 30, 31],            #values of a over sliding window
       [20, 21, 30, 31, 40, 41],
       [30, 31, 40, 41, 50, 51]])

>>> np.sum(a[indexer], axis=1)
array([ 63, 123, 183, 243])         #sum of values in 'a' under the sliding window.
_

このコードが実行していることの説明。

np.arange(6)[None, :]は0〜6の行ベクトルを作成し、np.arange(4)[:, None]は0〜4の列ベクトルを作成します。これにより、各行(6つ)がウィンドウを表す4x6マトリックスが生成されます。行の数(4行)はウィンドウの数を表します。 2の倍数は、スライディングウィンドウを一度に2ユニットずつスライドさせます。これは、各タプルの上をスライドするために必要です。 numpy配列スライシングを使用すると、スライディングウィンドウを平坦化されたnumpy配列に渡し、sumのようにそれらを集計できます。

31
user42541

解決策は

np.lib.stride_tricks.as_strided(a, shape=(4,6), strides=(8,4))

ポインタ/アドレスの観点から考え始めると、ストライドの使用は直感的です。

as_strided()メソッドには3つの引数があります。

  1. データ
  2. 形状
  3. 歩む

dataは、操作対象の配列です。

as_strided()を使用してスライディングウィンドウ関数を実装するには、事前に出力の形状を計算する必要があります。質問では、(4,6)は出力の形状です。寸法が正しくない場合、ガベージ値を読み取ることになります。これは、ポインターを数バイトだけ移動してデータにアクセスしているためです(データ型に依存)。

ストライドの正しい値を決定することは、期待される結果を得るために不可欠です。歩幅を計算する前に、_arr.strides[-1]_を使用して各要素が占有しているメモリを見つけます。この例では、1つの要素が占有するメモリは4バイトです。 Numpy配列は、行優先方式で作成されます。次の行の最初の要素は、現在の行の最後の要素のすぐ隣にあります。

例:0、1 | 10、11 | ...

10は1のすぐ隣です。

2D配列が1Dに再形成されたと想像してください(これは、データが行優先形式で保存されるため許容されます)。出力の各行の最初の要素は、1D配列の奇数インデックス要素です。 0、10、20、30、..

したがって、0から10、10から20などに移動するために必要なメモリ内のステップ数は2 *要素のmemサイズです。各行には2 * 4バイト= 8のストライドがあります。出力の特定の行では、仮想1D配列内のすべての要素が互いに隣接しています。行の次の要素を取得するには、要素のサイズに等しいストライドを1つ実行します。列ストライドの値は4バイトです。

したがって、strides=(8,4)

別の説明:出力の形状は(4,6)です。列のストライド_4_。したがって、最初の行の要素はインデックス_0_で始まり、それぞれ4バイトの間隔で6つの要素があります。最初の行が収集された後、2番目の行は現在の行の開始から8バイト離れて開始します。 3番目の行は、2番目の行の開始点から8バイト離れた位置から始まります。

形状は、必要な行と列の数を決定します。ストライドは、行を開始して列要素を収集するためのメモリステップを定義します

21
pbskumar

_more_itertools.windowed_ を使用すると、短いリストを理解できます。1

Given

_import numpy as np
import more_itertools as mit


a = [["00","01"],
     ["10","11"],
     ["20","21"],
     ["30","31"],
     ["40","41"],
     ["50","51"]]

b = np.array(a)
_

コード

_np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
_

または

_np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
_

または

_np.array(list(mit.windowed(b.ravel(), n=6)))
_

出力

_array([['00', '01', '10', '11', '20', '21'],
       ['10', '11', '20', '21', '30', '31'],
       ['20', '21', '30', '31', '40', '41'],
       ['30', '31', '40', '41', '50', '51']], 
      dtype='<U2')
_

サイズ_n=3_のスライディングウィンドウが作成され、フラット化されます。デフォルトのステップサイズはmore_itertools.windowed(..., step=1)であることに注意してください。


パフォーマンス

配列として、受け入れられた答えは最速です。

_%timeit np.hstack((a[:-2], a[1:-1], a[2:]))
# 37.5 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.hstack((b[:-2], b[1:-1], b[2:]))
# 12.9 µs ± 166 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
# 23.2 µs ± 1.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
# 21.2 µs ± 999 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array(list(mit.windowed(b.ravel(), n=6)))
# 43.4 µs ± 374 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
_

itertool recipes および多くの有用なツールを実装するサードパーティライブラリ。

7
pylang

これは純粋なPython実装です:

def sliding_window(arr, window=3):
    i = iter(arr)
    a = []
    for e in range(0, window): a.append(next(i))
    yield a
    for e in i:
        a = a[1:] + [e]
        yield a

例:

# flatten array
flatten = lambda l: [item for sublist in l for item in sublist]

a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
w = sliding_window(a, width=3)
print( list(map(flatten,w)) )

[[0, 1, 10, 11, 20, 21], [10, 11, 20, 21, 30, 31], [20, 21, 30, 31, 40, 41], [30, 31, 40, 41, 50, 51]]

ベンチマーク

import timeit
def benchmark():
  a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
  sliding_window(a, width=3)

times = timeit.Timer(benchmark).repeat(3, number=1000)
time_taken = min(times) / 1000
print(time_taken)

1.0944640007437556e-06
0
loretoparisi