Bitişik ve bitişik olmayan diziler arasındaki fark nedir?


110

Gelen numpy kılavuzda yenidenbiçimleme () işlevi hakkında, diyor

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

Sorularım:

  1. Sürekli ve bitişik olmayan diziler nelerdir? C'deki bitişik bellek bloğuna benzer mi Bitişik bellek bloğu nedir?
  2. Bu ikisi arasında performans farkı var mı? Birini veya diğerini ne zaman kullanmalıyız?
  3. Neden transpoze diziyi bitişik olmayan yapar?
  4. Neden c.shape = (20)hata veriyor incompatible shape for a non-contiguous array?

Cevabınız için teşekkürler!

Yanıtlar:


235

Bitişik bir dizi, yalnızca kesintisiz bir bellek bloğunda depolanan bir dizidir: dizideki bir sonraki değere erişmek için, yalnızca bir sonraki bellek adresine gidiyoruz.

2B diziyi düşünün arr = np.arange(12).reshape(3,4). Şöyle görünüyor:

görüntü açıklamasını buraya girin

Bilgisayarın belleğinde, değerleri arrşu şekilde saklanır:

görüntü açıklamasını buraya girin

Bu arr, C bitişik dizisidir çünkü satırlar bitişik bellek blokları olarak depolanır. Bir sonraki bellek adresi, o satırdaki bir sonraki satır değerini tutar. Bir sütunda aşağı inmek istiyorsak, sadece üç bloğun üzerinden atlamamız gerekir (örneğin, 0'dan 4'e atlamak 1,2 ve 3'ü atladığımız anlamına gelir).

Dizinin yerini değiştirmek, arr.Tbitişik satır girişleri artık bitişik bellek adreslerinde olmadığından C bitişikliğinin kaybolduğu anlamına gelir. Bununla birlikte, arr.Tbir Fortran bitişik yana sütun bellek bitişik bloklar vardır:

görüntü açıklamasını buraya girin


Performans açısından, yan yana olan bellek adreslerine erişmek, daha "yayılmış" adreslere erişmekten çok daha hızlıdır (RAM'den bir değer almak, CPU için bir dizi komşu adresin alınmasını ve önbelleğe alınmasını gerektirebilir.) bitişik diziler üzerindeki işlemlerin genellikle daha hızlı olacağı anlamına gelir.

C bitişik bellek düzeninin bir sonucu olarak, satır tabanlı işlemler genellikle sütun tabanlı işlemlerden daha hızlıdır. Örneğin, genellikle şunu bulursunuz

np.sum(arr, axis=1) # sum the rows

şundan biraz daha hızlıdır:

np.sum(arr, axis=0) # sum the columns

Benzer şekilde, Fortran bitişik dizileri için sütunlar üzerindeki işlemler biraz daha hızlı olacaktır.


Son olarak, neden yeni bir şekil atayarak Fortran bitişik dizisini düzleştiremiyoruz?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array

Bunun mümkün olması için NumPy'nin şu şekilde sıraları bir arr.Taraya getirmesi gerekir :

görüntü açıklamasını buraya girin

(Ayarlamak shape doğrudan C sırasını varsayar - yani NumPy işlemi satır bazında gerçekleştirmeye çalışır.)

Bunu yapmak imkansız. Herhangi bir eksen için NumPy'nin dizinin bir sonraki öğesine ulaşmak için sabit bir adım uzunluğuna (taşınacak bayt sayısı) sahip olması gerekir. arr.TBu şekilde düzleştirme , dizinin ardışık değerlerini almak için bellekte ileri ve geri atlamayı gerektirir.

Diye yazarsak arr2.reshape(12)yerine (bu şekil için orijinal verilere bir görünüm döndüremez beri), NumPy bellek yeni bir bloğa ARR2 değerlerini kopyalamak olacaktır.


Bunu anlamakta güçlük çekiyorum, biraz detaylandırır mısınız? Hafızadaki imkansız sıralamanın en son grafik sunumunda aslında adımlar bence sabit. Örneğin 0'dan 1'e gitmek için adım 1 bayttır (diyelim ki her eleman bir bayttır) ve her sütun için aynıdır. Aynı şekilde, sıradaki bir öğeden diğerine gitmek için adım 4 bayttır ve aynı zamanda sabittir.
Vesnog

3
@Vesnog, 2B'nin arr21D şekline başarısız yeniden şekillendirilmesi (12,)için C sırasını kullanır, yani eksen 1, eksen 0'dan önce çözülür (yani, istenen 1D dizisini oluşturmak için dört satırın her birinin yan yana yerleştirilmesi gerekir). Bu tamsayı dizisini (0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11) sabit bir adım uzunluğu (ziyarete atlanacak baytlar) kullanarak tampondan okumak imkansızdır. sırayla bu elemanlar 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4 olacaktır). NumPy, eksen başına bir sabit adım uzunluğu gerektirir.
Alex Riley

İlk başta teşekkürler, yeni bir dizi oluşturacağını düşünmüştüm ama eskisinin hafızasını kullanıyor.
Vesnog

@AlexRiley Bir dizi C veya F komutuyla işaretlendiğinde perde arkasında ne olur? Örneğin, her NxD dizi dizisini alın ve yazdırın (arr [:, :: - 1] .flags). Bu durumda ne olur? Sanırım dizi gerçekten C veya F şeklinde, ama hangisi? Ve her iki işaret de Yanlış ise hangi hissizlik optimizasyonlarını kaybediyoruz?
Jjang

@Jjang: NumPy'nin dizinin C mi yoksa F mi olduğunu düşünmesi tamamen şekle ve adımlara bağlıdır (kriterler burada ). Öyleyse arr[:, ::-1], aynı bellek tamponunun bir görünümü olsa da arr, NumPy, tampondaki değerleri "standart olmayan" bir sırada geçtiği için C veya F sırasını dikkate almaz ...
Alex Riley

13

Belki 12 farklı dizi değerine sahip bu örnek yardımcı olacaktır:

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 orderDeğerler onlar üretildi sırayla vardır. Aktarılmış olanlar değildir

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

Her ikisinin de 1d görünümünü alabilirsiniz

In [214]: x1=x.T

In [217]: x.shape=(12,)

şekli xde değiştirilebilir.

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

Ancak devrik şekli değiştirilemez. dataHala 0,1,2,3,4...olarak erişilebilir erişilemiyor için, 0,4,8...bir 1d dizi.

Ancak bir kopyası x1değiştirilebilir:

In [227]: x2=x1.copy()

In [228]: x2.flags
Out[228]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [229]: x2.shape=(12,)

Bakmak stridesda yardımcı olabilir. Adımlar, bir sonraki değere ulaşmak için ne kadar uzağa (bayt cinsinden) ulaşması gerektiğidir. 2 boyutlu bir dizi için 2 adım değeri olacaktır:

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)

