为什么NumPy为x[[切片(无),1,2]]创建视图

2024-09-30 05:23:42 发布

您现在位置:Python中文网/ 问答频道 /正文

advanced indexing的NumPy文档中,提到

Also recognize that x[[1, 2, 3]] will trigger advanced indexing, whereas x[[1, 2, slice(None)]] will trigger basic slicing.

矩阵按顺序存储在存储器中。我知道查看x[[1, 2, slice(None)]]是有意义的,因为元素是按顺序存储在内存中的。但是为什么Numpy返回x[[1, slice(None), 2]]x[[slice(None), 1, 2]]的视图呢。例如,假设

x = [[[ 0,  1,  2],
      [ 3,  4,  5],
      [ 6,  7,  8]],
     [[ 9, 10, 11],
      [12, 13, 14],
      [15, 16, 17]],
     [[18, 19, 20],
      [21, 22, 23],
      [24, 25, 26]]]

x[[1, slice(None), 2]]返回未顺序存储在内存中的[11, 14, 17]视图,以及返回[5, 14, 23]x[[slice(None), 1, 2]]视图。你知道吗

我想知道

  1. 为什么在这两种情况下NumPy甚至返回一个视图

  2. NumPy如何处理内存寻址来创建这些视图


Tags: 内存文档numpynone视图thatslicewill
2条回答

SciPy cookbook

The rule of thumb for creating a slice view is that the viewed elements can be addressed with offsets, strides, and counts in the original array.

当你有一个像x[[1, slice(None), 2]]这样的索引时,你会得到一个视图,因为切片整个轴允许一定的偏移、跨距和计数来表示原始数组的切片。你知道吗

例如,对于x = np.arange(27).reshape(3, 3, 3).copy(),我们有:

In [79]: x_view = x[1, :, 2]  # or equivalently x[[1, slice(None), 2]]

In [80]: x_view
Out[80]: array([11, 14, 17])

In [81]: x_view.base
Out[81]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23],
        [24, 25, 26]]])

然后我们可以使用^{}(不是公共API的一部分,YMMV)来说明从原始数组获取切片的偏移量。你知道吗

In [82]: np.byte_bounds(x_view)[0] - np.byte_bounds(x_view.base)[0]
Out[82]: 88

这是有意义的,因为在片中的第一个值11之前有11个8字节整数。NumPy使用一个公式来计算这个偏移量,这个公式可以see here,使用原始数组的步长。你知道吗

In [93]: (x.strides * np.array([1, 0, 2])).sum()
Out[93]: 88

我们切片中的步幅只是成为沿着我们切片的轴的x的步幅。i、 例如x.strides[1] == x_view.strides[0]。现在,偏移量、新的步长和计数加在一起就足够让NumPy从原始数组中查看切片了。你知道吗

In [94]: x_view.strides
Out[94]: (24,)

In [95]: x_view.size
Out[95]: 3

最后,使用x[[0, 1, 2]]触发花哨索引的原因是,在没有全轴切片的情况下,通常不可能制定一些新的偏移量、字节顺序、跨距和计数,以便我们可以使用相同的基础数据查看切片。你知道吗

我喜欢使用__array_interface__来检查数组的属性:

用你的x

In [51]: x.__array_interface__
Out[51]: 
{'data': (43241792, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (3, 3, 3),
 'version': 3}
In [52]: x.strides
Out[52]: (72, 24, 8)

这是一个(3,3,3)数组。最后一个轴可以一次扫描8个字节,大小为x.itemsize。3*8步行,3*3*8步穿过平面(第一个尺寸)。你知道吗

In [53]: y = x[:,1,2]
In [54]: y.shape
Out[54]: (3,)
In [55]: y.strides
Out[55]: (72,)
In [56]: y.__array_interface__['data']
Out[56]: (43241832, False)

y元素可以通过平面3*3*8步进来寻址。43241832是起点,40字节进入数据缓冲区,5*8

In [59]: y
Out[59]: array([ 5, 14, 23])

所以它从第5个元素开始,一次前进一个平面(9个元素),总共3个元素。你知道吗

y.__array_interface__['data']x“data”范围内的事实告诉我y是一个视图。这是一个视图,因为这个缓冲区起点、跨步和形状的组合允许我们访问y的所有值。你知道吗

使用高级索引,通常不可能用这些简单的参数访问元素,因此numpy必须复制数据。你知道吗


只需改变步幅和“数据”起点,就可以得到相反的观点:

In [60]: z = y[::-1]
In [61]: z.__array_interface__
Out[61]: 
{'data': (43241976, False),
 'strides': (-72,),
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (3,),
 'version': 3}

转置也会改变步伐:

In [62]: x.T.strides
Out[62]: (8, 24, 72)

相关问题 更多 >

    热门问题