【python-numpy】高效分块操作np.stride_tricks.as_strided
在机器学习和深度学习应用中,我们往往需要对输入的数据进行滑动窗口预处理,常规的操作是使用for循环。但在python当中for循环是比较慢的操作,特别是当需要处理的数组维度特别庞大时,利用for循环进行滑动窗口预处理耗时会特别长。据笔者查阅资料所得,Numpy提供了两种高效的分块操作,分别是和。后者是比较常用的方法,但没有前者灵活,不能根据需要调整stride步长,很难实现带重叠的滑动窗口。而目前
一、背景
在机器学习和深度学习应用中,我们往往需要对输入的数据进行滑动窗口预处理,常规的操作是使用for循环。但在python当中for循环是比较慢的操作,特别是当需要处理的数组维度特别庞大时,利用for循环进行滑动窗口预处理耗时会特别长。据笔者查阅资料所得,Numpy提供了两种高效的分块操作,分别是numpy.lib.stride_tricks.as_strided和numpy.lib.stride_tricks.sliding_window_view。后者是比较常用的方法,但没有前者灵活,不能根据需要调整stride步长,很难实现带重叠的滑动窗口。而目前网络上对第一种分块操作的讲解很少,官方的手册也比较难懂,所以借本文来表达一下笔者个人的理解,若有错误请指出。
二、对numpy.stride的理解
要站在大佬的肩膀上思考问题,可以参考这篇博客(没接触过stride一定要先看一下这篇博客,不然本文后面的部分很可能看不懂),博主在文章中具体解释了多维数组stride的含义,真的非常易懂:Python多维数组跨度strides(学习笔记)_stride python-CSDN博客
三、一个使用np.stride_tricks.as_strided的例子
1.任务说明
我们以最基础的二维数组为例来学习np.stride_tricks.as_strided的使用,在这里借鉴了这篇文章,对博主表示感谢:【python】numpy中的高效分块操作np.stride_tricks.as_strided_np.strides-CSDN博客
现有一形状为(9,9)的二维数组,也就是9行9列,我们需要将其分为9个(3,3)的数组,如下图所示,在数组维度上的变化即(9,9)变化到(3, 3, 3, 3):

这里直接用图来解释一下为什么分割后的维度是(3, 3, 3, 3),首先从宏观上来看,将每一个3x3的正方形组合看成一个整体,这就组成了第0维度和第1维度,也就是对应的前两个(3,3),而再看每一个小正方形的组合,又组成了第2维度和第3维度,也就是对应后两个(3,3),如下图所示:


2.代码实现
这里先贴上上面博主写的代码:
# 来源:NumPy Cookbook 2e Ch2.9
import numpy as np
# 数独是个 9x9 的二维数组
# 包含 9 个 3x3 的九宫格
sudoku = np.array([
[2, 8, 7, 1, 6, 5, 9, 4, 3],
[9, 5, 4, 7, 3, 2, 1, 6, 8],
[6, 1, 3, 8, 4, 9, 7, 5, 2],
[8, 7, 9, 6, 5, 1, 2, 3, 4],
[4, 2, 1, 3, 9, 8, 6, 7, 5],
[3, 6, 5, 4, 2, 7, 8, 9, 1],
[1, 9, 8, 5, 7, 3, 4, 2, 6],
[5, 4, 2, 9, 1, 6, 3, 8, 7],
[7, 3, 6, 2, 8, 4, 5, 1, 9]
])
# 要将其变成 3x3x3x3 的四维数组
# 但不能直接 reshape,因为这样会把一行变成一个九宫格
shape = (3, 3, 3, 3)
# 大行之间隔 27 个元素,大列之间隔 3 个元素
# 小行之间隔 9 个元素,小列之间隔 1 个元素
strides = sudoku.itemsize * np.array([27, 3, 9, 1])
squares = np.lib.stride_tricks.as_strided(sudoku, shape=shape, strides=strides)
print(squares)
'''
[[[[2 8 7]
[9 5 4]
[6 1 3]]
[[1 6 5]
[7 3 2]
[8 4 9]]
[[9 4 3]
[1 6 8]
[7 5 2]]]
[[[8 7 9]
[4 2 1]
[3 6 5]]
[[6 5 1]
[3 9 8]
[4 2 7]]
[[2 3 4]
[6 7 5]
[8 9 1]]]
...
[[4 2 6]
[3 8 7]
[5 1 9]]]
'''
最开始接触这段代码的时候,笔者确实不懂大行、小行、大列、小列到底是什么意思,后来画图才明白,这里的大行表示的是第0维度上的两个存储的元素之间距离,相当于是下图第1个正方形和第28个正方形元素对应的距离;而大列则表示第1维度上两个存储的元素之间的距离,相当于是下图第1个小正方形和第4个小正方形元素对应的距离;小列则表示第1个正方形和第2个正方形之间的距离,小行表示第1个正方形和第10个正方形之间的距离:

