检查numpy数组中步幅的非模糊性

时间:2016-09-16 21:09:10

标签: python numpy

对于numpy数组X,其元素X[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的不同索引对应于内存中的不同地址,最简单的实例是stride的零值,例如:参见scipy讲座的advanced NumPy部分。

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

如果没有,那么如何写一个,最好是为了避免对步幅进行排序?

2 个答案:

答案 0 :(得分:2)

编辑:我花了一些时间来确定你在问什么。通过大步的技巧,可以以不同的方式在数据填充器中索引相同的元素,并且广播实际上在封面下进行。通常我们不担心它,因为它是隐藏的或有意的。

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

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

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

定义一个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)

索引项目

In [456]: X[1,2]
Out[456]: 6

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

In [457]: np.ravel_multi_index((1,2),X.shape)
Out[457]: 6

我也可以使用步幅获取此位置 - 请注意步幅以字节为单位(此处每个项目4个字节)

In [458]: 1*16+2*4
Out[458]: 24
In [459]: (1*16+2*4)/4
Out[459]: 6.0

所有这些数字都与数据缓冲区的起点有关。我们可以从X.dataX.__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]])

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]])
如果你不小心,

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]])

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

普通数组创建和索引没有这种重复的索引问题。广播可以做一些类似的事情,在封面下,(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)

在任何情况下,在正常的数组使用中,我们不必担心这种歧义。我们只是采取有意的行动,而不是偶然行动。

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

测试的结果如何:

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

答案 1 :(得分:2)

事实证明,此测试已经在numpy中实现,请参阅mem_overlap.c:842

测试公开为numpy.core.multiarray_tests.internal_overlap(x)

示例:

>>> import numpy as np
>>> from numpy.core.multiarray_tests import internal_overlap
>>> from numpy.lib.stride_tricks import as_strided

现在,创建一个连续的数组,并使用as_strided创建一个内部重叠的数组,并通过测试确认:

>>> x = np.arange(3*4, dtype=np.float64).reshape((3,4))
>>> y = as_strided(x, shape=(5,4), strides=(16, 8))
>>> y
array([[  0.,   1.,   2.,   3.],
       [  2.,   3.,   4.,   5.],
       [  4.,   5.,   6.,   7.],
       [  6.,   7.,   8.,   9.],
       [  8.,   9.,  10.,  11.]])
>>> internal_overlap(x)
False
>>> internal_overlap(y)
True

该函数经过优化,可快速返回Fortran或C-连续数组的False