Skip to main content

Introduction à TensorFlow

Dans cette première partie, nous allons plonger au cœur de TensorFlow. Nous allons découvrir ce qui le distingue des autres librairies Python, comment il fonctionne, et quels sont ses concepts fondamentaux.

Ce que Vous Allez Apprendre

  • Philosophie de TensorFlow : Comprendre l'approche basée sur les graphes de calcul et l'exécution différée.
  • Tenseurs : Découvrir ce que sont les tenseurs, leurs propriétés (type, rang, forme) et comment les créer et les manipuler avec TensorFlow.
  • Variables : Comprendre le rôle des variables TensorFlow pour stocker les paramètres des modèles.
  • Opérations: Découvrir comment effectuer des calculs.
  • Graphe de calcul: Comment tout cela s'articule.
info

L'objectif n'est pas de faire de vous des experts de TensorFlow en une heure et demie, mais de vous donner une base solide pour comprendre les concepts clés et pouvoir les mettre en pratique par la suite.

Philosophie de TensorFlow : Graphe de Calcul, Exécution Différée

Qu'est-ce que TensorFlow ?

TensorFlow est une bibliothèque open-source pour le calcul numérique, particulièrement adaptée au Machine Learning et au Deep Learning. Développée initialement par l'équipe Google Brain, elle est aujourd'hui largement utilisée par la communauté scientifique, les entreprises et les développeurs individuels.

warning

Veuillez vous référer à la documentation officielle afin d'avoir plus d'exemples et d'explications.

Caractéristiques principales :

  • Calcul sur graphe de données (Dataflow Graph) : C'est le concept central de TensorFlow. Les calculs sont représentés sous forme de graphe, où les nœuds sont des opérations et les arêtes sont des tenseurs (tableaux multidimensionnels) transportant les données.
  • Exécution sur différents matériels : TensorFlow peut exécuter les calculs sur CPU, GPU, TPU (Tensor Processing Units, des accélérateurs matériels conçus par Google pour le Deep Learning) et même sur des appareils mobiles.
  • Différenciation automatique : TensorFlow calcule automatiquement les gradients (dérivées) des fonctions, ce qui est essentiel pour l'entraînement des modèles de Machine Learning (descente de gradient).
  • Flexibilité et extensibilité : TensorFlow offre à la fois une API de bas niveau (pour un contrôle précis) et des API de haut niveau (comme Keras, pour la simplicité et la rapidité de développement).
  • Vaste écosystème : TensorFlow s'accompagne de nombreux outils et ressources, comme TensorBoard (pour la visualisation), TensorFlow Hub (pour les modèles pré-entraînés), TensorFlow Serving (pour le déploiement de modèles), etc.

Philosophie : Graphe de Calcul

La philosophie de TensorFlow repose sur le concept de graphe de calcul (ou graphe de données, dataflow graph).

Principe :

  1. Définition du graphe : Vous définissez d'abord la structure du calcul sous forme de graphe. Ce graphe est une représentation abstraite des opérations à effectuer, sans qu'aucun calcul ne soit réellement effectué à ce stade.
  2. Exécution du graphe : Vous exécutez ensuite le graphe dans une session TensorFlow. C'est à ce moment-là que les données (tenseurs) circulent dans le graphe et que les opérations sont effectuées.

Analogie :

Imaginez que vous construisiez un circuit électrique.

  • Définition du graphe : Vous dessinez d'abord le schéma du circuit, en indiquant les composants (résistances, condensateurs, etc.) et leurs connexions, sans brancher le circuit sur une source d'alimentation.
  • Exécution du graphe : Vous branchez ensuite le circuit sur une source d'alimentation, et le courant circule à travers les composants, effectuant les opérations prévues.

Avantages du graphe de calcul :

  • Optimisation : TensorFlow peut analyser et optimiser le graphe avant de l'exécuter (par exemple, en fusionnant certaines opérations, en parallélisant d'autres, en allouant efficacement la mémoire).
  • Portabilité : Le graphe peut être exécuté sur différents types de matériel (CPU, GPU, TPU) sans modification du code.
  • Distribuabilité : Le graphe peut être distribué sur plusieurs machines pour accélérer les calculs.

Exécution Différée (Deferred Execution)

L'approche de TensorFlow est souvent appelée "exécution différée" (deferred execution) ou "exécution symbolique" (symbolic execution), par opposition à l'exécution immédiate (eager execution) que l'on trouve dans des librairies comme NumPy.

  • Exécution immédiate (NumPy) : Chaque opération est exécutée immédiatement, et le résultat est disponible.
  • Exécution différée (TensorFlow) : Les opérations sont d'abord définies de manière abstraite, puis exécutées dans une session.

