Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 35

Array Broadcasting Fundamentals

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons découvert la puissance des opérations vectorisées de NumPy, qui permettent d'effectuer des calculs sur des tableaux entiers de manière extrêmement rapide. Nous avons vu comment cela fonctionne pour des tableaux de même taille et avons eu un aperçu du broadcasting, le mécanisme que NumPy utilise pour les tableaux de tailles différentes.

Aujourd'hui, nous allons nous plonger au cœur de ce mécanisme. L'objectif de cette leçon est d'expliquer et d'appliquer les règles de broadcasting pour les opérations sur des tableaux de formes différentes. Maîtriser ces règles est fondamental, car cela vous permettra d'écrire du code non seulement plus rapide, mais aussi beaucoup plus concis et lisible pour des tâches complexes, une compétence essentielle en machine learning.

1. Qu'est-ce que le Broadcasting ?

Le broadcasting est un ensemble de règles strictes que NumPy applique pour effectuer des opérations arithmétiques (comme l'addition, la soustraction, etc.) sur des tableaux de formes différentes. Au lieu de retourner une erreur, NumPy essaie de "diffuser" ou "d'étirer" virtuellement le plus petit tableau pour qu'il corresponde à la forme du plus grand.

Le cas le plus simple, que nous avons vu la dernière fois, est l'opération entre un tableau et un scalaire (un seul nombre).

import numpy as np

a = np.array([0, 1, 2])
result = a + 5  # Le scalaire 5 est "diffusé" sur chaque élément de 'a'
print(result)   # Affiche [5 6 7]

Conceptuellement, on peut imaginer que NumPy transforme 5 en un tableau [5, 5, 5] pour que les formes correspondent. L'avantage est que cette duplication n'a pas lieu en mémoire ; c'est une optimisation qui rend le calcul très efficace. Mais ce modèle mental de "stretching" est très utile pour comprendre des cas plus complexes.


2. Les Règles du Broadcasting

Pour que le broadcasting fonctionne, les formes des tableaux doivent être "compatibles". NumPy suit trois règles simples pour déterminer la compatibilité et la forme du résultat. Pour comparer les formes de deux tableaux (par exemple, (4, 3) et (3,)), NumPy commence par la dernière dimension (la plus à droite) et remonte vers la gauche.

Les règles sont les suivantes :

  1. Règle 1 : Compléter les dimensions. Si les deux tableaux n'ont pas le même nombre de dimensions, NumPy ajoute des 1 au début (à gauche) de la forme du tableau qui en a le moins, jusqu'à ce que les deux formes aient le même nombre de dimensions.

    • a.shape = (2, 3) et b.shape = (3,)
    • NumPy traite b comme ayant la forme (1, 3).
  2. Règle 2 : Étirer les dimensions de taille 1. Pour chaque dimension, si la taille de la dimension d'un tableau est 1 et celle de l'autre est supérieure à 1, NumPy "étire" la dimension de taille 1 pour qu'elle corresponde à l'autre.

    • a.shape = (2, 3) et b.shape = (1, 3)
    • NumPy étire la première dimension de b de 1 à 2. La forme finale de b pour le calcul est (2, 3).
  3. Règle 3 : Incompatibilité. Si, dans n'importe quelle dimension, les tailles sont différentes et qu'aucune n'est égale à 1, une erreur ValueError est levée. Le broadcasting est impossible.

    • a.shape = (3, 2) et b.shape = (3,)
    • Après la Règle 1, les formes sont (3, 2) et (1, 3).
    • Pour la dernière dimension, les tailles 2 et 3 sont différentes et aucune n'est 1. C'est une erreur.

Le Python Data Science Handbook fournit une excellente référence pour ces règles. Je vous invite à lire la section qui les présente formellement.

Computation on Arrays: Broadcasting

Cet extrait explique de manière très claire et formelle les trois règles du broadcasting que nous venons d'énoncer.

Lisez la section "Rules of Broadcasting". Intériorisez bien ces trois règles, car tout le reste de la leçon en découle.


3. Application des Règles : Exemples Concrets

Voyons comment ces règles s'appliquent en pratique. La vidéo suivante est un excellent complément visuel qui explique comment vérifier la compatibilité des formes et quel sera le résultat.

Numpy Array Broadcasting In Python Explained

Cette vidéo de mCoding décompose les règles de manière très visuelle, en montrant comment aligner les formes des tableaux et vérifier la compatibilité axe par axe.

Regardez attentivement les deux sections suivantes : De l'explication des règles, où il présente les règles avec un schéma d'alignement à droite. Puis, de la série d'exemples qui montre des cas compatibles et incompatibles, et comment la forme du résultat est déterminée.

Maintenant, analysons quelques cas courants avec du code.

Cas 1 : Matrice 2D + Vecteur 1D

C'est l'un des cas les plus fréquents. Imaginons que nous avons une matrice M de forme (3, 3) et un vecteur a de forme (3,).

M = np.ones((3, 3))
a = np.arange(3)

print("Forme de M:", M.shape)  # Affiche (3, 3)
print("Forme de a:", a.shape)  # Affiche (3,)

result = M + a
print(result)

Le résultat affiché sera :

[[1. 2. 3.]
 [1. 2. 3.]
 [1. 2. 3.]]

Décomposons cela avec les règles :

  1. M.shape est (3, 3) et a.shape est (3,).
  2. Règle 1 : a a moins de dimensions. Sa forme est complétée à gauche avec un 1. a.shape devient (1, 3).
  3. Règle 2 :
    • Dimension 1 (gauche) : M est 3, a est 1. On étire a pour qu'il corresponde. La forme de a devient (3, 3).
    • Dimension 2 (droite) : M est 3, a est 3. Elles correspondent.
  4. Les formes finales sont compatibles : (3, 3) et (3, 3). L'opération est possible. Le vecteur a est ajouté à chaque ligne de M.

L'image suivante, qui illustre la deuxième règle du broadcasting, montre visuellement ce qui se passe.

Visualisation du broadcasting. En haut, un scalaire est diffusé. Au milieu, un tableau 1D `[0, 1, 2]` est diffusé sur chaque ligne d'une matrice 3x3. En bas, un vecteur colonne et un vecteur ligne sont tous deux diffusés pour former une matrice 3x3.

Cas 2 : Un cas d'incompatibilité

Modifions légèrement l'exemple précédent.

M = np.ones((3, 2))
a = np.arange(3)

print("Forme de M:", M.shape) # Affiche (3, 2)
print("Forme de a:", a.shape) # Affiche (3,)




# Cette ligne lèvera une erreur
# M + a

Pourquoi cela échoue-t-il ?

  1. M.shape est (3, 2) et a.shape est (3,).
  2. Règle 1 : La forme de a devient (1, 3).
  3. Règle 3 : On compare les dimensions de droite à gauche.
    • Dernière dimension : M est 2, a est 3. Elles sont différentes et aucune n'est 1. Erreur ! Le broadcasting s'arrête ici.

Cas 3 : Les deux tableaux sont "stretchés"

Que se passe-t-il si l'on additionne un vecteur colonne de forme (3, 1) avec un vecteur ligne de forme (3,) (ou (1, 3)) ?

a = np.arange(3).reshape((3, 1)) # Vecteur colonne [[0], [1], [2]]
b = np.arange(3)                 # Vecteur ligne [0, 1, 2]

print("Forme de a:", a.shape) # Affiche (3, 1)
print("Forme de b:", b.shape) # Affiche (3,)

result = a + b
print(result)

Le résultat est une matrice (3, 3) !

[[0 1 2]
 [1 2 3]
 [2 3 4]]

Voici comment cela fonctionne :

  1. a.shape est (3, 1) et b.shape est (3,).
  2. Règle 1 : La forme de b devient (1, 3).
  3. Règle 2 :
    • Dimension 1 (gauche) : a est 3, b est 1. On étire b. La forme de b devient (3, 3).
    • Dimension 2 (droite) : a est 1, b est 3. On étire a. La forme de a devient (3, 3).
  4. Les deux tableaux sont virtuellement transformés en matrices (3, 3) et l'addition peut avoir lieu.

Cette animation illustre parfaitement ce dernier cas.

Broadcasting in Numpy

Cette vidéo de Telusko propose une excellente animation qui montre comment les deux tableaux (un vecteur colonne et un vecteur ligne) sont "copiés" pour créer deux matrices de même taille avant l'addition.

Regardez les deux parties suivantes : L'exemple principal où il additionne un tableau 1D et un tableau 2D (vecteur colonne), et l'animation qui suit. L'explication de l'échec qui montre pourquoi certaines formes sont incompatibles.


4. Forcer le Broadcasting avec np.newaxis

Dans notre exemple incompatible ((3, 2) + (3,)), l'opération a échoué parce que la dernière dimension de a (3) ne correspondait pas à celle de M (2). Mais que faire si nous voulions additionner le vecteur a à chaque colonne de M ? Pour cela, il faudrait que a ait une forme (3, 1).

Nous pouvons forcer la création d'une nouvelle dimension de taille 1 en utilisant np.newaxis ou la méthode .reshape().

M = np.ones((3, 2))
a = np.arange(3)




# On transforme 'a' de (3,) en (3, 1)
a_colonne = a[:, np.newaxis] # ou a.reshape(3, 1)

print("Forme de M:", M.shape)          # Affiche (3, 2)
print("Forme de a_colonne:", a_colonne.shape) # Affiche (3, 1)

result = M + a_colonne
print(result)

Résultat :

[[1. 1.]
 [2. 2.]
 [3. 3.]]

Maintenant, ça fonctionne ! Le vecteur a a été additionné à chaque colonne.

  1. M.shape est (3, 2) et a_colonne.shape est (3, 1).
  2. Règle 2 :
    • Dimension de droite : M est 2, a est 1. On étire a pour correspondre. a devient (3, 2).
    • Les formes sont compatibles, et le tour est joué.

Cette technique est extrêmement utile. Un cas d'usage courant en machine learning est de "centrer" les données, c'est-à-dire de soustraire la moyenne de chaque colonne à tous les éléments de cette colonne.

NumPy Broadcasting | Towards Data Science

Cet article montre des exemples pratiques de broadcasting, notamment le cas où une opération échoue et comment la corriger avec reshape ou np.newaxis.

Lisez l'exemple "soustraire la moyenne des colonnes", qui est un cas de broadcasting direct. Ensuite, concentrez-vous sur l'exemple "déméaner les lignes". Il illustre parfaitement un cas d'échec du broadcasting et comment le résoudre en remodelant l'array des moyennes avec reshape(-1, 1).


Exercices pratiques

Testez votre compréhension des règles du broadcasting.

  1. Exercice 1 : Prédire les formes
    Sans exécuter le code, déterminez si les opérations suivantes sont valides. Si oui, quelle sera la forme du tableau résultant ?
    a. Un tableau de forme (5, 4) + un tableau de forme (4,).
    b. Un tableau de forme (5, 4) + un tableau de forme (5,).
    c. Un tableau de forme (2, 5, 4) + un tableau de forme (5, 1).

  2. Exercice 2 : Normalisation de données
    La normalisation est une étape courante en machine learning. Une façon simple de la faire est de transformer les données pour qu'elles se situent entre 0 et 1. La formule est :

    Imaginez que vous ayez un tableau de données data de forme (10, 3), où chaque colonne est une "feature" (caractéristique). Vous devez normaliser chaque colonne indépendamment.

    • Créez un tableau data de forme (10, 3) avec des nombres aléatoires (np.random.rand(10, 3) * 100).
    • Calculez le minimum de chaque colonne (data.min(axis=0)).
    • Calculez le maximum de chaque colonne (data.max(axis=0)).
    • En utilisant le broadcasting, appliquez la formule de normalisation à votre tableau data.

Solutions

Solution de l'exercice 1 :

a. Valide. Formes : (5, 4) et (4,). Règle 1 : (4,) -> (1, 4). Règle 2 : (1, 4) -> (5, 4). Forme finale : (5, 4).
b. Invalide. Formes : (5, 4) et (5,). Règle 1 : (5,) -> (1, 5). Règle 3 : La dernière dimension est 4 vs 5. Erreur. (Pour que ça marche, il faudrait que la forme soit (5, 1)).
c. Valide. Formes : (2, 5, 4) et (5, 1). Règle 1 : (5, 1) -> (1, 5, 1). Règle 2 : (1, 5, 1) -> (2, 5, 4). Forme finale : (2, 5, 4).

Solution de l'exercice 2 :

import numpy as np




# Générer des données aléatoires
data = np.random.rand(10, 3) * 100
print("Données originales (premières 3 lignes):\n", data[:3])




# Calculer min et max pour chaque colonne (axis=0)
min_par_colonne = data.min(axis=0)
max_par_colonne = data.max(axis=0)

print("\nForme de data:", data.shape)               # (10, 3)
print("Forme de min_par_colonne:", min_par_colonne.shape) # (3,)
print("Forme de max_par_colonne:", max_par_colonne.shape) # (3,)




# Appliquer la formule en utilisant le broadcasting
# min_par_colonne et (max_par_colonne - min_par_colonne) sont des tableaux de forme (3,)
# Ils sont "diffusés" sur les 10 lignes du tableau 'data'.
data_normalise = (data - min_par_colonne) / (max_par_colonne - min_par_colonne)

print("\nDonnées normalisées (premières 3 lignes):\n", data_normalise[:3])




# Vérification : le min de chaque colonne doit être 0 et le max 1
print("\nMin des colonnes normalisées:", data_normalise.min(axis=0)) # Devrait être [0. 0. 0.]
print("Max des colonnes normalisées:", data_normalise.max(axis=0)) # Devrait être [1. 1. 1.]

Conclusion

Le broadcasting peut sembler un peu magique au début, mais il suit des règles très logiques et prévisibles. En le maîtrisant, vous ouvrez la porte à des calculs complexes et performants avec un code remarquablement simple.

Points clés à retenir :

  • Le broadcasting permet des opérations sur des tableaux de formes différentes mais compatibles.
  • La compatibilité est vérifiée en comparant les dimensions de droite à gauche.
  • Pour être compatibles, les dimensions doivent soit être égales, soit l'une d'elles doit être 1.
  • NumPy complète les formes plus courtes avec des 1 à gauche et étire virtuellement les dimensions de taille 1 pour correspondre aux autres.
  • Utilisez np.newaxis ou .reshape() pour ajouter explicitement une dimension et contrôler le comportement du broadcasting.

Dans notre prochaine leçon, nous continuerons à explorer la puissance de NumPy en nous penchant sur ses fonctions statistiques. Vous verrez que le broadcasting est un allié indispensable pour calculer des statistiques comme la moyenne ou l'écart-type sur des axes spécifiques de vos données.