6.5. Forme et pas (strides)

Les données à l’intérieur d’un ndarray constituent un bloc compact de nombres. Le descripteur placé devant ce bloc décide comment ce bloc plat est lu comme un tenseur.

6.5.1. Ce que le descripteur enregistre

Cinq valeurs décrivent comment lire le bloc de données comme un tenseur

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

L’utilitaire ndinfo() les affiche toutes ainsi que l’emplacement du tampon sous-jacent en un seul appel. Deux tableaux dont les emplacements de tampon correspondent partagent la mémoire

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

6.5.2. Les pas (strides) expliqués

Un pas (stride) est le nombre d’octets à franchir dans le bloc de données pour avancer d’un élément le long d’un axe donné. Pour le tableau uint8 2x3 ci-dessus, les pas sont (3, 1) : descendre d’une ligne saute 3 octets, avancer d’une colonne saute 1 octet. Cela revient à dire que les lignes sont stockées bout à bout, de gauche à droite

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

Pour lire a[i, j], numpy calcule i * strides[0] + j * strides[1] depuis le début du bloc de données et lit itemsize octets à partir de là. La même formule s’étend à un nombre quelconque de dimensions.

Cette disposition – lignes stockées bout à bout, le dernier axe variant le plus vite en mémoire – est appelée ordre row-major (par lignes). Chaque tableau que numpy alloue sur la caméra utilise cette disposition.

6.5.3. L’ordre row-major a des conséquences

Deux choses découlent du « lignes stockées bout à bout » qui comptent lorsqu’on façonne un tampon sur la caméra.

Le dernier axe est contigu. Passer de a[0, 0] à a[0, 1] touche l’octet suivant. Passer de a[0, 0] à a[1, 0] saute par-dessus toute une ligne.

Le dernier axe est l’axe rapide pour les calculs sur tableaux entiers. numpy sur la caméra parcourt toujours le dernier axe à l’intérieur, quel que soit l’axe le plus long. La bibliothèque numpy de bureau réordonne silencieusement ses boucles pour placer l’axe le plus long à l’intérieur ; la caméra ne le fait pas, donc un choix de disposition que numpy de bureau aurait masqué coûte tout de même du temps ici. np.sum(m, axis=1) réduit le dernier axe et s’exécute dans la direction contiguë ; np.sum(m, axis=0) non. Lorsque l’application a le choix de la manière de disposer un tampon, placez l’axe long en dernier afin que les opérations le long de celui-ci restent dans la boucle interne.

Si la disposition est mauvaise au départ, transpose() (ou le raccourci .T) la corrige sans copier les données – elle se contente d’échanger les pas

a = b.T            # now iterates fast

Performances contient la discussion complète sur les performances.

6.5.4. Reshape, transpose, tranchage – modifications du descripteur

Toute opération qui ne fait que réécrire le descripteur est gratuite. reshape échange un nouveau shape et de nouveaux strides sur le même bloc de données. transpose inverse les pas. a[::2] double un pas. Chacune renvoie une vue du même tampon sous-jacent.

Tout ce qui doit parcourir les données et écrire un nouveau tampon est une copie. La règle pour l’instant est que les modifications du descripteur sont gratuites et que les parcours de données ne le sont pas.

6.5.5. Une note à propos de ndim

numpy sur la caméra est compilé avec un ndim maximal pris en charge de 4. Les opérations qui produiraient un tableau de rang supérieur lèvent ValueError. La grande majorité du travail côté caméra est en 1-D ou 2-D, donc cette limite pose rarement problème.