Exemple (conceptuel) :

# Style NumPy (exécution immédiate)
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = a + b # L'addition est effectuée immédiatement
print(c) # Le résultat est disponible

# Style TensorFlow (exécution différée, TensorFlow 1.x)
import tensorflow as tf

# Définition du graphe
a = tf.placeholder(tf.int32) # Placeholder : "boîte vide" pour les données
b = tf.placeholder(tf.int32)
c = a + b # L'addition est définie, mais pas effectuée

# Exécution du graphe
with tf.Session() as sess:
result = sess.run(c, feed_dict={a: [1, 2, 3], b: [4, 5, 6]})
print(result)

TensorFlow 2.x et l'Exécution Immédiate

Depuis TensorFlow 2.0, l'exécution immédiate est activée par défaut. Cela rend TensorFlow plus intuitif et plus facile à utiliser, tout en conservant les avantages du graphe de calcul (optimisation, portabilité) grâce à la fonctionnalité @tf.function.

# TensorFlow 2.x (exécution immédiate par défaut)
import tensorflow as tf

a = tf.constant([1, 2, 3]) # Tenseur constant
b = tf.constant([4, 5, 6])
c = a + b # L'addition est effectuée immédiatement (mais peut être optimisée)
print(c)

Même si l'exécution immédiate est activée par défaut, il est important de comprendre le concept de graphe de calcul, car il reste au cœur du fonctionnement de TensorFlow.

info

Avec TensorFlow 2.x, vous pouvez utiliser à la fois l'exécution immédiate (pour la facilité d'utilisation) et la construction explicite de graphes (pour un contrôle plus fin et des optimisations avancées).

Représentation visuelle

Voici à quoi ressemble un graphe de calcul :

Dans cet exemple :

  • Les noeuds A et B représentent les entrées,
  • Le noeud C représente une opération d'addition,
  • Le noeud D représente la sortie.

Tenseurs : Définition, Types, Rang, Forme, Création, Opérations

Au cœur de TensorFlow se trouve le concept de tenseur.

warning

Veuillez vous référer à la documentation officielle afin d'avoir plus d'exemples et d'explications.

Qu'est-ce qu'un Tenseur ?

Un tenseur est une généralisation des vecteurs et des matrices à un nombre arbitraire de dimensions. C'est un tableau multidimensionnel de nombres (ou d'autres types de données, mais le plus souvent des nombres).

Analogies :

  • Scalaire : Un nombre simple (ex : 5, 3.14, -2). C'est un tenseur de rang 0.
  • Vecteur : Un tableau unidimensionnel de nombres (ex : [1, 2, 3]). C'est un tenseur de rang 1.
  • Matrice : Un tableau bidimensionnel de nombres (ex : [[1, 2], [3, 4]]). C'est un tenseur de rang 2.
  • Tenseur de rang 3 : Un "cube" de nombres (ex : [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]). Imaginez une pile de matrices.
  • Tenseur de rang supérieur : Une généralisation à n'importe quel nombre de dimensions.

Types de Données (dtype)

Les éléments d'un tenseur ont un type de données spécifique. Les types de données courants dans TensorFlow sont :

  • tf.float32 : Nombre à virgule flottante (32 bits).
  • tf.float64 : Nombre à virgule flottante (64 bits).
  • tf.int32 : Entier (32 bits).
  • tf.int64 : Entier (64 bits).
  • tf.bool : Booléen (Vrai ou Faux).
  • tf.string : Chaîne de caractères.

Rang (Rank)

Le rang d'un tenseur est son nombre de dimensions.

  • Scalaire : Rang 0
  • Vecteur : Rang 1
  • Matrice : Rang 2
  • Tenseur 3D : Rang 3
  • etc.

Forme (Shape)

La forme d'un tenseur est un tuple d'entiers qui indique la taille de chaque dimension.

Exemples :

  • Un scalaire a une forme ().
  • Un vecteur de taille 3 a une forme (3,).
  • Une matrice 2x4 a une forme (2, 4).
  • Un tenseur 3x2x5 a une forme (3, 2, 5).

Création de Tenseurs

TensorFlow offre plusieurs façons de créer des tenseurs :

import tensorflow as tf

# À partir de constantes
scalar = tf.constant(5) # Scalaire (rang 0)
print(f"Scalaire :\n{scalar}")
vector = tf.constant([1, 2, 3]) # Vecteur (rang 1)
print(f"Vecteur :\n{vector}")
matrix = tf.constant([[1, 2], [3, 4]]) # Matrice (rang 2)
print(f"Matrice :\n{matrix}")
tensor3D = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # Tenseur 3D (rang 3)
print(f"Tenseur 3D :\n{tensor3D}")

