6.5. Şekil ve adımlar

Bir ndarray içindeki veri, sayılardan oluşan tek bir sıkıştırılmış bloktur. Bu bloğun önündeki tanımlayıcı, bu düz bloğun bir tensör olarak nasıl okunacağına karar verir.

6.5.1. Tanımlayıcının kaydettikleri

Veri bloğunun bir tensör olarak nasıl okunacağını beş değer tanımlar:

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint8)

a.ndim       # 2     - number of dimensions
a.shape      # (2, 3)- length along each dimension
a.itemsize   # 1     - bytes per element (from dtype)
a.size       # 6     - total number of elements
a.strides    # (3, 1)- step pattern through the buffer

ndinfo() yardımcısı, bunların tümünü ve alttaki arabelleğin konumunu tek bir çağrıda yazdırır. Arabellek konumları eşleşen iki dizi belleği paylaşıyor demektir:

np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8

6.5.2. Adımların açıklaması

Bir adım (stride), bir öğeyi belirli bir eksen boyunca bir adım ilerletmek için veri bloğunda kaç bayt atlanacağıdır. Yukarıdaki 2x3 uint8 dizisi için adımlar (3, 1) şeklindedir: bir satır aşağı inmek 3 bayt atlar, bir sütun sağa gitmek 1 bayt atlar. Bu, satırların soldan sağa, art arda saklandığını söylemekle aynı şeydir:

memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
          ^ row 0          ^ row 1
          <------- 3 bytes ---->

a[i, j] değerini okumak için numpy, veri bloğunun başlangıcından itibaren i * strides[0] + j * strides[1] değerini hesaplar ve oradan itemsize bayt okur. Aynı formül herhangi bir sayıdaki boyuta genişler.

Satırların art arda saklandığı, son eksenin bellek boyunca en hızlı değiştiği bu yerleşime satır-öncelikli (row-major) sıra denir. numpy kütüphanesinin kamerada ayırdığı her dizi bu yerleşimi kullanır.

6.5.3. Satır-öncelikli yerleşimin sonuçları vardır

“Satırların art arda saklanması” durumundan, kamerada bir arabelleği şekillendirirken önem taşıyan iki şey çıkar.

Son eksen bitişiktir. a[0, 0] öğesinden a[0, 1] öğesine ilerlemek, bir sonraki bayta dokunur. a[0, 0] öğesinden a[1, 0] öğesine ilerlemek ise bütün bir satırın üzerinden atlar.

Son eksen, tüm dizi üzerindeki işlemler için hızlı eksendir. Kameradaki numpy, hangi eksenin daha uzun olduğuna bakmaksızın son ekseni daima en içte dolaşır. Masaüstü numpy kütüphanesi en uzun ekseni en içe koymak için döngülerini sessizce yeniden sıralar; kamera bunu yapmaz, dolayısıyla masaüstü numpy kütüphanesinin üstünü örteceği bir yerleşim seçimi burada hâlâ zaman maliyetine yol açar. np.sum(m, axis=1) son ekseni daraltır ve bitişik yönde çalışır; np.sum(m, axis=0) ise çalışmaz. Uygulamanın bir arabelleği nasıl yerleştireceği konusunda bir seçeneği olduğunda, uzun ekseni en sona koyun ki bu eksen boyunca işlemler iç döngüde kalsın.

Yerleşim baştan yanlışsa, transpose() (veya .T kısayolu) bunu veriyi kopyalamadan düzeltir – yalnızca adımları takas eder:

a = b.T            # now iterates fast

Performans performans konusunun tamamını ele alır.

6.5.4. Reshape, transpose, dilimleme – tanımlayıcı düzenlemeleri

Yalnızca tanımlayıcıyı yeniden yazan herhangi bir işlem bedavadır. reshape, aynı veri bloğu üzerinde yeni bir shape ve strides takas eder. transpose adımları tersine çevirir. a[::2] bir adımı ikiye katlar. Her biri aynı alttaki arabelleğin bir görünümünü döndürür.

Veriyi dolaşıp yeni bir arabellek yazmak zorunda olan her şey bir kopyadır. Şimdilik kural şudur: tanımlayıcı düzenlemeleri bedavadır, veri dolaşmaları değildir.

6.5.5. ndim hakkında bir not

Kameradaki numpy, desteklenen en fazla ndim değeri 4 olacak şekilde derlenmiştir. Daha yüksek dereceli bir dizi üretecek işlemler ValueError yükseltir. Kamera tarafındaki işlerin büyük çoğunluğu 1 boyutlu veya 2 boyutludur, dolayısıyla bu sınır nadiren sorun olur.