- Qu'est-ce qu'un Graphe de Calcul ?
Un graphe de calcul est un graphe orienté où :
- Les nœuds représentent des variables (scalaires, vecteurs, matrices, tenseurs) ou des opérations mathématiques (addition, multiplication, etc.).
- Les arêtes représentent les dépendances entre les variables et les opérations, c'est-à-dire comment les données circulent entre les nœuds.
Par exemple, pour l'expression mathématique :
Y = (a + b) * (b - c)
On peut décomposer cette expression en trois opérations :
- d = a + b (addition)
- e = b - c (soustraction)
- Y = d * e (multiplication)
Le graphe de calcul correspondant ressemblerait à ceci :
- Types de Calculs dans un Graphe de Calcul
Les graphes de calcul sont utilisés pour deux types de calculs principaux :
2.1. Calcul Direct (Forward Computation)
Objectif : Calculer la sortie finale à partir des entrées.
Exemple : Dans l'expression Y = (a + b) * (b - c), le calcul direct consiste à évaluer d, e, puis Y.
2.2. Calcul Inverse (Backward Computation)
Objectif : Calculer les dérivées partielles (gradients) pour mettre à jour les paramètres du modèle.
Exemple : Utiliser la règle de la chaîne pour calculer ∂Y/∂a, ∂Y/∂b, et ∂Y/∂c.
- Terminologies Clés
- Variable : Représentée par un nœud dans le graphe. Elle peut être un scalaire, un vecteur, une matrice, un tenseur, etc.
- Opération : Une fonction simple (addition, soustraction, multiplication) ou complexe (combinaison de plusieurs opérations).
- Arête : Représente une dépendance entre les variables ou les opérations. Elle indique la direction du flux de données.
- Application en Apprentissage Profond
En apprentissage profond, les graphes de calcul sont utilisés pour organiser les calculs en deux étapes principales :
4.1. Passe Directe (Forward Pass)
Objectif : Calculer la sortie du réseau de neurones à partir des entrées.
Exemple : Pour un réseau de neurones, la passe directe calcule les activations de chaque couche jusqu'à la sortie finale.
4.2. Passe Inverse (Backward Pass)
Objectif : Calculer les gradients des paramètres du modèle par rapport à la fonction de perte, en utilisant la règle de la chaîne.
Exemple : Pour l'expression Y = (a + b) * (b - c), les gradients sont calculés comme suit :
∂Y/∂a = ∂Y/∂d * ∂d/∂a
∂Y/∂b = ∂Y/∂d * ∂d/∂b + ∂Y/∂e * ∂e/∂b
∂Y/∂c = ∂Y/∂e * ∂e/∂c
- Types de Graphes de Calcul
5.1. Graphes Statiques
Définition : Le graphe est défini une fois avant l'exécution des calculs. Toutes les opérations sont planifiées à l'avance.
Avantages :
- Optimisation poussée (fusion d'opérations, allocation mémoire efficace).
- Performances élevées en production.
Inconvénients :
- Moins flexible pour le débogage et le prototypage.
- Difficile à utiliser avec des données de taille variable.
Exemple : TensorFlow 1.x.
5.2. Graphes Dynamiques
Définition : Le graphe est construit "à la volée" pendant l'exécution du code. Chaque opération est exécutée immédiatement.
Avantages :
- Très flexible et intuitif.
- Facile à déboguer (exécution ligne par ligne).
- Idéal pour la recherche et les données structurées.
Inconvénients :
- Moins optimisé pour la production.
- Moins performant pour les très grands modèles.
Exemple : PyTorch.
- Exemple Pratique
Prenons l'expression Y = (a + b) * (b - c). Voici comment elle est évaluée dans un graphe de calcul :
Calcul Direct :
- d = a + b
- e = b - c
- Y = d * e
Calcul Inverse (Backpropagation) :
- ∂Y/∂d = e
- ∂Y/∂e = d
- ∂Y/∂a = ∂Y/∂d * ∂d/∂a = e * 1 = e
- ∂Y/∂b = ∂Y/∂d * ∂d/∂b + ∂Y/∂e * ∂e/∂b = e * 1 + d * 1 = e + d
- ∂Y/∂c = ∂Y/∂e * ∂e/∂c = d * (-1) = -d
- Conclusion
Les graphes de calcul sont un outil puissant pour organiser et optimiser les calculs en apprentissage profond. Ils permettent de :
- Représenter des expressions mathématiques complexes.
- Calculer efficacement les sorties (passe directe) et les gradients (passe inverse).
- Choisir entre flexibilité (graphes dynamiques) et performance (graphes statiques).
Utilisation des Graphes de Calcul dans PyTorch et TensorFlow
- Introduction
Les frameworks PyTorch et TensorFlow utilisent des graphes de calcul pour organiser et optimiser les opérations mathématiques dans les modèles d'apprentissage profond. Cependant, ils diffèrent dans leur approche :
- PyTorch utilise un graphe dynamique, construit à la volée pendant l'exécution.
- TensorFlow utilise par défaut un graphe statique, mais propose également un mode dynamique (eager execution).
- PyTorch: Graphe Dynamique
Dans PyTorch, le graphe de calcul est construit dynamiquement à chaque exécution. Cela permet une grande flexibilité et facilite le débogage.
Exemple Simple : Addition et Multiplication
Voici un exemple de calcul simple avec PyTorch :
import torch
# Déclaration des variables
a = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(3.0, requires_grad=True)
c = torch.tensor(4.0, requires_grad=True)
# Calculs
d = a + b
e = b - c
Y = d * e
# Affichage des résultats
print("Y :", Y.item()) # Sortie : Y : -5.0
# Calcul des gradients
Y.backward()
# Affichage des gradients
print("∂Y/∂a :", a.grad.item()) # Sortie : ∂Y/∂a : -1.0
print("∂Y/∂b :", b.grad.item()) # Sortie : ∂Y/∂b : 2.0
print("∂Y/∂c :", c.grad.item()) # Sortie : ∂Y/∂c : 3.0
Explication :
- Le graphe de calcul est construit dynamiquement lors de l'exécution des opérations (d = a + b, e = b - c, Y = d * e).
- La méthode backward() calcule les gradients automatiquement en utilisant la règle de la chaîne.
- TensorFlow: Graphe Statique et Mode Eager
TensorFlow utilise par défaut un graphe statique, mais depuis TensorFlow 2.x, il propose également un mode dynamique appelé eager execution.
Exemple Simple : Addition et Multiplication
Voici un exemple de calcul simple avec TensorFlow en mode eager :
import tensorflow as tf
# Activation du mode eager (dynamique)
tf.config.run_functions_eagerly(True)
# Déclaration des variables
a = tf.Variable(2.0)
b = tf.Variable(3.0)
c = tf.Variable(4.0)
# Calculs
with tf.GradientTape() as tape:
d = a + b
e = b - c
Y = d * e
# Affichage des résultats
print("Y :", Y.numpy()) # Sortie : Y : -5.0
# Calcul des gradients
grads = tape.gradient(Y, [a, b, c])
# Affichage des gradients
print("∂Y/∂a :", grads[0].numpy()) # Sortie : ∂Y/∂a : -1.0
print("∂Y/∂b :", grads[1].numpy()) # Sortie : ∂Y/∂b : 2.0
print("∂Y/∂c :", grads[2].numpy()) # Sortie : ∂Y/∂c : 3.0
Explication :
- Le mode eager permet d'exécuter les opérations immédiatement, comme PyTorch.
- tf.GradientTape() enregistre les opérations pour calculer les gradients.
- Comparaison des Approches
PyTorch :
- Graphe dynamique, construit à la volée.
- Idéal pour la recherche et le prototypage.
- Facile à déboguer (exécution ligne par ligne).
TensorFlow :
- Graphe statique par défaut, optimisé pour la production.
- Mode eager disponible pour une flexibilité similaire à PyTorch.
- Meilleure performance en production grâce à l'optimisation du graphe.
- Conclusion
Les deux frameworks, PyTorch et TensorFlow, utilisent des graphes de calcul pour organiser et optimiser les opérations mathématiques. Le choix entre les deux dépend des besoins :
- PyTorch est préférable pour la recherche et le prototypage grâce à son graphe dynamique.
- TensorFlow est plus adapté pour la production grâce à son graphe statique optimisé.