# Avec des fonctions prédéfinies
zeros = tf.zeros((2, 3)) # Tenseur de zéros de forme (2, 3)
print(f"Tenseur de zéros :\n{zeros}")
ones = tf.ones((3, 4)) # Tenseur de uns de forme (3, 4)
print(f"Tenseur de uns :\n{ones}")
random = tf.random.normal((2, 2)) # Tenseur de nombres aléatoires (distribution normale)
print(f"Tenseur aléatoire :\n{random}")

# À partir de tableaux NumPy
import numpy as np
array = np.array([[1, 2, 3], [4, 5, 6]])
tensor_from_array = tf.convert_to_tensor(array)
print(f"Tenseur à partir d'un tableau NumPy :\n{tensor_from_array}")

Opérations sur les Tenseurs

TensorFlow fournit un large éventail d'opérations sur les tenseurs, similaires à celles de NumPy :

a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[5, 6], [7, 8]])

# Opérations élément par élément
print(f"Addition :\n{a + b}") # Ou tf.add(a, b)
print(f"Soustraction :\n{a - b}")
print(f"Multiplication :\n{a * b}")
print(f"Division :\n{a / b}")

# Produit matriciel
print(f"Produit matriciel :\n{tf.matmul(a, b)}")

# Transposition
print(f"Transposée de a :\n{tf.transpose(a)}")

# Réduction (somme, moyenne, etc.)
print(f"Somme des éléments de a : {tf.reduce_sum(a)}")
print(f"Moyenne des éléments de a : {tf.reduce_mean(a)}")

# Autres opérations
print(f"Exponentielle de a :\n{tf.exp(a)}")
print(f"Racine carrée de a :\n{tf.sqrt(a)}")

Indexation et Slicing

tensor = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(tensor[0, 1]) # Accéder à un élément spécifique (2 dans ce cas)
print(tensor[1, :]) # Accéder à une ligne entière
print(tensor[:, 1]) # Accéder à une colonne entière

Tenseurs et Graphe de Calcul

Lorsque vous créez des tenseurs et que vous effectuez des opérations sur eux en mode d'exécution différée (TensorFlow 1.x), vous ne faites que définir les nœuds et les arêtes du graphe de calcul. Les calculs ne sont effectués que lorsque vous exécutez le graphe dans une session. Avec TensorFlow 2 et son mode d'exécution immédiate par défaut, les opérations sont exécutées au fur et à mesure, mais le concept de tenseur reste central.

info

Les tenseurs sont les éléments fondamentaux de TensorFlow. Ils transportent les données à travers le graphe de calcul.

Variables : Importance pour les Paramètres des Modèles (Lien avec les Poids du Perceptron)

Dans TensorFlow, les variables (tf.Variable) sont un type spécial de tenseur utilisé pour stocker et mettre à jour les paramètres d'un modèle de Machine Learning.

warning

Veuillez vous référer à la documentation officielle afin d'avoir plus d'exemples et d'explications.

Qu'est-ce qu'une Variable TensorFlow ?

Une variable TensorFlow est un tenseur dont la valeur peut être modifiée au cours de l'exécution du graphe de calcul. C'est ce qui permet à un modèle d'apprendre : les paramètres (poids, biais) sont stockés dans des variables, et la descente de gradient ajuste ces variables pour minimiser la fonction de perte.

Caractéristiques principales :

  • Persistance : Contrairement aux tenseurs créés avec tf.constant, la valeur d'une variable est conservée entre les exécutions du graphe (dans une même session).
  • Modifiable : La valeur d'une variable peut être modifiée par des opérations spéciales, comme assign ou assign_add. C'est essentiel pour l'apprentissage.
  • Initialisation : Les variables doivent être initialisées avant d'être utilisées. L'initialisation leur donne une valeur de départ.

Pourquoi Utiliser des Variables ?

Dans un modèle de Machine Learning, les paramètres (poids, biais) doivent être ajustés pendant l'entraînement. Les variables TensorFlow sont conçues pour cela :

  • Stockage des paramètres : Les variables servent à stocker les paramètres du modèle.
  • Mise à jour des paramètres : La descente de gradient (et d'autres algorithmes d'optimisation) utilise les gradients de la fonction de perte par rapport aux paramètres pour mettre à jour les variables.

Lien avec les Poids du Perceptron

