检查 numpy 数组中步幅的非歧义性

use*_*314 5 python numpy

对于 numpy 数组X,其元素的位置从byX[k[0], ..., k[d-1]]的位置偏移,其中是表示 的元组。X[0,..., 0]k[0]*s[0] + ... + k[d-1]*s[d-1](s[0],...,s[d-1])X.strides

据我所知,numpy 数组规范中没有任何内容要求数组的不同索引X对应于内存中的不同地址,最简单的例子是步幅的零值,例如,请参阅scipy 讲座的高级 NumPy部分。

numpy 是否有一个内置谓词来测试步幅和形状是否使得不同的索引映射到不同的内存地址?

如果没有,如何编写,最好是避免对步幅进行排序?

hpa*_*ulj 5

编辑:我花了一些时间才弄清楚你在问什么。通过跨步技巧,可以以不同的方式对数据缓冲区中的同一元素进行索引,而广播实际上是在幕后完成此操作的。通常我们不担心它,因为它要么是隐藏的,要么是故意的。

在跨步映射中重新创建并查找重复项可能是测试这一点的唯一方法。我不知道有任何现有函数可以检查它。

=================

我不太清楚你关心什么。但让我来说明一下形状和步幅是如何工作的

定义一个 3x4 数组:

In [453]: X=np.arange(12).reshape(3,4)
In [454]: X.shape
Out[454]: (3, 4)
In [455]: X.strides
Out[455]: (16, 4)
Run Code Online (Sandbox Code Playgroud)

索引项目

In [456]: X[1,2]
Out[456]: 6
Run Code Online (Sandbox Code Playgroud)

我可以使用以下方法在数组的扁平版本(例如原始版本arange)中获取它的索引ravel_multi_index

In [457]: np.ravel_multi_index((1,2),X.shape)
Out[457]: 6
Run Code Online (Sandbox Code Playgroud)

我还可以使用步幅来获取此位置 - 请记住步幅以字节为单位(此处每个项目 4 个字节)

In [458]: 1*16+2*4
Out[458]: 24
In [459]: (1*16+2*4)/4
Out[459]: 6.0
Run Code Online (Sandbox Code Playgroud)

所有这些数字都相对于数据缓冲区的开头。X.data我们可以从或获取数据缓冲区地址X.__array_interface__['data'],但通常不需要。

因此,这个步长告诉我们,从条目到下一个,步骤 4 个字节,从一行到下一个步骤 16.6位于缓冲区的下一行、2 个字节或 24 个字节处。

as_strided链接的示例中,strides=(1*2, 0)会生成特定值的重复索引。

和我的X

In [460]: y=np.lib.stride_tricks.as_strided(X,strides=(16,0), shape=(3,4))
In [461]: y
Out[461]: 
array([[0, 0, 0, 0],
       [4, 4, 4, 4],
       [8, 8, 8, 8]])
Run Code Online (Sandbox Code Playgroud)

y是一个 3x4,重复索引 的第一列X

更改 中的一项y最终会更改 中的一个值,X但更改 中的一整行y

In [462]: y[1,2]=10
In [463]: y
Out[463]: 
array([[ 0,  0,  0,  0],
       [10, 10, 10, 10],
       [ 8,  8,  8,  8]])
In [464]: X
Out[464]: 
array([[ 0,  1,  2,  3],
       [10,  5,  6,  7],
       [ 8,  9, 10, 11]])
Run Code Online (Sandbox Code Playgroud)

as_strided如果你不小心,可能会产生一些奇怪的效果。

好吧,也许我已经弄清楚是什么困扰了您 - 我可以识别这样的情况:两个不同的索引元组最终指向数据缓冲区中的同一位置吗?据我所知。步幅y包含 0 是一个非常好的指标。

as_strided通常用于创建重叠窗口:

In [465]: y=np.lib.stride_tricks.as_strided(X,strides=(8,4), shape=(3,4))
In [466]: y
Out[466]: 
array([[ 0,  1,  2,  3],
       [ 2,  3, 10,  5],
       [10,  5,  6,  7]])
In [467]: y[1,2]=20
In [469]: y
Out[469]: 
array([[ 0,  1,  2,  3],
       [ 2,  3, 20,  5],
       [20,  5,  6,  7]])
Run Code Online (Sandbox Code Playgroud)

再次更改 中的 1 个项目y最终会更改 y 中的 2 个值,但仅更改 中的 1 个值X

普通的数组创建和索引不存在这种重复索引的问题。广播可能会在幕后执行类似的操作,将 (4,) 数组更改为 (1,4),然后更改为 (3,4),从而有效地复制行。我认为还有另一个stride_tricks函数可以明确执行此操作。

In [475]: x,y=np.lib.stride_tricks.broadcast_arrays(X,np.array([.1,.2,.3,.4]))
In [476]: x
Out[476]: 
array([[ 0,  1,  2,  3],
       [20,  5,  6,  7],
       [ 8,  9, 10, 11]])
In [477]: y
Out[477]: 
array([[ 0.1,  0.2,  0.3,  0.4],
       [ 0.1,  0.2,  0.3,  0.4],
       [ 0.1,  0.2,  0.3,  0.4]])
In [478]: y.strides
Out[478]: (0, 8)
Run Code Online (Sandbox Code Playgroud)

无论如何,在正常的数组使用中我们不必担心这种歧义。我们只有通过有意的行动才能获得它,而不是偶然的行动。

=============

测试一下这个怎么样:

def dupstrides(x):
    uniq={sum(s*j for s,j in zip(x.strides,i)) for i in np.ndindex(x.shape)}
    print(uniq)
    print(len(uniq))
    print(x.size)
    return len(uniq)<x.size

In [508]: dupstrides(X)
{0, 32, 4, 36, 8, 40, 12, 44, 16, 20, 24, 28}
12
12
Out[508]: False
In [509]: dupstrides(y)
{0, 4, 8, 12, 16, 20, 24, 28}
8
12
Out[509]: True
Run Code Online (Sandbox Code Playgroud)