Skip to content

NumPy : Règles et exemples de diffusion

Dans les opérations entre tableaux NumPy (ndarray), chaque forme est automatiquement convertie pour être la même par diffusion.

Cet article décrit le contenu suivant.

  • Règles de diffusion dans NumPy
  • Exemples de diffusion dans NumPy
    • Exemples de tableau 2D
    • Exemples de tableau 3D
  • Cas qui ne peuvent pas diffuser
  • Fonctions pour obtenir le tableau diffusé
    • Diffuser un tableau à une forme spécifiée. :np.broadcast_to()
    • Diffusez plusieurs baies :np.broadcast_arrays()

La documentation officielle expliquant la diffusion est ci-dessous.

Utilisez reshape() ou np.newaxis si vous souhaitez remodeler ndarray à la forme de votre choix.

Règles de diffusion dans NumPy

Il existe les deux règles suivantes pour la diffusion dans NumPy.

  1. Faites en sorte que les deux tableaux aient le même nombre de dimensions.
    • Si les nombres de dimensions des deux tableaux sont différents, ajoutez de nouvelles dimensions avec la taille 1 à la tête du tableau avec la plus petite dimension.
  2. Faites en sorte que chaque dimension des deux tableaux ait la même taille.
    • Si les tailles de chaque dimension des deux tableaux ne correspondent pas, les dimensions de taille 1 sont étirées à la taille de l’autre tableau.
    • S’il existe une dimension dont la taille n’est pas 1 dans l’un des deux tableaux, elle ne peut pas être diffusée et une erreur est générée.

Notez que le nombre de dimensions de ndarray peut être obtenu avec l’attribut ndim et la forme avec l’attribut shape.

Exemples de diffusion dans NumPy

Exemples de tableau 2D

Tableau 2D et tableau 1D

Les tableaux 2D et 1D suivants sont utilisés comme exemples. Pour faciliter la compréhension du résultat de la diffusion, l’un d’eux utilise zeros() pour mettre tous les éléments à 0.

import numpy as np

a = np.zeros((3, 3), np.int)
print(a)
# [[0 0 0]
#  [0 0 0]
#  [0 0 0]]

print(a.shape)
# (3, 3)

b = np.arange(3)
print(b)
# [0 1 2]

print(b.shape)
# (3,)

La forme du tableau 1D est (3,) au lieu de (3) car les tuples avec un élément ont une virgule à la fin.

Le résultat de l’addition de ces deux ndarray est le suivant.

print(a + b)
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

Transformons le tableau avec un plus petit nombre de dimensions (tableau 1D b) selon les règles décrites ci-dessus.

Tout d’abord, selon la règle 1, le tableau est transformé de la forme (3,) à (1, 3) en ajoutant une nouvelle dimension de taille 1 en tête. La méthode reshape() est utilisée.

b_1_3 = b.reshape(1, 3)
print(b_1_3)
# [[0 1 2]]

print(b_1_3.shape)
# (1, 3)

Ensuite, la taille de chaque dimension est étirée selon la règle 2. Le tableau est étiré de (1, 3) à (3, 3). La pièce étirée est une copie de la pièce d’origine. np.tile() est utilisé.