Sonraki satıra geçmek için, adım 16 bayt, yalnızca sonraki sütun 4.

In [235]: x1.strides
Out[235]: (4, 16)

Transpoze sadece adımların sırasını değiştirir. Bir sonraki satır sadece 4 bayttır - yani bir sonraki sayı.

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)

Şekli değiştirmek aynı zamanda adımları da değiştirir - sadece bir seferde 4 bayt arabelleğe adım atın.

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)

Olsa x2tıpkı görünüyor x1, farklı bir sırayla değerlerle kendi veri tamponunu sahiptir. Bir sonraki sütun 4 bayt fazla iken, sonraki satır 12'dir (3 * 4).

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

Ve olduğu gibi x , şekli 1d değiştirmek, adımların sayısını azaltır (4,).

Sıradaki x1verilerle, 0,1,2,...verebilecek 1d'lik bir adım yoktur.0,4,8... .

__array_interface__ dizi bilgilerini görüntülemenin başka bir kullanışlı yoludur:

In [242]: x1.__array_interface__
Out[242]: 
{'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'version': 3,
 'data': (163336056, False),
 'descr': [('', '<i4')]}

x1Veri ara bellek adresi için aynı olacaktırx veriyi paylaştığı. x2farklı bir tampon adresine sahiptir.

Ayrıca ve komutlarına bir order='F'parametre eklemeyi deneyebilirsiniz .copyreshape

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.