何时获取 numpy 数组的子矩阵返回视图但不返回副本?

Posted

技术标签:

【中文标题】何时获取 numpy 数组的子矩阵返回视图但不返回副本?【英文标题】:When does getting submatrix of an numpy array returns view but not copy? 【发布时间】:2014-02-01 17:10:54 【问题描述】:

我正在尝试获取 numpy 二维数组的子矩阵,并对其进行修改。有时我得到一个copy,对它的修改不会影响原始数组:

In [650]: d=np.random.rand(5,5)

In [651]: may_share_memory(d, d[[0,1],:][:,[2,3]])
Out[651]: False

In [652]: d[[0,1],:][:,[2,3]]=2

In [653]: d
Out[653]: 
array([[ 0.0648922 ,  0.41408311,  0.88024646,  0.22471181,  0.81811439],
       [ 0.32154096,  0.88349028,  0.30755883,  0.55301128,  0.61138144],
       [ 0.18398833,  0.40208368,  0.69888324,  0.93197147,  0.43538379],
       [ 0.55633382,  0.80531999,  0.71486132,  0.4186339 ,  0.76487239],
       [ 0.81193408,  0.4951559 ,  0.97713937,  0.33904998,  0.27660239]])

虽然有时我似乎得到了一个视图,尽管may_share_memory 也返回False

In [662]: d[np.ix_([0,1],[2,3])]=1

In [663]: d
Out[663]: 
array([[ 0.0648922 ,  0.41408311,  1.        ,  1.        ,  0.81811439],
       [ 0.32154096,  0.88349028,  1.        ,  1.        ,  0.61138144],
       [ 0.18398833,  0.40208368,  0.69888324,  0.93197147,  0.43538379],
       [ 0.55633382,  0.80531999,  0.71486132,  0.4186339 ,  0.76487239],
       [ 0.81193408,  0.4951559 ,  0.97713937,  0.33904998,  0.27660239]])

In [664]: may_share_memory(d, d[np.ix_([0,1],[2,3])])
Out[664]: False

更奇怪的是,如果把那个'view'赋值给一个变量,它就变成了'copy'(同样,修改不影响原始数组):

In [658]: d2=d[np.ix_([0,1],[2,3])]

In [659]: may_share_memory(d,d2)
Out[659]: False

In [660]: d2+=1

In [661]: d
Out[661]: 
array([[ 0.0648922 ,  0.41408311,  0.88024646,  0.22471181,  0.81811439],
       [ 0.32154096,  0.88349028,  0.30755883,  0.55301128,  0.61138144],
       [ 0.18398833,  0.40208368,  0.69888324,  0.93197147,  0.43538379],
       [ 0.55633382,  0.80531999,  0.71486132,  0.4186339 ,  0.76487239],
       [ 0.81193408,  0.4951559 ,  0.97713937,  0.33904998,  0.27660239]])

【问题讨论】:

【参考方案1】:

我同意;这很奇怪。然而,它有一个逻辑。

请注意,切片赋值是 python 中一种特殊的重载方法。切片分配不会创建视图然后写入它;它直接写入数组。您不能为 a[[2,0,1]] 的 ndarray 创建视图,因为您不能将此视图表示为跨步数组,这是所有 numpy 函数所需的基本接口。但是您可以直接使用索引并对其进行操作。可以说,为了一致性,这样的切片赋值应该对副本进行修改;但是,如果您不将新创建的数组绑定到新名称,那么重点在哪里?

总的来说,在 python 中,赋值和切片赋值是完全不同的野兽,它们做的事情完全不同,这有点尴尬。这也是其根源所在。右侧的切片赋值和切片调用不同的函数,并且在概念上有些不同。 may_share_memory 指的是右侧切片的行为,而不是切片分配。

【讨论】:

【参考方案2】:

您看到的是difference between "fancy" indexing and normal indexing。

另外,为了清楚起见,d[np.ix_([0,1],[2,3])] = 1 不是一个视图,它是一个任务。有关这方面的更多解释,请参阅@EelcoHoogendoorn 的答案。您的困惑的根源似乎在于 Eelco 解决的 __setitem____getitem__,但我想我会添加一些特定于 numpy 的说明。

每当您使用坐标序列进行索引(np.ix_ 返回一个索引数组)时,它都是“花哨的”索引,并且总是会返回一个副本。

任何你可以用切片做的事情总是返回一个视图。

例如:

In [1]: import numpy as np

In [2]: x = np.arange(10)

In [3]: y = x[3:5]

In [4]: z = x[[3, 4]]

In [5]: z[0] = 100

In [5]: x
Out[5]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [6]: y[0] = 100

In [7]: x
Out[7]: array([  0,   1,   2, 100,   4,   5,   6,   7,   8,   9])

原因是 numpy 数组在内存中必须是半连续的(更准确地说,它们必须能够通过偏移量、步幅和形状来描述)。

任何类型的切片都可以用这种方式描述(甚至像 x[:, 3:100:5, None] 这样的东西)。

不能是任意坐标序列(例如x[[1, 4, 5, 100]])。

因此,如果使用切片,numpy 总是返回一个视图,如果使用“花式索引”(也就是使用索引序列或布尔掩码),则返回一个副本。

但是,分配(例如x[blah] = y)将总是就地修改 numpy 数组。

【讨论】:

以上是关于何时获取 numpy 数组的子矩阵返回视图但不返回副本?的主要内容,如果未能解决你的问题,请参考以下文章

numpy 辨异 —— numpy ravel vs numpy flatten

numpy 矩阵变换 reshape ravel flatten

在二维矩阵中使用 numpy.nanargmin()

使用 NumPy 从矩阵中获取最小/最大 n 值和索引的有效方法

numpy数组列表中N个最高元素的索引[重复]

Numpy randn rand 及数组转换