问题:连续数组和非连续数组有什么区别?
在有关reshape()函数的numpy手册中,它说
>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# Taking a view makes it possible to modify the shape without modifying the
# initial object.
>>> c = b.view()
>>> c.shape = (20)
AttributeError: incompatible shape for a non-contiguous array
我的问题是:
- 什么是连续和不连续数组?它类似于C 中的连续存储块吗?什么是连续存储块?
- 两者之间是否有性能差异?我们什么时候应该使用其中一个?
- 为什么转置会使数组不连续?
- 为什么会
c.shape = (20)
引发错误incompatible shape for a non-contiguous array
?
感谢您的回答!
回答 0
连续数组只是存储在不间断内存块中的数组:要访问数组中的下一个值,我们只需移至下一个内存地址。
考虑2D数组arr = np.arange(12).reshape(3,4)
。看起来像这样:
在计算机的内存中,的值arr
存储如下:
这意味着arr
是C连续数组,因为行被存储为连续的内存块。下一个内存地址保存该行的下一行值。如果要向下移动一列,我们只需要跳过三个块(例如,从0跳到4意味着我们跳过1,2和3)。
用换位数组arr.T
意味着C连续性丢失,因为相邻的行条目不再位于相邻的存储器地址中。但是,Fortranarr.T
是连续的,因为列在内存的连续块中:
从性能角度来看,访问彼此相邻的内存地址通常比访问更“扩展”的地址更快(从RAM中获取值可能需要为CPU提取并缓存许多相邻地址。)意味着对连续阵列的操作通常会更快。
由于C连续的内存布局,因此按行操作通常比按列操作快。例如,您通常会发现
np.sum(arr, axis=1) # sum the rows
快于:
np.sum(arr, axis=0) # sum the columns
同样,对于Fortran连续数组,对列的操作将稍快一些。
最后,为什么不能通过分配新形状来展平Fortran连续数组?
>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array
为了使这成为可能,NumPy必须arr.T
像这样将各行放在一起:
(shape
直接设置该属性将假定C顺序-即NumPy尝试逐行执行该操作。)
这是不可能的。对于任何轴,NumPy必须具有恒定的步幅长度(要移动的字节数)才能到达数组的下一个元素。arr.T
以这种方式展平将需要在内存中向前和向后跳过以检索数组的连续值。
如果我们arr2.reshape(12)
改为写,NumPy会将arr2的值复制到新的内存块中(因为它无法将视图返回到该形状的原始数据)。
回答 1
也许此示例具有12个不同的数组值将有所帮助:
In [207]: x=np.arange(12).reshape(3,4).copy()
In [208]: x.flags
Out[208]:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
...
In [209]: x.T.flags
Out[209]:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
...
该C order
值是,他们在生成的顺序。在调换哪些不是
In [212]: x.reshape(12,) # same as x.ravel()
Out[212]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
In [213]: x.T.reshape(12,)
Out[213]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
您可以同时获得两者的一维视图
In [214]: x1=x.T
In [217]: x.shape=(12,)
的形状x
也可以更改。
In [220]: x1.shape=(12,)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-220-cf2b1a308253> in <module>()
----> 1 x1.shape=(12,)
AttributeError: incompatible shape for a non-contiguous array
但是移调的形状无法更改。在data
仍处于0,1,2,3,4...
顺序,这不能被访问访问如0,4,8...
在一维数组。
但是x1
可以更改的副本:
In [227]: x2=x1.copy()
In [228]: x2.flags
Out[228]:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
...
In [229]: x2.shape=(12,)
看strides
也许也有帮助。跨步是到达下一个值必须走多远(以字节为单位)。对于2d数组,将有2个跨步值:
In [233]: x=np.arange(12).reshape(3,4).copy()
In [234]: x.strides
Out[234]: (16, 4)
要到达下一行,请步进16个字节,仅下一列4。
In [235]: x1.strides
Out[235]: (4, 16)
转置只是切换步幅的顺序。下一行只有4个字节,即下一个数字。
In [236]: x.shape=(12,)
In [237]: x.strides
Out[237]: (4,)
改变形状也会改变步幅-一次仅通过缓冲区4个字节。
In [238]: x2=x1.copy()
In [239]: x2.strides
Out[239]: (12, 4)
即使x2
看起来像x1
,它也有自己的数据缓冲区,其值以不同的顺序排列。现在,下一列是4字节,而下一行是12(3 * 4)。
In [240]: x2.shape=(12,)
In [241]: x2.strides
Out[241]: (4,)
并且x
,将形状更改为1d会将步幅减小为(4,)
。
因为x1
,按0,1,2,...
顺序排列数据,不会产生一维的跨度0,4,8...
。
__array_interface__
是显示数组信息的另一种有用方法:
In [242]: x1.__array_interface__
Out[242]:
{'strides': (4, 16),
'typestr': '<i4',
'shape': (4, 3),
'version': 3,
'data': (163336056, False),
'descr': [('', '<i4')]}
该x1
数据缓冲器地址将是相同x
,同它的数据。 x2
具有不同的缓冲区地址。
您也可以尝试order='F'
在copy
和reshape
命令中添加参数。