web-dev-qa-db-ja.com

NumPy配列のスライディングウィンドウの最大値

指定されたnumpy配列を移動するウィンドウのすべてのmax() esを保持する配列を作成したいと思います。これが混乱して聞こえたらすみません。例を挙げましょう。入力:

_[ 6,4,8,7,1,4,3,5,7,2,4,6,2,1,3,5,6,3,4,7,1,9,4,3,2 ]
_

ウィンドウ幅が5の出力は次のようになります。

_[     8,8,8,7,7,7,7,7,7,6,6,6,6,6,6,7,7,9,9,9,9     ]
_

各数値は、入力配列の幅5のサブ配列の最大値になります。

_[ 6,4,8,7,1,4,3,5,7,2,4,6,2,1,3,5,6,3,4,7,1,9,4,3,2 ]
  \       /                 \       /
   \     /                   \     /
    \   /                     \   /
     \ /                       \ /
[     8,8,8,7,7,7,7,7,7,6,6,6,6,6,6,7,7,9,9,9,9     ]
_

私はこれを行うすぐに使える関数をnumpy内で見つけませんでした(しかし、それがあったとしても驚かないでしょう;私は常にnumpyの開発者が考えている用語で考えているわけではありません)。入力のシフトされた2Dバージョンを作成することを検討しました。

_[ [ 6,4,8,7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1 ]
  [ 4,8,7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9 ]
  [ 8,7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9,4 ]
  [ 7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9,4,3 ]
  [ 1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9,4,3,2 ] ]
_

次に、これにnp.max(input, 0)を適用すると、結果が得られます。しかし、私の配列ではウィンドウとウィンドウの幅の両方が大きくなる可能性があるため(> 1000000エントリと> 100000のウィンドウ幅)、これは私の場合は効率的ではありません。データは、ウィンドウの幅の要因によって多かれ少なかれ拡大されます。

また、何らかの方法でnp.convolve()を使用することを検討しましたが、それを使用して目標を達成する方法を理解できませんでした。

これを効率的に行う方法はありますか?

11
Alfe

PandasにはSeriesとDataFramesの両方のローリングメソッドがあり、ここで使用できます。

import pandas as pd

lst = [6,4,8,7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9,4,3,2]
lst1 = pd.Series(lst).rolling(5).max().dropna().tolist()

# [8.0, 8.0, 8.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 9.0, 9.0, 9.0, 9.0]

一貫性を保つために、lst1の各要素をintに強制変換できます。

[int(x) for x in lst1]

# [8, 8, 8, 7, 7, 8, 8, 8, 8, 8, 6, 6, 6, 6, 6, 7, 7, 9, 9, 9, 9]
6
Abdou

アプローチ#1:1D Scipyからの最大フィルター -

from scipy.ndimage.filters import maximum_filter1d

def max_filter1d_valid(a, W):
    hW = (W-1)//2 # Half window size
    return maximum_filter1d(a,size=W)[hW:-hW]

アプローチ#2:stridesstrided_app を作成するには2Dバージョンを配列のビューとしてかなり効率的にシフトしました。これにより、後で2番目の軸に沿ってカスタムの縮小操作を使用できるようになります-

def max_filter1d_valid_strided(a, W):
    return strided_app(a, W, S=1).max(axis=1)

ランタイムテスト-

In [55]: a = np.random.randint(0,10,(10000))

# @Abdou's solution using pandas rolling
In [56]: %timeit pd.Series(a).rolling(5).max().dropna().tolist()
1000 loops, best of 3: 999 µs per loop

In [57]: %timeit max_filter1d_valid(a, W=5)
    ...: %timeit max_filter1d_valid_strided(a, W=5)
    ...: 
10000 loops, best of 3: 90.5 µs per loop
10000 loops, best of 3: 87.9 µs per loop
7
Divakar

私はいくつかのバリアントを試しましたが、Pandasバージョンをこのパフォーマンスレースの勝者として宣言します。バイナリツリー(純粋なPythonで実装)を使用して、任意の部分範囲(ソースはオンデマンドで利用可能)。私が思いついた最良のアルゴリズムは、リングバッファを使用したプレーンなローリングウィンドウでした。その最大値は、現在の最大値がこの反復でドロップされた場合にのみ完全に再計算する必要がありました。古いライブラリと比較して、この純粋なPythonの実装は他のライブラリよりも高速でした。

結局、問題のライブラリのバージョンは非常に関連性が高いことがわかりました。私が主にまだ使用していたかなり古いバージョンは、最新バージョンよりもかなり低速でした。以下は、100万の数値で、100kサイズのウィンドウでRollingMaxされた数値です。

_         old (slow HW)           new (better HW)
scipy:   0.9.0:  21.2987391949   0.13.3:  11.5804400444
pandas:  0.7.0:  13.5896410942   0.18.1:   0.0551438331604
numpy:   1.6.1:   1.17417216301  1.8.2:    0.537392139435
_

リングバッファを使用した純粋なnumpyバージョンの実装は次のとおりです。

_def rollingMax(a, window):
  def eachValue():
    w = a[:window].copy()
    m = w.max()
    yield m
    i = 0
    j = window
    while j < len(a):
      oldValue = w[i]
      newValue = w[i] = a[j]
      if newValue > m:
        m = newValue
      Elif oldValue == m:
        m = w.max()
      yield m
      i = (i + 1) % window
      j += 1
  return np.array(list(eachValue()))
_

私の入力では、すべての方向に多くのピークを持つオーディオデータを処理しているため、これはうまく機能します。絶えず減少する信号をその中に入れた場合(例:-np.arange(10000000))、最悪のケースが発生します(そのような場合は、入力と出力を逆にする必要があります)。

古いライブラリを備えたマシンで誰かがこのタスクを実行したい場合に備えて、これを含めます。

1
Alfe

まず、説明の最初にある初期入力配列の10番目の要素は8に等しいため、説明に誤りがあると思います。以下では、ウィンドウを適用すると2になります。

それを修正した後、私はあなたが望むことをするコードは次のとおりだと思います:

import numpy as np
a=np.array([ 6,4,8,7,1,4,3,5,7,8,4,6,2,1,3,5,6,3,4,7,1,9,4,3,2 ])
window=5
for i in range(0,len(a)-window,1): 
    b[i] = np.amax(a[i:i+window])

この方法は、シフトした2Dバージョンの入力を作成するよりも優れていると思います。そのようなバージョンを作成すると、元の入力配列を使用するよりもはるかに多くのメモリを使用する必要があるため、入力が大きい場合にメモリが不足する可能性があります。

1
Guido

株価などの2つのディメンションデータがあり、ローリングマックスなどを取得したい場合、これは機能します。反復を使用せずに計算する。

n = 5  # size of rolling window

data_expanded = np.expand_dims(data, 1)
data_shift = [np.roll(data_expanded, shift=-i, axis=2) for i in range(n)]
data_shift = np.concatenate(data_shift, axis=1)

data_max = np.max(data_shift, axis=1)  # max, mean, std...
0
Kim BoChan