Python NumPy数组中滑动窗口中的最大值

Python NumPy数组中滑动窗口中的最大值,python,performance,numpy,scipy,max,Python,Performance,Numpy,Scipy,Max,我想创建一个数组,其中包含在给定numpy数组中移动的窗口的所有max()。如果这听起来让人困惑,我很抱歉。我举个例子。输入: [ 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

我想创建一个数组,其中包含在给定numpy数组中移动的窗口的所有
max()。如果这听起来让人困惑,我很抱歉。我举个例子。输入:

[ 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(输入,0)
,并得到我的结果。但在我的例子中,这似乎并不有效,因为我的数组和窗口宽度都可能很大(>1000000个条目和>100000个窗口宽度)。数据或多或少会被窗口宽度放大一倍

我还考虑过以某种方式使用
np.convalve()
,但无法找到一种方法来实现我的目标

有什么办法可以有效地做到这一点吗?

方法#1:您可以使用-

方法#2:这里有另一种方法:创建一个
2D
转换版本,作为数组中的视图,非常有效,这应该允许我们在之后沿第二个轴使用任何自定义缩减操作-

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

Pandas对系列和数据帧都有一种滚动方法,在这里可以使用:

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]

首先,我认为你的解释有一个错误,因为你的解释开始时初始输入数组的第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版本要好,因为创建这种版本时,需要使用比使用原始输入数组多得多的内存,因此,如果输入太大,您可能会耗尽内存。

我现在已经尝试了几种变体,并宣布熊猫版是这次性能竞赛的赢家。我尝试了几种变体,甚至使用二叉树(用纯Python实现)快速计算任意子范围的最大值。(来源可按需提供)。我自己想出的最好的算法是使用环形缓冲区的普通滚动窗口;如果在该迭代中删除了当前最大值,则只需完全重新计算该值的最大值;否则它将保持或增加到下一个新值。与旧库相比,这个纯Python实现比其他库更快

最后,我发现有问题的库的版本是高度相关的。我主要还在使用的旧版本比现代版本慢得多。以下是1M数字的数字,最大滚动窗口大小为100k:

         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
以下是使用ringbuffer的纯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()))
def rollingMax(一个窗口):
def eachValue():
w=a[:窗口]。复制()
m=w.max()
产量m
i=0
j=窗口
而jm:
m=新值
elif oldValue==m:
m=w.max()
产量m
i=(i+1)%window
j+=1
返回np.array(list(eachValue()))
对于我的输入来说,这非常有效,因为我处理的音频数据在各个方向都有很多峰值。如果你把一个不断减小的信号放进去(例如,
-np.arange(10000000)
),那么你将经历最坏的情况(在这种情况下,也许你应该反转输入和输出)


如果有人想在一台有旧库的机器上执行此任务,我会将其包括在内。

如果您有二维数据,例如股票价格,并且希望获得滚动最大值或其他任何数据,这将起作用。 不使用迭代进行计算

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...

Numpy 1.20
开始,提供了在元素窗口中滑动/滚动的方法。然后,您可以为其找到最大值的窗口:

from numpy.lib.stride_tricks import sliding_window_view

# values = np.array([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])
np.max(sliding_window_view(values, window_shape = 5), axis = 1)
# array([8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 9, 9, 9, 9])
其中:

  • window\u shape
    是滑动窗口的大小
  • np.max(数组,轴=1)
    查找每个子数组的最大值
滑动的中间结果是:

sliding_window_view(values, window_shape = 5)
# array([[6, 4, 8, 7, 1],
#        [4, 8, 7, 1, 4],
#        [8, 7, 1, 4, 3],
#        ...
#        [7, 1, 9, 4, 3],
#        [1, 9, 4, 3, 2]])

天哪,你说得对!我在写问题的过程中改变了输入,以显示更多的案例。我并不是因此而来的。我现在已经修好了。根据您的建议:我希望避免在输入上出现任何Python编写的循环,因为这总是比使用包的任何功能(如
numpy
scipy
pandas
等)都要慢。如果您认为您的解决方案具有竞争力,请提供timeits。否则:当然,这是直截了当的好办法。它只是没有达到我的性能预期。这听起来非常有希望,将性能与
pandas
解决方案进行比较。不幸的是,对于我正在处理的数组,这会引发一个
值错误:数组太大。
。自己试试看:
a=np.arange(1000000)
np.lib.stride\u技巧。as\u跨步(a,shape=(1000,len(a)-1000+1),跨步=(a.strips[0],a.strips[0])
。实际上,我需要大小为100k的窗口,阵列大小为10m或更大。你有什么解决办法吗?@Alfe只需使用他提出的
scipy.ndimage.maximum_filter1d
方法。它几乎同样快,即使对于大型阵列也应该非常有效。@MSeifert不幸的是,它比pandas
rolling\u max()
,,在我的测试中,大小比实际大小的下限大约大2倍。这很有趣,因为在我的计算机上,
maximum_filter1d
对于100k的窗口大小和a
sliding_window_view(values, window_shape = 5)
# array([[6, 4, 8, 7, 1],
#        [4, 8, 7, 1, 4],
#        [8, 7, 1, 4, 3],
#        ...
#        [7, 1, 9, 4, 3],
#        [1, 9, 4, 3, 2]])