视图中的 Numpy 重塑
Numpy reshape on view
我对视图上的 numpy reshape 操作的结果感到困惑。
下面的q.flags表明它不拥有数据,但是q.base既不是x也不是y,那是什么呢?我很惊讶地看到 q.strides 是 8,这意味着它每次在内存中移动 8 个字节时都会获取下一个元素(如果我理解正确的话)。然而,如果除 x 之外的数组 none 拥有数据,则唯一的数据缓冲区来自 x,这不允许通过移动 8 个字节来获取 q 的下一个元素。
In [99]: x = np.random.rand(4, 4)
In [100]: y = x.T
In [101]: q = y.reshape(16)
In [102]: q.base is y
Out[102]: False
In [103]: q.base is x
Out[103]: False
In [104]: y.flags
Out[104]:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
In [105]: q.flags
Out[105]:
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
In [106]: q.strides
Out[106]: (8,)
In [107]: x
Out[107]:
array([[ 0.62529694, 0.20813211, 0.73932923, 0.43183722],
[ 0.09755023, 0.67082005, 0.78412615, 0.40307291],
[ 0.2138691 , 0.35191283, 0.57455781, 0.2449898 ],
[ 0.36476299, 0.36590522, 0.24371933, 0.24837697]])
In [108]: q
Out[108]:
array([ 0.62529694, 0.09755023, 0.2138691 , 0.36476299, 0.20813211,
0.67082005, 0.35191283, 0.36590522, 0.73932923, 0.78412615,
0.57455781, 0.24371933, 0.43183722, 0.40307291, 0.2449898 ,
0.24837697])
更新:
原来在numpy讨论区有人问过这个问题:
http://numpy-discussion.10968.n7.nabble.com/OWNDATA-flag-and-reshape-views-vs-copies-td10363.html
简而言之:您不能总是依赖 ndarray.flags['OWNDATA']
。
>>> import numpy as np
>>> x = np.random.rand(2,2)
>>> y = x.T
>>> q = y.reshape(4)
>>> y[0,0]
0.86751629121019136
>>> y[0,0] = 1
>>> q
array([ 0.86751629, 0.87671107, 0.65239976, 0.41761267])
>>> x
array([[ 1. , 0.65239976],
[ 0.87671107, 0.41761267]])
>>> y
array([[ 1. , 0.87671107],
[ 0.65239976, 0.41761267]])
>>> y.flags['OWNDATA']
False
>>> x.flags['OWNDATA']
True
>>> q.flags['OWNDATA']
False
>>> np.may_share_memory(x,y)
True
>>> np.may_share_memory(x,q)
False
因为 q
没有反映第一个元素的变化,如 x
或 y
,它必须以某种方式成为数据的所有者(以某种方式在下面解释) .
在 numpy-discussion mailinglist. In the How can I tell if NumPy creates a view or a copy? 问题上有更多关于 OWNDATA
标志的讨论,简要提到有时简单地检查 ndarray
的 flags.owndata
似乎失败,而且它似乎不可靠,正如你提到的。那是因为每个 ndarray
也有一个 base
属性:
如果内存源自其他地方,base of an ndarray 是对另一个数组的引用(否则,基数是 None
)。操作 y.reshape(4)
创建一个副本,而不是视图,因为 y
的步幅是 (8,16)
。要将其重塑(C-连续)为 (4,)
,内存指针必须跳转 0->16->8->24
,这一步是做不到的。因此 q.base
指向由强制复制操作 y.reshape
生成的内存位置,其形状与 y
相同,但复制了元素,因此再次具有正常步幅:(16, 8)
. q.base
因此不受任何其他名称的约束,因为它是强制复制操作 y.reshape(4)
的结果。只有现在才能以 (4,)
形状查看对象 q.base
,因为步幅允许这样做。 q
确实是对 q.base
的看法。
对于大多数人来说,看到 q.flags.owndata
是 False
会感到困惑,因为如上所示,它不是 y
上的视图。但是,它是 y
副本上的视图。然而,该副本 q.base
是数据的所有者。因此,如果您仔细检查,这些标志实际上是正确的。
我喜欢用.__array_interface__
。
In [811]: x.__array_interface__
Out[811]:
{'data': (149194496, False),
'descr': [('', '<f8')],
'shape': (4, 4),
'strides': None,
'typestr': '<f8',
'version': 3}
In [813]: y.__array_interface__
Out[813]:
{'data': (149194496, False),
'descr': [('', '<f8')],
'shape': (4, 4),
'strides': (8, 32),
'typestr': '<f8',
'version': 3}
In [814]: x.strides
Out[814]: (32, 8)
In [815]: y.strides
Out[815]: (8, 32)
转置是通过反转步幅来执行的。基本数据指针相同。
In [817]: q.__array_interface__
Out[817]:
{'data': (165219304, False),
'descr': [('', '<f8')],
'shape': (16,),
'strides': None,
'typestr': '<f8',
'version': 3}
所以q
数据是一个副本(不同的指针)。步幅 (8,)
表示通过从一个 f8
步进到下一个来访问其元素。但是 x.reshape(16)
是 x
的视图 - 因为它的数据可以通过简单的 8
步骤访问。
要按q
顺序访问原始数据,它必须步进32个字节3次(向下x
行),然后回到开始,第8步到第2步x
列,然后是 3 行步等。由于跨步无法以这种方式工作,因此必须从副本开始工作。
另请注意 y[0,0]
更改 x[0,0]
,但 q[0]
独立于两者。
虽然 q
的 OWNDATA
为假,但 y.ravel()
和 y.flatten()
为真。我怀疑 reshape()
在这种情况下正在制作一个副本,然后重塑,它是 'owns' 数据 q.base
.
的中间副本
我对视图上的 numpy reshape 操作的结果感到困惑。 下面的q.flags表明它不拥有数据,但是q.base既不是x也不是y,那是什么呢?我很惊讶地看到 q.strides 是 8,这意味着它每次在内存中移动 8 个字节时都会获取下一个元素(如果我理解正确的话)。然而,如果除 x 之外的数组 none 拥有数据,则唯一的数据缓冲区来自 x,这不允许通过移动 8 个字节来获取 q 的下一个元素。
In [99]: x = np.random.rand(4, 4)
In [100]: y = x.T
In [101]: q = y.reshape(16)
In [102]: q.base is y
Out[102]: False
In [103]: q.base is x
Out[103]: False
In [104]: y.flags
Out[104]:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
In [105]: q.flags
Out[105]:
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
In [106]: q.strides
Out[106]: (8,)
In [107]: x
Out[107]:
array([[ 0.62529694, 0.20813211, 0.73932923, 0.43183722],
[ 0.09755023, 0.67082005, 0.78412615, 0.40307291],
[ 0.2138691 , 0.35191283, 0.57455781, 0.2449898 ],
[ 0.36476299, 0.36590522, 0.24371933, 0.24837697]])
In [108]: q
Out[108]:
array([ 0.62529694, 0.09755023, 0.2138691 , 0.36476299, 0.20813211,
0.67082005, 0.35191283, 0.36590522, 0.73932923, 0.78412615,
0.57455781, 0.24371933, 0.43183722, 0.40307291, 0.2449898 ,
0.24837697])
更新:
原来在numpy讨论区有人问过这个问题: http://numpy-discussion.10968.n7.nabble.com/OWNDATA-flag-and-reshape-views-vs-copies-td10363.html
简而言之:您不能总是依赖 ndarray.flags['OWNDATA']
。
>>> import numpy as np
>>> x = np.random.rand(2,2)
>>> y = x.T
>>> q = y.reshape(4)
>>> y[0,0]
0.86751629121019136
>>> y[0,0] = 1
>>> q
array([ 0.86751629, 0.87671107, 0.65239976, 0.41761267])
>>> x
array([[ 1. , 0.65239976],
[ 0.87671107, 0.41761267]])
>>> y
array([[ 1. , 0.87671107],
[ 0.65239976, 0.41761267]])
>>> y.flags['OWNDATA']
False
>>> x.flags['OWNDATA']
True
>>> q.flags['OWNDATA']
False
>>> np.may_share_memory(x,y)
True
>>> np.may_share_memory(x,q)
False
因为 q
没有反映第一个元素的变化,如 x
或 y
,它必须以某种方式成为数据的所有者(以某种方式在下面解释) .
在 numpy-discussion mailinglist. In the How can I tell if NumPy creates a view or a copy? 问题上有更多关于 OWNDATA
标志的讨论,简要提到有时简单地检查 ndarray
的 flags.owndata
似乎失败,而且它似乎不可靠,正如你提到的。那是因为每个 ndarray
也有一个 base
属性:
如果内存源自其他地方,base of an ndarray 是对另一个数组的引用(否则,基数是 None
)。操作 y.reshape(4)
创建一个副本,而不是视图,因为 y
的步幅是 (8,16)
。要将其重塑(C-连续)为 (4,)
,内存指针必须跳转 0->16->8->24
,这一步是做不到的。因此 q.base
指向由强制复制操作 y.reshape
生成的内存位置,其形状与 y
相同,但复制了元素,因此再次具有正常步幅:(16, 8)
. q.base
因此不受任何其他名称的约束,因为它是强制复制操作 y.reshape(4)
的结果。只有现在才能以 (4,)
形状查看对象 q.base
,因为步幅允许这样做。 q
确实是对 q.base
的看法。
对于大多数人来说,看到 q.flags.owndata
是 False
会感到困惑,因为如上所示,它不是 y
上的视图。但是,它是 y
副本上的视图。然而,该副本 q.base
是数据的所有者。因此,如果您仔细检查,这些标志实际上是正确的。
我喜欢用.__array_interface__
。
In [811]: x.__array_interface__
Out[811]:
{'data': (149194496, False),
'descr': [('', '<f8')],
'shape': (4, 4),
'strides': None,
'typestr': '<f8',
'version': 3}
In [813]: y.__array_interface__
Out[813]:
{'data': (149194496, False),
'descr': [('', '<f8')],
'shape': (4, 4),
'strides': (8, 32),
'typestr': '<f8',
'version': 3}
In [814]: x.strides
Out[814]: (32, 8)
In [815]: y.strides
Out[815]: (8, 32)
转置是通过反转步幅来执行的。基本数据指针相同。
In [817]: q.__array_interface__
Out[817]:
{'data': (165219304, False),
'descr': [('', '<f8')],
'shape': (16,),
'strides': None,
'typestr': '<f8',
'version': 3}
所以q
数据是一个副本(不同的指针)。步幅 (8,)
表示通过从一个 f8
步进到下一个来访问其元素。但是 x.reshape(16)
是 x
的视图 - 因为它的数据可以通过简单的 8
步骤访问。
要按q
顺序访问原始数据,它必须步进32个字节3次(向下x
行),然后回到开始,第8步到第2步x
列,然后是 3 行步等。由于跨步无法以这种方式工作,因此必须从副本开始工作。
另请注意 y[0,0]
更改 x[0,0]
,但 q[0]
独立于两者。
虽然 q
的 OWNDATA
为假,但 y.ravel()
和 y.flatten()
为真。我怀疑 reshape()
在这种情况下正在制作一个副本,然后重塑,它是 'owns' 数据 q.base
.