繁体   English   中英

从一维数组构建高效的Numpy 2D数组

[英]Efficient Numpy 2D array construction from 1D array

我有一个像这样的数组:

A = array([1,2,3,4,5,6,7,8,9,10])

我试图得到这样的数组:

B = array([[1,2,3],
          [2,3,4],
          [3,4,5],
          [4,5,6]])

每行(具有固定的任意宽度)每行偏移一个。 A的数组是10k记录长,我试图在Numpy中找到一种有效的方法。 目前,我正在使用vstack和for循环,这很慢。 有没有更快的方法?

编辑:

width = 3 # fixed arbitrary width
length = 10000 # length of A which I wish to use
B = A[0:length + 1]
for i in range (1, length):
    B = np.vstack((B, A[i, i + width + 1]))

实际上,还有一种更有效的方法来执行此操作...使用vstack等的缺点是,您正在复制数组。

顺便说一句,这实际上与@Paul的答案相同,但是我发布这个只是为了更详细地解释事情...

有一种方法可以只使用视图来执行此操作,从而不会复制任何内存。

我是直接从Erik Rigtorp的帖子中借给numpy-discussion的 ,后者又从Keith Goodman的Bottleneck (这很有用!)中借来了它。

基本技巧是直接操纵数组步幅 (对于一维数组):

import numpy as np

def rolling(a, window):
    shape = (a.size - window + 1, window)
    strides = (a.itemsize, a.itemsize)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(10)
print rolling(a, 3)

其中a是您的输入数组, window是您想要的窗口的长度(在您的情况下为3)。

这样产生:

[[0 1 2]
 [1 2 3]
 [2 3 4]
 [3 4 5]
 [4 5 6]
 [5 6 7]
 [6 7 8]
 [7 8 9]]

但是,原始a和返回的数组之间绝对没有重复的内存。 这意味着,它的快速和规模比其他选项好得多

例如(使用a = np.arange(100000)window=3 ):

%timeit np.vstack([a[i:i-window] for i in xrange(window)]).T
1000 loops, best of 3: 256 us per loop

%timeit rolling(a, window)
100000 loops, best of 3: 12 us per loop

如果我们将其沿N维数组的最后一个轴归纳为“滚动窗口”,则会得到Erik Rigtorp的“滚动窗口”功能:

import numpy as np

def rolling_window(a, window):
   """
   Make an ndarray with a rolling window of the last dimension

   Parameters
   ----------
   a : array_like
       Array to add rolling window to
   window : int
       Size of rolling window

   Returns
   -------
   Array that is a view of the original array with a added dimension
   of size w.

   Examples
   --------
   >>> x=np.arange(10).reshape((2,5))
   >>> rolling_window(x, 3)
   array([[[0, 1, 2], [1, 2, 3], [2, 3, 4]],
          [[5, 6, 7], [6, 7, 8], [7, 8, 9]]])

   Calculate rolling mean of last dimension:
   >>> np.mean(rolling_window(x, 3), -1)
   array([[ 1.,  2.,  3.],
          [ 6.,  7.,  8.]])

   """
   if window < 1:
       raise ValueError, "`window` must be at least 1."
   if window > a.shape[-1]:
       raise ValueError, "`window` is too long."
   shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
   strides = a.strides + (a.strides[-1],)
   return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

因此,让我们看看这里发生了什么...操纵数组的strides似乎有些神奇,但是一旦您了解了正在发生的事情,那根本就没有了。 numpy数组的步幅描述了沿给定轴递增一个值所必须执行的步长(以字节为单位)。 因此,对于64位浮点数的一维数组,每一项的长度为8个字节,而x.strides(8,)

x = np.arange(9)
print x.strides

现在,如果我们将其重塑为2D,3x3数组,则步幅将为(3 * 8, 8) ,因为我们必须跳24个字节才能沿第一个轴递增一步,而要跳8个字节来沿第一个轴递增一步。第二轴。

y = x.reshape(3,3)
print y.strides

类似地,转置与反转数组的步幅相同:

print y
y.strides = y.strides[::-1]
print y

显然,阵列的步幅和阵列的形状紧密相连。 如果更改一个,则必须相应地更改另一个,否则,我们将无法获得对实际上保存数组值的内存缓冲区的有效描述。

因此,如果你想同时改变一个数组的两个形状和尺寸,你不能仅仅通过设置做x.stridesx.shape ,即使新的进展和形状是兼容的。

那就是numpy.lib.as_strided出现的地方。它实际上是一个非常简单的函数,它仅同时设置数组的步幅和形状。

它会检查这两者是否兼容,但不会检查旧的步幅和新形状是否兼容,如果您分别设置这两者会发生这种情况。 (它实际上是通过numpy的__array_interface__ ,该数组允许任意类将内存缓冲区描述为numpy数组。)