Reprenons l'exemple du Perceptron. Les poids (w1,w2,...,wnw_1, w_2, ..., w_n) et le biais (bb) sont les paramètres du modèle. Dans une implémentation TensorFlow, ces paramètres seraient stockés dans des variables :

import tensorflow as tf

# Définition des variables (avec des valeurs initiales)
w = tf.Variable([0.5, -0.2], dtype=tf.float32) # Poids
b = tf.Variable(0.1, dtype=tf.float32) # Biais

Pendant l'entraînement, la descente de gradient modifierait les valeurs de w et b pour que le Perceptron apprenne à partir des données.

Création et Initialisation de Variables

import tensorflow as tf

# Création d'une variable (scalaire) avec une valeur initiale
v1 = tf.Variable(5.0)
print(f"v1 (scalaire) :\n{v1}")

# Création d'une variable (matrice) initialisée avec des zéros
v2 = tf.Variable(tf.zeros((2, 3)))
print(f"v2 (matrice de zéros) :\n{v2}")

# Création d'une variable initialisée avec des valeurs aléatoires (distribution normale)
v3 = tf.Variable(tf.random.normal((3, 2)))
print(f"v3 (valeurs aléatoires) :\n{v3}")

# Spécification du type de données
v4 = tf.Variable([1, 2, 3], dtype=tf.int32)
print(f"v4 (entiers) :\n{v4}")

Modification de la Valeur d'une Variable

v = tf.Variable(2.0)

# Assignation d'une nouvelle valeur
v.assign(5.0)
print(f"v après assignation :\n{v}")

# Ajout à la valeur existante
v.assign_add(3.0)
print(f"v après assign_add :\n{v}")

# Soustraction à la valeur existante
v.assign_sub(1.0)
print(f"v après assign_sub :\n{v}")
info

Les variables TensorFlow sont essentielles pour l'apprentissage des modèles. Elles permettent de stocker et de mettre à jour les paramètres du modèle pendant l'entraînement.

Pourquoi ne pas Utiliser des Variables Python Normales ?

