如何在向量化NumPy数组上进行移动窗口

今天很有可能你已经做了一些使用滑动窗口(也称为移动窗口)的事情,而你甚至不知道它。例如:许多编辑算法都是基于移动窗口的。在GIS中做地形分析的大多数地形栅格度量(坡度、坡向、山坡阴影等)都基于滑动窗口。很多情况下,对格式化为二维数组的数据进行分析时,都很有可能涉及到滑动窗口。

滑动窗口操作非常普遍,非常有用。它们也很容易在Python中实现。学习如何实现移动窗口将把你的数据分析和争论技能提升到一个新的水平。

什么是滑动窗?

下面的例子显示了一个3×3(3×3)滑动窗口。用红色标注的数组元素是目标元素。这是滑动窗口将计算的新度量的数组位置。例如,在下面的图像中,我们可以计算灰色窗口中9个元素的平均值(平均值也是8),并将其分配给目标元素,用红色标出。你可以计算最小值(0)、最大值(16)或其他一些指标,而不是平均值。对数组中的每个元素都这样做。

就是这样。这就是滑动窗口的基本原理。当然,事情可能变得更加复杂。有限差分方法可以用于时间和空间数据。逻辑可以实现。可以使用更大的窗口大小或非正方形窗口。你懂的。但在其核心,移动窗口分析可以简单地总结为邻居元素的平均值。

需要注意的是,必须为边缘元素设置特殊的调整,因为它们没有9个相邻元素。因此,许多分析都排除了边缘元素。为简单起见,我们将在本文中排除边缘元素。

52C3CC43-E885-8A16-5E95-514C880A0C85.png

样例数组

A482C1EE-BE7C-02F0-832C-BD3E0E349995.png

3x3的滑动窗口

创建一个NumPy数组

为了实现一些简单的示例,让我们创建上面所示的数组。首先,导入numpy。

import numpy as np

然后使用arange创建一个7×7的数组,值范围从1到48。另外,创建另一个包含无数据值的数组,该数组的形状和数据类型与初始数组相同。在本例中,我使用-1作为无数据值。

a = np.arange(49).reshape((7, 7)) 
b = np.full(a.shape, -1.0)

我们将使用这些数组来开发下面的滑动窗口示例。

通过循环实现滑动窗口

毫无疑问,你已经听说过Python中的循环很慢,应该尽可能避免。特别是在使用大型NumPy数组时。这是完全正确。尽管如此,我们将首先看一个使用循环的示例,因为这是一种简单的方法来概念化在移动窗口操作中发生的事情。在你通过循环示例掌握了概念之后,我们将继续使用更有效的向量化方法。

要实现移动窗口,只需循环遍历所有内部数组元素,识别所有相邻元素的值,并在特定的计算中使用这些值。

通过行和列偏移量可以很容易地识别相邻值。3×3窗口的偏移量如下所示。

A26ABB20-BA6A-B8E4-3774-467FA09813A1.png

行偏移

596A9727-F6CF-AFE5-EA35-4C405A112D72.png

列偏移

循环中NumPy移动窗口的Python代码

我们可以用三行代码实现一个移动窗口。这个例子在滑动窗口内计算平均值。首先,循环遍历数组的内部行。其次,循环遍历数组的内部列。第三,在滑动窗口内计算平均值,并将值赋给输出数组中相应的数组元素。

for i in range(1, a.shape[0]-1):
    for j in range(1, a.shape[1]-1): 
        b[i, j] = (a[i-1, j-1] + a[i-1, j] + a[i-1, j+1] + a[i, j-1] + a[i, j] + a[i, j+1] + a[i+1, j-1] + a[i+1, j] + a[i+1, j+1]) / 9.0

循环后结果

你将注意到结果与输入数组具有相同的值,但是外部元素没有被分配数据值,因为它们不包含9个相邻元素。

[[-1. -1. -1. -1. -1. -1. -1.]
 [-1. 8. 9. 10. 11. 12. -1.]
 [-1. 15. 16. 17. 18. 19. -1.]
 [-1. 22. 23. 24. 25. 26. -1.]
 [-1. 29. 30. 31. 32. 33. -1.] 
 [-1. 36. 37. 38. 39. 40. -1.]
 [-1. -1. -1. -1. -1. -1. -1.]]

向量化滑动窗口

Python中的数组循环通常计算效率低下。通过对通常在循环中执行的操作进行向量化,可以提高效率。移动窗口矢量化可以通过同时抵消数组内部的所有元素来实现。

如下图所示。每个图像都有相应的索引。你将注意到最后一张图像索引了所有内部元素,并且对应的图像索引了每个相邻元素的偏移量。

8B0ABA76-3DEA-5252-035B-A49CB01C74DD.png

5F04B45E-C35A-7218-83B3-CFABE1452196.png

F2EF8195-15A0-BDEB-A1AF-030D0D6DBAC1.png

从左到右的偏移索引:[1:-1,:-2],[1:-1,2:],[2 :, 2:]

AB7AD522-67E2-874C-D4E7-03BFE681C147.png

9F4DBA34-24B4-A649-C3D9-71AD94FC69A9.png

DE176BE3-DFA7-3537-F38A-73F64536A72F.png

从左到右的偏移索引:[2 :,:-2],[2 :, 1:-1],[:-2,1:-1]

41576221-C494-24A6-F135-9C95F5030234.png

E3396856-2ED6-70AA-0084-C6E48B59BDE9.png

EFBB646B-AFF3-27CF-9379-CFFCF0DBDCD7.png

从左到右的偏移索引:[:-2,2:],[:-2,:-2],[1:-1、1:-1]

Numpy数组上的向量化移动窗口的Python代码

有了上述偏移量,我们现在可以轻松地在一行代码中实现滑动窗口。 只需将输出数组的所有内部元素设置为根据相邻元素计算所需输出的函数。

b[1:-1, 1:-1] = (a[1:-1, 1:-1] + a[:-2, 1:-1] + a[2:, 1:-1] + a[1:-1, :-2] + a[1:-1, 2:] + a[2:, 2:] + a[:-2, :-2] + a[2:, :-2] + a[:-2, 2:]) / 9.0

矢量化滑动窗口结果

如你所见,这将得到与循环相同的结果。

[[-1. -1. -1. -1. -1. -1. -1.]
 [-1. 8. 9. 10. 11. 12. -1.]
 [-1. 15. 16. 17. 18. 19. -1.]
 [-1. 22. 23. 24. 25. 26. -1.]
 [-1. 29. 30. 31. 32. 33. -1.]
 [-1. 36. 37. 38. 39. 40. -1.]
 [-1. -1. -1. -1. -1. -1. -1.]]

速度比较

上述两种方法产生相同的结果,但哪一种更有效?我计算了从5行到100列的数组的每种方法的速度。每种方法对每个测试100次。下面是每种方法的平均时间。

A4EDA7CE-C38A-A7CC-A7DE-CB3F4373BD63.png

很明显,向量化的方法更加有效。随着数组大小的增加,循环的效率呈指数级下降。另外,需要注意的是,一个包含10,000个元素(100行和100列)的数组非常小。

总结

移动窗口计算在许多数据分析工作流程中非常常见。这些计算是非常有用的,非常容易实现。然而,使用循环来实现滑动窗口操作是非常低效的。向量化的移动窗口实现不仅更高效,而且使用更少的代码行。一旦掌握了实现滑动窗口的向量化方法,就可以轻松有效地提高工作流程的速度。

作者:Konrad Hafen

deephub翻译组

收藏 (0)
评论列表
正在载入评论列表...
我是有底线的