步幅如何帮助遍历 numpy 中的数组?

How strides help in traversing an array in numpy?

arr = np.arange(16).reshape((2, 2, 4))

arr.strides
(32, 16, 4)

因此,据我所知,我相信在记忆中它会类似于下图。 步幅沿轴标记(在箭头上)。

这就是我在使用命令转置其中一个轴后得到的结果:

arr.transpose((1, 0, 2))

我知道内存块中没有变化,但我无法理解步幅究竟如何帮助遍历内存块中的数组以生成预期的数组。 (是否将不同轴上的元素倒序遍历?)

[[[ 0  1  2  3]
[ 4  5  6  7]] 
[[ 8  9 10 11]
[12 13 14 15]]]

我试图通过 C 中的官方 numpy 代码来理解,但我无法理解。

如果有人能以更简单的方式提供解释,那就太好了。

根据我的经验,处理 C 代码的工作量太大了。简单地找到相关功能是最困难的部分。 strides 无论尺寸或 'transpose' 都一样工作。

从更简单的东西开始,例如 (2,3) 数组,其转置步幅为 (8,24)。想象一下穿过公寓 [0,1,2...].

示例数组,大小为 1 个字节,因此顺序步长仅为 1

In [635]: x=np.arange(6,dtype='uint8')
In [636]: x
Out[636]: array([0, 1, 2, 3, 4, 5], dtype=uint8)
In [637]: x.strides
Out[637]: (1,)

现在重塑它:

In [638]: y=x.reshape(2,3)
In [639]: y
Out[639]: 
array([[0, 1, 2],
       [3, 4, 5]], dtype=uint8)
In [640]: y.strides
Out[640]: (3, 1)

为了跨列,我们仍然只是一步一步地通过共享 x 数据缓冲区。要往下走,我们将 3、0 步进到 3、2 步到 5。

In [641]: z = y.transpose()
In [642]: z
Out[642]: 
array([[0, 3],
       [1, 4],
       [2, 5]], dtype=uint8)
In [643]: z.strides
Out[643]: (1, 3)

现在我们向下走 1 步,穿过 3 步。

但随后您对图像进行了相同的推断(正式我们应该忽略 :))

那么你的问题是什么?如果您在阅读某些特定的 C 代码时遇到问题,您需要展示它,或者至少引用它。

遍历数组的方法有很多种。对于 flatiter strides 并不重要,它只是一次通过数据缓冲区一项。

但是如果我们在第一个维度上迭代,

In [687]: for i in y:print(i)
[0 1 2]
[3 4 5]

步进 3 以获取下一行。

In [688]: for i in z:print(i)
[0 3]
[1 4]
[2 5]

而这一步是 1。

Numpy 总是从最大的轴到最小的轴(即降序)迭代轴,除非明确要求(例如使用 axis 参数)。因此,在您的示例中,它首先读取内存中偏移量 0 处的视图项目,然后添加轴 2 的步幅(此处为 4)并读取下一个项目,依此类推,直到到达轴的末端。然后它添加一次轴 1 的步幅并再次重复之前的循环,以此类推其他轴。

内部 Numpy C 代码的行为如下:

// Numpy array are not strongly typed internally.
// The raw memory buffer is always contiguous.
char* data = view.rawData;

const size_t iStride = view.stride[0];
const size_t jStride = view.stride[1];
const size_t kStride = view.stride[2];

for(int i=0 ; i<view.shape[0] ; ++i) {
    for(int j=0 ; j<view.shape[1] ; ++j) {
        for(int k=0 ; k<view.shape[2] ; ++k) {
            // Compute the offset from the view strides
            const size_t offset = iStride * i + jStride * j + kStride * k;

            // Extract an item at the memory offset
            Type item = (Type*)(data + offset);

            // Do something with item here (eg. print it)
        }
    }
}

当您应用换位时,Numpy 会更改步幅,以便交换 iStridejStride。它还更新形状(view.shape[0]view.shape[1] 也被交换)。代码将 执行就像交换两个循环一样 除了内存访问效率较低,因为它们较少 连续 。这是一个例子:

arr = np.arange(16).reshape((2, 2, 4))
arr.strides   # (32, 16, 4)
view = arr.transpose((1, 0, 2))
view.strides  # (16, 32, 4)  <--  note that 16 and 32 have been swapped

请注意,步幅以 字节为单位 (而不是项目数)。