6.10. Diffusion (broadcasting)¶
Lorsqu’un opérateur binaire reçoit deux tableaux dont les formes ne correspondent pas exactement, numpy ne lève pas d’erreur – il diffuse. La diffusion est un petit ensemble de règles qui déterminent si deux formes sont compatibles et, si elles le sont, comment la plus petite est virtuellement étirée pour correspondre à la plus grande.
6.10.1. Les règles¶
Lorsque les deux opérandes ont les formes A et B, numpy les traite en deux étapes.
Faire correspondre les rangs. Si un opérande a moins d’axes que l’autre,
numpycomplète virtuellement l’avant de sa forme avec des axes de taille 1 jusqu’à ce que les deux formes aient le même nombre d’axes. Un opérande 1D de forme(3,)associé à un opérande 2D de forme(2, 3)devient(1, 3)face à(2, 3).Vérifier chaque axe. En parcourant axe par axe les formes désormais de même longueur, chaque paire de tailles doit satisfaire l’une de deux conditions : les tailles sont égales, ou l’une d’elles vaut 1. Un axe de taille 1 est virtuellement étiré à la taille de l’autre côté pour l’opération. La paire
(1, 3)face à(2, 3)est compatible car le premier axe a un 1 (s’étire à 2) et le second axe correspond (3 == 3) ; le résultat a la forme(2, 3).
Si une paire d’axes ne satisfait aucune des deux conditions, les formes sont incompatibles et l’opérateur lève ValueError.
6.10.2. Exemples¶
Scalaire face à n’importe quel tableau. Le scalaire se comporte comme la forme (1,) et s’étire à n’importe quoi
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10 # (2, 3) + scalar -> (2, 3)
Vecteur 1D sur une matrice 2D. La règle 1 préfixe un axe de taille 1 pour transformer (3,) en (1, 3) ; la règle 2 étire ensuite cette ligne le long de chaque colonne de a
row = np.array([100, 200, 300], dtype=np.float)
a + row # (2, 3) + (3,) -> (2, 3)
Deux tableaux 1D de même longueur s’additionnent élément par élément – aucune diffusion nécessaire
np.arange(4) + np.arange(4)
Vecteur colonne face à un vecteur ligne produit une forme « externe » 2D : (4, 1) associé à (3,) devient (4, 1) face à (1, 3) après le préfixage de rang, et la règle 2 étire chaque opérande le long de son axe de taille 1
x = np.array([1, 2, 3, 4]).reshape((4, 1)) # column
y = np.array([10, 20, 30]) # row
x + y # (4, 3) matrix
Les mêmes règles de forme s’appliquent à toute ufunc à deux arguments, y compris arctan2()
np.arctan2(y, 1.0)
np.arctan2(y, x)
6.10.3. Ce que la diffusion n’alloue pas¶
L’étirement est virtuel. numpy parcourt les deux opérandes ensemble, relisant le plus petit le long de son axe de diffusion au lieu de le copier. Les données du tableau le plus court ne sont jamais répliquées en mémoire.
C’est la taille du tableau de sortie qui compte pour la mémoire. a + row alloue une sortie de la forme de a, et non de la forme de a plus celle de row. De longues chaînes de diffusion peuvent tout de même produire de grands intermédiaires.
6.10.4. Quand la diffusion tourne mal¶
L’échec classique est celui de deux formes où aucune n’a d’axe de taille 1 à étirer et où les tailles divergent – (3, 4) face à (4, 3), par exemple. La règle 2 ne peut pas faire correspondre un 3 à un 4, donc numpy lève ValueError.
Un problème plus subtil est une diffusion qui réussit, mais pas de la manière voulue par l’application. (5,) face à (5, 1) est le cas canonique : le préfixage de rang transforme le (5,) en (1, 5), qui se diffuse face à (5, 1) pour produire une matrice (5, 5) – la combinaison externe des deux vecteurs, et non le résultat élément par élément de longueur 5 que l’application souhaitait probablement. En cas de doute, affichez shape des deux côtés et déroulez les règles avant de recourir à reshape() ou transpose().