我正在学习numpy,其中有一个叫做“strides”的主题。我理解这个主题的概念,但不知道它是如何工作的。我在网上没有找到任何有用的信息,有谁能用通俗易懂的语言帮我了解一下吗?
>>> a = np.arange(1,10).reshape(3,3)
>>> a
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
这个二维数组有两个方向,轴0(沿行从上往下垂直运行),和轴1(沿列从左往右水平运行),每个项的大小为:
>>> a.itemsize # in bytes
4
a[0, 0] -> a[0, 1]
(沿着第0行水平移动,从第0列到第1列)在数据缓冲区中的字节步长为4。对于a[0, 1] -> a[0, 2]
、a[1, 0] -> a[1, 1]
等也是如此。这意味着水平方向(轴-1)的步幅数为4个字节。a[0, 0] -> a[1, 0]
(沿着第0列垂直移动,从第0行到第1行),需要先遍历所有剩余的第0行项目才能到达第1行,然后通过第1行移动到项目 a[1, 0]
,即 a[0, 0] -> a[0, 1] -> a[0, 2] -> a[1, 0]
。因此,垂直方向(轴-0)的步幅数为3*4 = 12个字节。请注意,从 a[0, 2] -> a[1, 0]
,以及一般地从第i行的最后一个项目到第(i+1)行的第一个项目,也是4个字节,因为数组 a
是按行主序存储的。>>> a.strides # (strides[0], strides[1])
(12, 4)
strides[1]
不一定等于项大小的2D数组(例如列优先顺序的数组)。>>> b = np.array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]]).T
>>> b
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> b.strides
(4, 12)
strides[1]
是项大小的倍数。尽管数组b
看起来与数组a
相同,但它们是不同的数组:在内部,b
被存储为|1|4|7|2|5|8|3|6|9|
(因为转置不影响数据缓冲区,只交换步长和形状),而a
则为|1|2|3|4|5|6|7|8|9|
。使它们看起来相似的是不同的步幅。也就是说,b[0, 0] -> b[0, 1]
的字节步长为3*4=12字节,b[0, 0] -> b[1, 0]
的字节步长为4字节,而a[0, 0] -> a[0, 1]
的字节步长为4字节,a[0, 0] -> a[1, 0]
的字节步长为12字节。>>> np.lib.stride_tricks.as_strided(a, shape=a.shape[::-1], strides=a.strides[::-1])
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
这相当于对数组 a
进行转置。
让我简单补充一下,但不会深入细节,我们甚至可以定义非项大小倍数的步长。以下是一个示例:
>>> a = np.lib.stride_tricks.as_strided(np.array([1, 512, 0, 3], dtype=np.int16),
shape=(3,), strides=(3,))
>>> a
array([1, 2, 3], dtype=int16)
>>> a.strides[0]
3
>>> a.itemsize
2
x = np.arange(20).reshape([4, 5])
>>> x
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
预期输出:
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]],
[[ 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14]],
[[ 10, 11, 12, 13, 14],
[ 15, 16, 17, 18, 19]]])
>>> x.strides
(20, 4)
>>> np.int32().itemsize
4
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]],
[[ 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14]],
[[ 10, 11, 12, 13, 14],
[ 15, 16, 17, 18, 19]]])
>>> from numpy.lib import stride_tricks
>>> stride_tricks.as_strided(x, shape=(3, 2, 5),
strides=(20, 20, 4))
...
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]],
[[ 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14]],
[[ 10, 11, 12, 13, 14],
[ 15, 16, 17, 18, 19]]])
另一个选择可能是:
>>> d = dict(x.__array_interface__)
>>> d['shape'] = (3, 2, 5)
>>> s['strides'] = (20, 20, 4)
>>> class Arr:
... __array_interface__ = d
... base = x
>>> np.array(Arr())
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]],
[[ 5, 6, 7, 8, 9],
[ 10, 11, 12, 13, 14]],
[[ 10, 11, 12, 13, 14],
[ 15, 16, 17, 18, 19]]])
numpy.zeros
数组,并使用array.strides
获取步幅,并在函数stride_tricks.as_strided
中使用它。我根据@Rick M.提供的代码修改了它以适应我的问题,即对任何形状的numpy数组进行移动窗口切片。以下是代码:
def sliding_window_slicing(a, no_items, item_type=0):
"""This method perfoms sliding window slicing of numpy arrays
Parameters
----------
a : numpy
An array to be slided in subarrays
no_items : int
Number of sliced arrays or elements in sliced arrays
item_type: int
Indicates if no_items is number of sliced arrays (item_type=0) or
number of elements in sliced array (item_type=1), by default 0
Return
------
numpy
Sliced numpy array
"""
if item_type == 0:
no_slices = no_items
no_elements = len(a) + 1 - no_slices
if no_elements <=0:
raise ValueError('Sliding slicing not possible, no_items is larger than ' + str(len(a)))
else:
no_elements = no_items
no_slices = len(a) - no_elements + 1
if no_slices <=0:
raise ValueError('Sliding slicing not possible, no_items is larger than ' + str(len(a)))
subarray_shape = a.shape[1:]
shape_cfg = (no_slices, no_elements) + subarray_shape
strides_cfg = (a.strides[0],) + a.strides
as_strided = np.lib.stride_tricks.as_strided #shorthand
return as_strided(a, shape=shape_cfg, strides=strides_cfg)
这种方法可以自动计算步长(strides),并且适用于numpy数组的任何维度:
1D数组 - 通过切片数量进行切片
In [11]: a
Out[11]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [12]: sliding_window_slicing(a, 5, item_type=0)
Out[12]:
array([[0, 1, 2, 3, 4, 5],
[1, 2, 3, 4, 5, 6],
[2, 3, 4, 5, 6, 7],
[3, 4, 5, 6, 7, 8],
[4, 5, 6, 7, 8, 9]])
1D数组 - 通过每个切片的元素数量进行切片
In [13]: sliding_window_slicing(a, 5, item_type=1)
Out[13]:
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8],
[5, 6, 7, 8, 9]])
二维数组 - 通过切片获取多个数组元素
In [16]: a = np.arange(10).reshape([5,2])
In [17]: a
Out[17]:
array([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
In [18]: sliding_window_slicing(a, 2, item_type=0)
Out[18]:
array([[[0, 1],
[2, 3],
[4, 5],
[6, 7]],
[[2, 3],
[4, 5],
[6, 7],
[8, 9]]])
2D数组 - 根据每个切片的元素数量进行切片
In [19]: sliding_window_slicing(a, 2, item_type=1)
Out[19]:
array([[[0, 1],
[2, 3]],
[[2, 3],
[4, 5]],
[[4, 5],
[6, 7]],
[[6, 7],
[8, 9]]])