print(np.tile(b_1_3, (3, 1)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

Notez que reshape () et np.tile () sont utilisés ici à des fins d’explication, mais si vous souhaitez obtenir le tableau diffusé, il existe des fonctions np.broadcast_to () et np.broadcast_arrays () à cette fin. Voir ci-dessous.

Tableau 2D et tableau 2D

Le résultat de l’addition avec le tableau 2D de (3, 1) est le suivant.

b_3_1 = b.reshape(3, 1)
print(b_3_1)
# [[0]
#  [1]
#  [2]]

print(b_3_1.shape)
# (3, 1)

print(a + b_3_1)
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

Dans ce cas, puisque le nombre de dimensions est déjà le même, le tableau est étiré de (3, 1) à (3, 3) selon la règle 2.

print(np.tile(b_3_1, (1, 3)))
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

Dans les exemples précédents, un seul des tableaux est converti, mais il existe des cas où les deux sont convertis par diffusion.

Ce qui suit est le résultat de l’ajout de tableaux dont les formes sont (1, 3) et (3, 1).

print(b_1_3)
# [[0 1 2]]

print(b_1_3.shape)
# (1, 3)

print(b_3_1)
# [[0]
#  [1]
#  [2]]

print(b_3_1.shape)
# (3, 1)

print(b_1_3 + b_3_1)
# [[0 1 2]
#  [1 2 3]
#  [2 3 4]]

(1, 3) et (3, 1) sont étirés en (3, 3).

print(np.tile(b_1_3, (3, 1)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(np.tile(b_3_1, (1, 3)))
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

print(np.tile(b_1_3, (3, 1)) + np.tile(b_3_1, (1, 3)))
# [[0 1 2]
#  [1 2 3]
#  [2 3 4]]

Il en va de même si l’un d’eux est un tableau 1D.

c = np.arange(4)
print(c)
# [0 1 2 3]

print(c.shape)
# (4,)

print(b_3_1)
# [[0]
#  [1]
#  [2]]

print(b_3_1.shape)
# (3, 1)

print(c + b_3_1)
# [[0 1 2 3]
#  [1 2 3 4]
#  [2 3 4 5]]

Le tableau 1D est converti comme (4,) -> (1, 4) -> (3, 4) et le tableau 2D comme (3, 1) -> (3, 4).

print(np.tile(c.reshape(1, 4), (3, 1)))
# [[0 1 2 3]
#  [0 1 2 3]
#  [0 1 2 3]]

print(np.tile(b_3_1, (1, 4)))
# [[0 0 0 0]
#  [1 1 1 1]
#  [2 2 2 2]]

print(np.tile(c.reshape(1, 4), (3, 1)) + np.tile(b_3_1, (1, 4)))
# [[0 1 2 3]
#  [1 2 3 4]
#  [2 3 4 5]]

Notez que la dimension est étirée uniquement lorsque la taille d’origine est 1. Sinon, elle ne peut pas être diffusée et une erreur est générée, comme décrit ci-dessous.

Exemples de tableau 3D

La règle 1 s’applique même si la différence dans le nombre de dimensions est de deux ou plus.

En utilisant des tableaux 3D et 1D comme exemples, les résultats d’addition sont les suivants :

a = np.zeros((2, 3, 4), dtype=np.int)
print(a)
# [[[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]
# 
#  [[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]]

print(a.shape)
# (2, 3, 4)

b = np.arange(4)
print(b)
# [0 1 2 3]

print(b.shape)
# (4,)

print(a + b)
# [[[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]
# 
#  [[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]]

La forme est changée en (4, ) -> (1, 1, 4) -> (2, 3, 4).

b_1_1_4 = b.reshape(1, 1, 4)
print(b_1_1_4)
# [[[0 1 2 3]]]

print(np.tile(b_1_1_4, (2, 3, 1)))
# [[[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]
# 
#  [[0 1 2 3]
#   [0 1 2 3]
#   [0 1 2 3]]]

Cas qui ne peuvent pas diffuser

Comme mentionné ci-dessus, la dimension est étirée uniquement si la taille d’origine est 1. Si les tailles des dimensions sont différentes et que les tailles des deux tableaux ne sont pas 1, elles ne peuvent pas être diffusées et une erreur est générée.

a = np.zeros((4, 3), dtype=np.int)
print(a)
# [[0 0 0]
#  [0 0 0]
#  [0 0 0]
#  [0 0 0]]

print(a.shape)
# (4, 3)

b = np.arange(6).reshape(2, 3)
print(b)
# [[0 1 2]
#  [3 4 5]]

print(b.shape)
# (2, 3)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (4,3) (2,3) 

Il en va de même pour le cas suivant.

a = np.zeros((2, 3, 4), dtype=np.int)
print(a)
# [[[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]
# 
#  [[0 0 0 0]
#   [0 0 0 0]
#   [0 0 0 0]]]

print(a.shape)
# (2, 3, 4)

b = np.arange(3)
print(b)
# [0 1 2]

print(b.shape)
# (3,)

# print(a + b)
# ValueError: operands could not be broadcast together with shapes (2,3,4) (3,) 

Dans cet exemple, si une nouvelle dimension est ajoutée à la fin, le tableau peut être diffusé.

b_3_1 = b.reshape(3, 1)
print(b_3_1)
# [[0]
#  [1]
#  [2]]

print(b_3_1.shape)
# (3, 1)

print(a + b_3_1)
# [[[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]
# 
#  [[0 0 0 0]
#   [1 1 1 1]
#   [2 2 2 2]]]

Il est facile de comprendre s’il peut être diffusé ou non par une forme alignée à droite.

NG
(2, 3, 4)
(      3)

OK
(2, 3, 4)
(   3, 1) -> (1, 3, 1) -> (2, 3, 4)

Si les tailles sont différentes lorsqu’elles sont alignées à droite et comparées verticalement, l’une d’elles doit être 1 pour être diffusée.

Par exemple, dans le cas des images, une image couleur est un tableau 3D dont la forme est (hauteur, largeur, 3) (3 signifie rouge, vert et bleu), tandis qu’une image en niveaux de gris est un tableau 2D dont la forme est (hauteur , largeur).

Dans le cas du calcul de la valeur de chaque couleur dans une image couleur et de la valeur d’une image en niveaux de gris, il est impossible de diffuser même si la hauteur et la largeur sont identiques.

Vous devez ajouter une dimension à la fin de l’image en niveaux de gris avec np.newaxis, np.expand_dims(), etc.

NG
(h, w, 3)
(   h, w)

OK
(h, w, 3)
(h, w, 1) -> (h, w, 3)

Fonctions pour obtenir le tableau diffusé

Diffuser un tableau à une forme spécifiée. :np.broadcast_to()

Utilisez np.broadcast_to() pour diffuser ndarray avec la forme spécifiée.

Le premier argument est le ndarray d’origine et le second est un tuple ou une liste indiquant la forme. Le ndarray diffusé est renvoyé.

a = np.arange(3)
print(a)
# [0 1 2]

print(a.shape)
# (3,)

print(np.broadcast_to(a, (3, 3)))
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(type(np.broadcast_to(a, (3, 3))))
# <class 'numpy.ndarray'>

Une erreur se produit lors de la spécification d’une forme qui ne peut pas être diffusée.

# print(np.broadcast_to(a, (2, 2)))
# ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,) and requested shape (2,2)

Diffusez plusieurs baies :np.broadcast_arrays()

Utilisez np.broadcast_arrays() pour diffuser plusieurs ndarray.

Spécifiez plusieurs tableaux séparés par des virgules. Une liste de ndarray est renvoyée.

a = np.arange(3)
print(a)
# [0 1 2]

print(a.shape)
# (3,)

b = np.arange(3).reshape(3, 1)
print(b)
# [[0]
#  [1]
#  [2]]

print(b.shape)
# (3, 1)

arrays = np.broadcast_arrays(a, b)

print(type(arrays))
# <class 'list'>

print(len(arrays))
# 2

print(arrays[0])
# [[0 1 2]
#  [0 1 2]
#  [0 1 2]]

print(arrays[1])
# [[0 0 0]
#  [1 1 1]
#  [2 2 2]]

print(type(arrays[0]))
# <class 'numpy.ndarray'>

Une erreur se produit lors de la spécification d’une combinaison de tableaux qui ne peuvent pas être diffusés.

c = np.zeros((2, 2))
print(c)
# [[0. 0.]
#  [0. 0.]]

print(c.shape)
# (2, 2)

# arrays = np.broadcast_arrays(a, c)
# ValueError: shape mismatch: objects cannot be broadcast to a single shape