Pertanyaan Kapan numpy menyalin array saat menggunakan reshape ()


Dalam dokumen numpy.reshape, ia mengatakan:

Ini akan menjadi objek tampilan baru jika memungkinkan; jika tidak, itu akan menjadi salinan. Perhatikan tidak ada jaminan dari susunan memori (C- atau Fortran-contiguous) dari array yang dikembalikan.

Pertanyaan saya adalah, kapan numpy memilih untuk mengembalikan tampilan baru, dan kapan menyalin seluruh array? Apakah ada prinsip umum yang memberi tahu orang tentang perilaku reshape, atau itu tidak dapat diprediksi? Terima kasih.


5
2018-05-03 03:50


asal


Jawaban:


Tautan yang ditemukan @mgillson muncul untuk menjawab pertanyaan tentang 'bagaimana cara mengetahui apakah itu membuat salinan', tetapi tidak 'bagaimana cara memperkirakannya' atau memahami mengapa itu membuat salinannya. Untuk ujian, saya suka menggunakannya A.__array_interfrace__.

Kemungkinan besar ini akan menjadi masalah jika Anda mencoba untuk menetapkan nilai ke array dibentuk kembali, mengharapkan untuk juga mengubah aslinya. Dan saya akan sulit sekali menemukan SO case di mana itu masalahnya.

Salinan menyalin akan menjadi sedikit lebih lambat daripada yang non-copy, tetapi sekali lagi saya tidak bisa memikirkan sebuah kasus di mana yang menghasilkan perlambatan seluruh kode. Salinan juga bisa menjadi masalah jika Anda bekerja dengan array begitu besar sehingga operasi paling sederhana menghasilkan kesalahan memori.


Setelah membentuk kembali nilai-nilai dalam buffer data harus dalam urutan yang berdekatan, baik 'C' atau 'F'. Sebagai contoh:

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]])

Ini akan melakukan salinan jika urutan awal sangat 'kacau' sehingga tidak dapat mengembalikan nilai-nilai seperti ini. Membentuk kembali setelah melakukan transpose dapat melakukan ini (lihat contoh saya di bawah). Mungkin game dengan stride_tricks.as_strided. Di luar itu adalah satu-satunya kasus yang dapat saya pikirkan.

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)}

y, transpose, memiliki pointer 'data' yang sama. Transpose dilakukan tanpa mengubah atau menyalin data, itu hanya menciptakan objek baru dengan yang baru shape, strides, dan flags.

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

In [410]: x.flags
Out[410]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  ...

y adalah pesanan 'F'. Sekarang cobalah membentuknya kembali

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]])

z adalah salinan, itu data penunjuk buffer berbeda. Nilai-nilainya tidak diatur dengan cara apa pun yang menyerupai itu x atau y, tidak 0,1,2,....

Tapi hanya membentuk kembali x tidak menghasilkan salinan:

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)}

Raveling y sama dengan y.reshape(-1); ini menghasilkan sebagai salinan:

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)

Menetapkan nilai ke array yang tercerabut seperti ini mungkin merupakan kasus yang paling mungkin di mana salinan akan menghasilkan kesalahan. Sebagai contoh, x.ravel()[::2]=99 mengubah setiap nilai lainnya x dan y (kolom dan baris masing-masing). Tapi y.ravel()[::2]=0 tidak melakukan apa-apa karena penyalinan ini.

Jadi membentuk ulang setelah transpose adalah skenario salinan yang paling mungkin. Saya akan senang menjelajahi kemungkinan lain.

edit:  y.reshape(-1,order='F')[::2]=0 tidak mengubah nilai-nilai y. Dengan perintah yang kompatibel, membentuk ulang tidak menghasilkan salinan.


Satu jawaban di tautan @ mgillson, https://stackoverflow.com/a/14271298/901925, menunjukkan bahwa A.shape=... sintaks mencegah penyalinan. Jika tidak dapat mengubah bentuk tanpa menyalinnya akan memunculkan kesalahan:

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

Ini juga disebutkan dalam reshape dokumentasi

Jika Anda ingin kesalahan dinaikkan jika data disalin,   Anda harus menetapkan bentuk baru ke atribut bentuk dari array ::


SO pertanyaan tentang membentuk kembali berikut as_strided:

membentuk kembali tampilan array n-dimensi tanpa menggunakan perombakan

dan

Numpy View Reshape Without Copy (2d Moving / Sliding Window, Strides, Struktur Memori Masked)

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

Inilah potongan pertama saya dalam menerjemahkan shape.c/_attempt_nocopy_reshape menjadi Python. Ini dapat dijalankan dengan sesuatu seperti:

newstrides = attempt_reshape(numpy.zeros((3,4)), (4,3), False)

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

2
2018-05-03 04:26



@hoaulj memberikan jawaban yang bagus, tetapi ada kesalahan dalam pelaksanaannya _attempt_nocopy_reshape fungsi. Jika pembaca akan mencatat, di baris ke-4 kode-nya

newstrides = numpy.zeros(newnd+1).tolist()  # +1 is a fudge

ada faktor fudge. Peretasan ini hanya berfungsi dalam situasi tertentu (dan fungsi berhenti pada masukan tertentu). Peretasan diperlukan, karena ada kesalahan saat menambah dan menyetel ni, nj, oi, oj di ujung lingkaran terluar. Pembaruan harus dibaca

ni = nj;nj += 1; 
oi = oj;oj += 1;

Saya pikir kesalahan itu terjadi, karena dalam kode asli (di gokuk resmi numpy), itu diterapkan

 ni = nj++;
 oi = oj++;

menggunakan post-increment, sementara @hoaulj menerjemahkannya, seolah-olah pre-increment digunakan, yaitu ++nj.

Untuk kelengkapan, saya melampirkan kode yang dikoreksi di bawah ini. Saya harap ini bisa membuka kemungkinan kebingungan.

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).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):
        np = newdims[ni];
        op = olddims[oi];
        while (np != op):
            print(ni,oi,np,op,nj,oj)
            if (np < op):
                # /* Misses trailing 1s, these are handled later */
                np *= newdims[nj];
                nj += 1
            else:
                op *= olddims[oj];
                oj += 1

        #/* 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];
        ni = nj;nj += 1; 
        oi = oj;oj += 1;   

    # * Set strides corresponding to trailing 1s of the new shape.
    if (ni >= 1) :
        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

newstrides = attempt_reshape(numpy.zeros((5,3,2)), (10,3), False)

print(newstrides)

1
2017-09-28 21:07