1. 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 :

  1. d = a + b (addition)
  2. e = b - c (soustraction)
  3. Y = d * e (multiplication)

Le graphe de calcul correspondant ressemblerait à ceci :

           

  1. 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.

  1. 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.
  1. 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

  1. 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.

  1. 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
  1. 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

  1. 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).
  1. 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.
  1. 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.
  1. 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.
  1. 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é.