因此,我们所做的全部工作就是使它沿一个轴前进一个项目(在64位数组的情况下为8个字节),但也沿另一个轴前进8个字节

换句话说,在“窗口”大小为3的情况下,数组的形状为(whatever, 3) 3 * x.itemsize (whatever, 3) ,但不是为第二维步进完整的3 * x.itemsize ,而是仅向前步进一项 ,有效地使新数组的行成为原始数组的“移动窗口”视图。

(这也意味着x.shape[0] * x.shape[1]将与新数组的x.size 。)

无论如何,希望这会使事情变得更加清晰。

该解决方案无法通过python循环有效地实现,因为在使用numpy数组时,它最好避免各种类型检查。 如果您的阵列非常高,您会注意到它的速度大大提高了:

newshape = (4,3)
newstrides = (A.itemsize, A.itemsize)
B = numpy.lib.stride_tricks.as_strided(A, shape=newshape, strides=newstrides)

这给出了数组A的视图 。如果要编辑一个新数组,请执行同样的操作,但最后使用.copy()

大步的详细信息:

在这种情况下,新newstrides元组将为(4,4),因为该数组具有4个字节的项目,并且您想继续以i维的单项步骤newstrides数据。 第二个值“ 4”是指j维度中的跨度(在正常4x4数组中为16)。 因为在这种情况下,您还希望以j维度的4字节步长增加从缓冲区的读取。

乔说这绝招是同时改变步伐和形状时,给了一个很好而详尽的描述,并使事情变得清晰。

为了进一步了解@Joe general的答案

import numpy as np
def rolling(a, window):
    step = 2 
    shape = ( (a.size-window)/step + 1   , window)


    strides = (a.itemsize*step, a.itemsize)

    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(10)

print rolling(a, 3)

输出:

[[0 1 2]
 [2 3 4]
 [4 5 6]
 [6 7 8]]

为了进一步概括2d情况,即使用它从图像中提取补丁

def rolling2d(a,win_h,win_w,step_h,step_w):

    h,w = a.shape
    shape = ( ((h-win_h)/step_h + 1)  * ((w-win_w)/step_w + 1) , win_h , win_w)

    strides = (step_w*a.itemsize, h*a.itemsize,a.itemsize)


    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(36).reshape(6,6)
print a
print rolling2d (a,3,3,2,2)

输出:

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]
 [24 25 26 27 28 29]
 [30 31 32 33 34 35]]
[[[ 0  1  2]
  [ 6  7  8]
  [12 13 14]]

 [[ 2  3  4]
  [ 8  9 10]
  [14 15 16]]

 [[ 4  5  6]
  [10 11 12]
  [16 17 18]]

 [[ 6  7  8]
  [12 13 14]
  [18 19 20]]]

您使用哪种方法?

import numpy as np
A = np.array([1,2,3,4,5,6,7,8,9,10])
width = 3

np.vstack([A[i:i-len(A)+width] for i in xrange(len(A)-width)])
# needs 26.3µs

np.vstack([A[i:i-width] for i in xrange(width)]).T
# needs 13.2µs

如果您的宽度相对较小(3),并且具有较大的A (10000个元素),那么差异就更为重要:第一个为32.4ms,第二个为44µs。

我正在使用与@JustInTime类似的更通用的函数,但适用于ndarray

def sliding_window(x, size, overlap=0):
    step = size - overlap # in npts
    nwin = (x.shape[-1]-size)//step + 1
    shape = x.shape[:-1] + (nwin, size)
    strides = x.strides[:-1] + (step*x.strides[-1], x.strides[-1])
    return stride_tricks.as_strided(x, shape=shape, strides=strides)

一个例子,

x = np.arange(10)
M.sliding_window(x, 5, 3)
Out[1]: 
array([[0, 1, 2, 3, 4],
       [2, 3, 4, 5, 6],
       [4, 5, 6, 7, 8]])


x = np.arange(10).reshape((2,5))
M.sliding_window(x, 3, 1)
Out[2]: 
array([[[0, 1, 2],
        [2, 3, 4]],

       [[5, 6, 7],
        [7, 8, 9]]])

看一下: view_as_windows

import numpy as np
from skimage.util.shape import view_as_windows
window_shape = (4, )
aa = np.arange(1000000000) # 1 billion
bb = view_as_windows(aa, window_shape)

1秒左右。

我认为当宽度固定为一个较小的数字时,这可能比循环更快。

import numpy
a = numpy.array([1,2,3,4,5,6])
b = numpy.reshape(a, (numpy.shape(a)[0],1))
b = numpy.concatenate((b, numpy.roll(b,-1,0), numpy.roll(b,-2,0)), 1)
b = b[0:(numpy.shape(a)[0]/2) + 1,:]

编辑显然,使用跨步的解决方案要优于此,唯一的主要缺点是尚未有充分的文档记录...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM