什么时候使用reshape()时会numpy复制数组

Lif*_*ang 13 python numpy

在文件中numpy.reshape,它说:

如果可能,这将是一个新的视图对象; 否则,它将是一份副本.注意,不保证返回数组的内存布局(C-或Fortran-连续).

我的问题是,numpy何时会选择返回一个新视图,何时复制整个数组?是否有任何一般原则告诉人们行为reshape,或者只是不可预测?谢谢.

hpa*_*ulj 7

@mgillson 找到的链接似乎解决了“我如何判断它是否制作了副本”的问题,而不是“我如何预测它”或了解它为什么制作副本的问题。至于测试,我喜欢使用A.__array_interfrace__.

如果您尝试将值分配给重新整形的数组,并且期望也更改原始数组,那么这很可能是一个问题。而且我很难找到问题所在的 SO 案例。

复制重塑会比非复制重塑慢一点,但我再次想不出会导致整个代码变慢的情况。如果您使用的数组太大以至于最简单的操作会产生内存错误,那么副本也可能是一个问题。


对数据缓冲区中的值进行整形后,需要按连续顺序排列,即 'C' 或 'F'。例如:

In [403]: np.arange(12).reshape(3,4,order='C')
Out[403]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [404]: np.arange(12).reshape(3,4,order='F')
Out[404]: 
array([[ 0,  3,  6,  9],
       [ 1,  4,  7, 10],
       [ 2,  5,  8, 11]])
Run Code Online (Sandbox Code Playgroud)

如果初始订单如此“混乱”以至于无法返回这样的值,它将进行复制。转置后的重塑可能会这样做(请参阅下面的示例)。使用stride_tricks.as_strided. 这些是我能想到的唯一案例。

In [405]: x=np.arange(12).reshape(3,4,order='C')

In [406]: y=x.T

In [407]: x.__array_interface__
Out[407]: 
{'version': 3,
 'descr': [('', '<i4')],
 'strides': None,
 'typestr': '<i4',
 'shape': (3, 4),
 'data': (175066576, False)}