我们以第0维度也就是所谓的“大行”为例,这之间的距离应该相当于是3个大正方形(27个小正方形,因为numpy的存储顺序是按照“行”来进行的),则对应的总字节应该等于27*单个元素(小正方形)占用的字节数。
3.举一反三
有了上面的例子后,我们将问题变更为将9*9的二维数组划分为3个3*9的数组,即数组维度的变化从(9,9)变更为(3,3,9),可以发现在这种情况下只需要沿维度0滑动即可实现,所以我们的stride维度为3。从维度0上来看,距离依旧是27个小正方形,从维度1上来看是9个正方形,从维度2上来看是1个正方形(一定要注意维度的变化,如果这段话您明白了,这个函数的使用对您来说那真的是轻轻松松)。对应的代码修改如下:
import numpy as np
# 数独是个 9x9 的二维数组
# 包含 9 个 3x3 的九宫格
sudoku = np.array([
[2, 8, 7, 1, 6, 5, 9, 4, 3],
[9, 5, 4, 7, 3, 2, 1, 6, 8],
[6, 1, 3, 8, 4, 9, 7, 5, 2],
[8, 7, 9, 6, 5, 1, 2, 3, 4],
[4, 2, 1, 3, 9, 8, 6, 7, 5],
[3, 6, 5, 4, 2, 7, 8, 9, 1],
[1, 9, 8, 5, 7, 3, 4, 2, 6],
[5, 4, 2, 9, 1, 6, 3, 8, 7],
[7, 3, 6, 2, 8, 4, 5, 1, 9]
])
# 要将其变成 3x3x3x3 的四维数组
# 但不能直接 reshape,因为这样会把一行变成一个九宫格
shape = (3, 3, 9)
# 大行之间隔 27 个元素,
# 小行之间隔 9 个元素,小列之间隔 1 个元素
strides = sudoku.itemsize * np.array([27, 9, 1])
squares = np.lib.stride_tricks.as_strided(sudoku, shape=shape, strides=strides)
print(squares)
四.使用np.stride_tricks.as_strided对多通道数据进行划分
假设在机器学习任务中特征向量维度为(L,C),其中L是时间序列的长度,C表示通道数,在本例子中假设L为10,C为6,需要使用窗口宽度为3,stride为2的滑动窗口预处理,则对应的代码如下(注:本代码没有考虑padding,余下的部分直接舍弃):
import numpy as np
np.random.seed(42)
arr = np.random.randint(low=0, high=5, size=(10, 6))
"""
[[3 4 2 4 4 1]
[2 2 2 4 3 2]
[4 1 3 1 3 4]
[0 3 1 4 3 0]
[0 2 2 1 3 3]
[2 3 3 0 2 4]
[2 4 0 1 3 0]
[3 1 1 0 1 4]
[1 3 3 3 3 4]
[2 0 3 1 3 1]]
"""
def sliding_window(arr, window_size, stride=None):
if stride is None:
stride = window_size // 2
assert len(arr.shape) == 2, "Input array must be 2d"
assert window_size <= arr.shape[0], "Window size must be smaller than or equal to the length of time sequence"
assert stride <= window_size, "Stride must be smaller than or equal to window size"
shape = ((arr.shape[0] - window_size) // stride + 1, window_size, arr.shape[1])
strides = (arr.strides[0] * stride, arr.strides[0], arr.strides[1])
return np.lib.stride_tricks.as_strided(arr, shape=shape, strides=strides)
window_size = 3
result = []
print(arr)
print('\n')
for i in range(sliding_window(arr, window_size, stride=2).shape[0]):
print(sliding_window(arr, window_size, stride=2)[i])
"""
[[3 4 2 4 4 1]
[2 2 2 4 3 2]
[4 1 3 1 3 4]]
[[4 1 3 1 3 4]
[0 3 1 4 3 0]
[0 2 2 1 3 3]]
[[0 2 2 1 3 3]
[2 3 3 0 2 4]
[2 4 0 1 3 0]]
[[2 4 0 1 3 0]
[3 1 1 0 1 4]
[1 3 3 3 3 4]]
"""
代码参考了基于Numpy中的as_strided函数的滑动窗口应用|极客教程 (geek-docs.com),在此向原作者表示衷心感谢和敬意!
更多推荐




所有评论(0)