Vous vous demandez peut-être pourquoi nous avons besoin de tf.Variable alors que Python a déjà des variables. Voici les raisons principales :

  1. Intégration au Graphe de Calcul TensorFlow :

    • Les variables TensorFlow (tf.Variable) sont des objets spéciaux qui sont intégrés au graphe de calcul TensorFlow. Cela signifie que TensorFlow peut suivre automatiquement les dépendances entre les variables et les opérations, et calculer les gradients nécessaires à l'apprentissage.
    • Les variables Python normales ne sont pas connues de TensorFlow. Elles existent en dehors du graphe de calcul. Si vous essayiez d'utiliser des variables Python normales pour stocker les paramètres d'un modèle, TensorFlow ne pourrait pas les mettre à jour pendant l'entraînement.
  2. Gestion de la Mémoire et des Ressources :

    • TensorFlow gère la mémoire et les ressources (CPU, GPU, TPU) de manière optimisée pour les calculs sur les tenseurs. Les variables TensorFlow sont stockées dans cette mémoire gérée par TensorFlow.
    • Les variables Python normales sont stockées dans la mémoire Python standard, qui n'est pas optimisée pour les calculs haute performance de TensorFlow.
  3. Persistance et Sauvegarde :

    • Les variables TensorFlow peuvent être facilement sauvegardées et restaurées (par exemple, pour enregistrer l'état d'un modèle après l'entraînement et le réutiliser plus tard).
    • Les variables Python normales ne sont pas automatiquement persistantes. Vous devriez écrire du code supplémentaire pour les sauvegarder et les restaurer.
  4. Opérations Spéciales :

    • TensorFlow fournit des opérations spéciales pour modifier les variables TensorFlow (comme assign, assign_add), qui sont optimisées pour fonctionner avec le graphe de calcul et le matériel sous-jacent.
    • Les variables Python normales n'ont pas ces opérations spéciales.
  5. Différenciation Automatique

  • TensorFlow utilise la différenciation automatique pour calculer les gradients de la fonction de perte par rapport aux paramètres du modèle (les variables TensorFlow). Ce mécanisme repose sur le suivi des opérations effectuées sur les variables TensorFlow.
  • Si vous utilisiez des variables Python normales, TensorFlow ne serait pas capable de calculer automatiquement ces gradients.

En résumé : Les variables TensorFlow sont des objets spéciaux, intégrés au graphe de calcul TensorFlow, qui permettent à TensorFlow de gérer efficacement la mémoire, d'effectuer des optimisations, de calculer automatiquement les gradients et de sauvegarder/restaurer l'état du modèle. Les variables Python normales ne possèdent pas ces fonctionnalités.

info

Bien que vous puissiez techniquement utiliser des variables Python à l'intérieur de fonctions que vous définissez avec @tf.function (nous verrons cela plus tard), il est fortement recommandé d'utiliser tf.Variable pour les paramètres de vos modèles.

Exercices : Tenseurs et Variables TensorFlow

info

Pour ces exercices, utilisez TensorFlow 2.x (l'exécution immédiate est activée par défaut). Vous pouvez exécuter ces exercices dans un notebook Jupyter dans VS Code. N'hésitez pas à revoir la théorie en parallèle pour pouvoir faire les exercices.

Exercice 1 : Création et Manipulation de Tenseurs

  1. Créez un tenseur constant a contenant les entiers de 5 à 10 (inclus).
  2. Créez un tenseur b de forme (2, 3) rempli de zéros flottants (type tf.float32).
  3. Créez un tenseur c de forme (3, 3) rempli de nombres aléatoires tirés d'une distribution normale (moyenne 0, écart-type 1).
  4. Affichez le rang et la forme de chaque tenseur (a, b, c).
  5. Calculez la somme des éléments de a.
  6. Calculez le produit matriciel de b et c (attention à l'ordre !).
  7. Extrayez la deuxième colonne de c.
Solution Exercice 1
import tensorflow as tf

# 1.
a = tf.constant([5, 6, 7, 8, 9, 10])

# 2.
b = tf.zeros((2, 3), dtype=tf.float32)

# 3.
c = tf.random.normal((3, 3))

# 4.
print(f"Rang de a : {tf.rank(a)}")
print(f"Forme de a : {a.shape}")
print(f"Rang de b : {tf.rank(b)}")
print(f"Forme de b : {b.shape}")
print(f"Rang de c : {tf.rank(c)}")
print(f"Forme de c : {c.shape}")

# 5.
somme_a = tf.reduce_sum(a)
print(f"Somme des éléments de a : {somme_a}")

# 6.
produit_bc = tf.matmul(b, c) # On ne peut pas faire le produit de c et b
print(f"Produit matriciel de b et c :\n{produit_bc}")

# 7.
deuxieme_colonne_c = c[:, 1]
print(f"Deuxième colonne de c :\n{deuxieme_colonne_c}")

Exercice 2 : Variables TensorFlow

  1. Créez une variable TensorFlow v1 initialisée avec la valeur 3.14 (flottant).
  2. Créez une variable TensorFlow v2 initialisée avec un tableau NumPy contenant les entiers de 1 à 4.
  3. Affichez la valeur actuelle de v2.
  4. Assignez la valeur 10.0 à v1.
  5. Ajoutez 5 à la valeur actuelle de v2.
  6. Affichez la valeur de v1 et v2.
Solution Exercice 2
import tensorflow as tf
import numpy as np

# 1.
v1 = tf.Variable(3.14)

# 2.
v2 = tf.Variable(np.array([1, 2, 3, 4]))

# 3.
print(f"Valeur initiale de v2 :\n{v2.numpy()}")

# 4.
v1.assign(10.0)

# 5.
v2.assign_add([5, 5, 5, 5])

#6.
print(f"Valeur de v1 : {v1}")
print(f"Valeur de v2 : {v2}")

Exercice 3 : Combinaison de Tenseurs et de Variables

  1. Créez un tenseur constant x contenant les valeurs [1.0, 2.0, 3.0].
  2. Créez une variable TensorFlow w initialisée avec des valeurs aléatoires tirées d'une distribution normale, de forme (3, 1).
  3. Créez une variable TensorFlow b initialisée avec la valeur 0.0.
  4. Calculez y = tf.matmul(x, w) + b. Quel est le problème ? Corrigez-le.
  5. Affichez la valeur de y.
Solution Exercice 3
import tensorflow as tf

# 1.
x = tf.constant([1.0, 2.0, 3.0])

# 2.
w = tf.Variable(tf.random.normal((3, 1)))

# 3.
b = tf.Variable(0.0)

# 4.
# Problème : x a une forme (3,) et w a une forme (3, 1).
# On ne peut pas multiplier une matrice 1x3 par une matrice 3x1.
# Correction : On doit transposer x pour qu'il ait une forme (1, 3) ou w pour qu'il ait une forme (3,)
x_reshaped = tf.reshape(x, (1, 3))
y = tf.matmul(x_reshaped, w) + b
#Ou alors :
# w_transposed = tf.transpose(w)
# y = tf.matmul(x, w_transposed) + b

# 5.
print(f"Valeur de y : {y}")