6.5. Alak és lépésközök

Az ndarray belsejében lévő adatok egyetlen tömörített számtömböt alkotnak. Az e blokk előtt álló leíró dönti el, hogyan olvasandó ki ez a lapos blokk tenzorként.

6.5.1. Mit rögzít a leíró

Öt érték írja le, hogyan kell az adatblokkot tenzorként olvasni:

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

A ndinfo() segédfüggvény egyetlen hívással kiírja mindegyiket, valamint az alapul szolgáló puffer helyét. Két tömb, amelynek a pufferhelye megegyezik, megosztja a memóriát:

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

6.5.2. A lépésközök magyarázata

Egy lépésköz azt mondja meg, hány bájtot kell lépni az adatblokkban ahhoz, hogy egy adott tengely mentén egy elemmel arrébb lépjünk. A fenti 2x3-as uint8 tömbnél a lépésközök (3, 1): egy sorral lejjebb lépve 3 bájtot ugrunk, egy oszloppal jobbra lépve 1 bájtot. Ez ugyanazt jelenti, mintha azt mondanánk, hogy a sorok közvetlenül egymás után, balról jobbra tárolódnak:

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

Az a[i, j] kiolvasásához a numpy kiszámítja az i * strides[0] + j * strides[1] értéket az adatblokk elejétől, és onnan itemsize bájtot olvas. Ugyanez a képlet tetszőleges számú dimenzióra kiterjeszthető.

Ezt az elrendezést – ahol a sorok egymás után tárolódnak, és az utolsó tengely változik leggyorsabban a memóriában – sorfolytonos (row-major) sorrendnek nevezzük. Minden tömb, amelyet a numpy a kamerán foglal, ezt az elrendezést használja.

6.5.3. A sorfolytonosságnak következményei vannak

A „sorok egymás után tárolva” elvből két dolog következik, ami számít, amikor egy puffert alakítasz a kamerán.

Az utolsó tengely összefüggő. Az a[0, 0]-ból az a[0, 1]-be lépve a következő bájtot érintjük. Az a[0, 0]-ból az a[1, 0]-ba lépve egy egész soron ugrunk át.

Az utolsó tengely a gyors tengely a teljes tömbös matematikához. A numpy a kamerán mindig az utolsó tengelyt járja be legbelül, függetlenül attól, melyik tengely a hosszabb. Az asztali numpy könyvtár csendben átrendezi a ciklusait, hogy a leghosszabb tengelyt tegye legbelülre; a kamera ezt nem teszi, így egy olyan elrendezésbeli döntés, amelyet az asztali numpy elsimított volna, itt mégis időbe kerül. Az np.sum(m, axis=1) az utolsó tengelyt vonja össze, és az összefüggő irányban fut; az np.sum(m, axis=0) nem. Ha az alkalmazás szabadon dönthet egy puffer elrendezéséről, tedd a hosszú tengelyt utolsónak, hogy a mentén végzett műveletek a belső ciklusban maradjanak.

Ha az elrendezés eleve hibás, a transpose() (vagy a .T rövidítés) az adatok másolása nélkül javítja – pusztán felcseréli a lépésközöket:

a = b.T            # now iterates fast

A Teljesítmény oldalon található a teljes teljesítményről szóló tárgyalás.

6.5.4. Reshape, transzponálás, szeletelés – leíró-szerkesztések

Minden olyan művelet ingyenes, amely csak a leírót írja át. A reshape egy új shape és strides párost cserél fel ugyanazon adatblokk fölött. A transpose megfordítja a lépésközöket. Az a[::2] megduplázza egy lépésközt. Mindegyik ugyanazon alapul szolgáló puffer egy nézetét adja vissza.

Bármi, aminek be kell járnia az adatokat, és egy új puffert kell írnia, az másolat. Egyelőre a szabály az, hogy a leíró-szerkesztések ingyenesek, az adatbejárások nem.

6.5.5. Megjegyzés az ndim-ről

A numpy a kamerán legfeljebb 4-es ndim támogatásával van megépítve. Az olyan műveletek, amelyek magasabb rangú tömböt eredményeznének, ValueError kivételt váltanak ki. A kameraoldali munka túlnyomó többsége 1 vagy 2 dimenziós, így ez a korlát ritkán jelent problémát.