In [408]: y.__array_interface__
Out[408]: 
{'version': 3,
 'descr': [('', '<i4')],
 'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'data': (175066576, False)}
Run Code Online (Sandbox Code Playgroud)

y,转置,具有相同的“数据”指针。在不改变或复制数据进行转置,它只是创造了一个新的对象与新的shapestridesflags

In [409]: y.flags
Out[409]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  ...

In [410]: x.flags
Out[410]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  ...
Run Code Online (Sandbox Code Playgroud)

y是订单'F'。现在尝试重塑它

In [411]: y.shape
Out[411]: (4, 3)

In [412]: z=y.reshape(3,4)

In [413]: z.__array_interface__
Out[413]: 
{...
 'shape': (3, 4),
 'data': (176079064, False)}

In [414]: z
Out[414]: 
array([[ 0,  4,  8,  1],
       [ 5,  9,  2,  6],
       [10,  3,  7, 11]])
Run Code Online (Sandbox Code Playgroud)

z是一个副本,它的data缓冲区指针是不同的。其值的排列方式与xy、 no 的排列方式不同0,1,2,...

但简单地重塑x不会产生副本:

In [416]: w=x.reshape(4,3)

In [417]: w
Out[417]: 
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

In [418]: w.__array_interface__
Out[418]: 
{...
 'shape': (4, 3),
 'data': (175066576, False)}
Run Code Online (Sandbox Code Playgroud)

Ravelingyy.reshape(-1); 它生成为副本:

In [425]: y.reshape(-1)
Out[425]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

In [426]: y.ravel().__array_interface__['data']
Out[426]: (175352024, False)
Run Code Online (Sandbox Code Playgroud)

像这样将值分配给散乱的数组可能是副本产生错误的最有可能的情况。例如,x.ravel()[::2]=99更改xy(分别为列和行)的每隔一个值。但y.ravel()[::2]=0由于这种复制,什么也不做。

所以转置后重塑是最有可能的复制场景。我很乐意探索其他可能性。

编辑: y.reshape(-1,order='F')[::2]=0确实改变了y. 使用兼容顺序,reshape 不会生成副本。


@mgillson 链接中的一个答案/sf/answers/998990891/指出该A.shape=...语法可防止复制。如果它不能在不复制的情况下更改形状,则会引发错误:

In [441]: y.shape=(3,4)
...
AttributeError: incompatible shape for a non-contiguous array
Run Code Online (Sandbox Code Playgroud)

reshape文档中也提到了这一点

如果您希望在复制数据时引发错误,则应将新形状分配给数组的 shape 属性:


关于重塑以下的问题as_strided

在不使用 reshape 的情况下重塑 n 维数组的视图

Numpy View Reshape without Copy(二维移动/滑动窗口、步幅、屏蔽内存结构)

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

这是我在翻译shape.c/_attempt_nocopy_reshape成 Python 时的第一次剪辑。它可以通过以下方式运行:

newstrides = attempt_reshape(numpy.zeros((3,4)), (4,3), False)
Run Code Online (Sandbox Code Playgroud)
import numpy   # there's an np variable in the code
def attempt_reshape(self, newdims, is_f_order):
    newnd = len(newdims)
    newstrides = numpy.zeros(newnd+1).tolist()  # +1 is a fudge

    self = numpy.squeeze(self)
    olddims = self.shape
    oldnd = self.ndim
    oldstrides = self.strides

    #/* oi to oj and ni to nj give the axis ranges currently worked with */

    oi,oj = 0,1
    ni,nj = 0,1
    while (ni < newnd) and (oi < oldnd):
        print(oi, ni)
        np = newdims[ni];
        op = olddims[oi];

        while (np != op):
            if (np < op):
                # /* Misses trailing 1s, these are handled later */
                np *= newdims[nj];
                nj += 1
            else:
                op *= olddims[oj];
                oj += 1

        print(ni,oi,np,op,nj,oj)

        #/* Check whether the original axes can be combined */
        for ok in range(oi, oj-1):
            if (is_f_order) :
                if (oldstrides[ok+1] != olddims[ok]*oldstrides[ok]):
                    # /* not contiguous enough */
                    return 0;
            else:
                #/* C order */
                if (oldstrides[ok] != olddims[ok+1]*oldstrides[ok+1]) :
                    #/* not contiguous enough */
                    return 0;

        # /* Calculate new strides for all axes currently worked with */
        if (is_f_order) :
            newstrides[ni] = oldstrides[oi];
            for nk in range(ni+1,nj):
                newstrides[nk] = newstrides[nk - 1]*newdims[nk - 1];
        else:
            #/* C order */
            newstrides[nj - 1] = oldstrides[oj - 1];
            #for (nk = nj - 1; nk > ni; nk--) {
            for nk in range(nj-1, ni, -1):
                newstrides[nk - 1] = newstrides[nk]*newdims[nk];
        nj += 1; ni = nj
        oj += 1; oi = oj  
        print(olddims, newdims)  
        print(oldstrides, newstrides)

    # * Set strides corresponding to trailing 1s of the new shape.
    if (ni >= 1) :
        print(newstrides, ni)
        last_stride = newstrides[ni - 1];
    else :
        last_stride = self.itemsize # PyArray_ITEMSIZE(self);

    if (is_f_order) :
        last_stride *= newdims[ni - 1];

    for nk in range(ni, newnd):
        newstrides[nk] = last_stride;
    return newstrides
Run Code Online (Sandbox Code Playgroud)


ben*_*min 5

您可以通过仅测试所涉及维度的连续性来进行预测。

\n\n

(这是 numpy 决定是使用视图还是副本的代码。)

\n\n

连续性意味着任何维度的步长完全等于下一个变化更快的维度的步长 \xc3\x97 长度。

\n\n

例如,涉及意味着,如果最里面的维度和最外面的维度不连续,只要保持它们相同,就没有关系。

\n\n
\n\n

一般来说,如果您所做的只是重塑整个数组,则可以期待一个视图。如果您正在处理较大数组中的子集,或者以任何方式对元素进行了重新排序,那么很可能是一个副本。

\n\n
\n\n

例如,考虑一个矩阵:

\n\n
A = np.asarray([[1,2,3],\n                [4,5,6]], dtype=np.uint8)\n
Run Code Online (Sandbox Code Playgroud)\n\n

底层数据(假设我们将数组分解为一维)已作为[1, 2, 3, 4, 5, 6]. 该数组具有 shape(2, 3)和 strides (3, 1)。您只需交换尺寸的步幅(以及长度)即可转置它。因此A.T,在 中,在内存中前进 3 个元素会将您置于新列中,而不是(像以前一样)新行中。

\n\n
[[1, 4],\n [2, 5],\n [3, 6]]\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我们想要解开转置(即重塑A.T为长度为 6 的一维数组),那么我们期望结果为[1 4 2 5 3 6]。但是,没有任何步幅允许我们按此特定顺序逐步遍历原始存储序列中的所有 6 个元素。因此,虽然A.T是视图,A.T.ravel()但将是副本(可以通过检查它们各自的.ctypes.data属性来确认)。

\n