1 Introduction : réseaux de neurones FCNN et CNN
Dans ce TD, nous allons continuer à travailler sur les réseaux de neurones profonds classiques, qui sont
- non bouclés (feed forward net),
- avec apprentissage supervisé (supervised learning).
et nous allons comparer deux types de ces réseaux de neurones :
- FCNN - fully connected neural networks (réseau de neurones entièrement connecté), et
- CNN - convolutional neural networks (réseau de neurones convolutionnel)
Comme nous allons le voir dans ce TD, les CNNs sont particulièrement performants car ils sont capables d’apprendre des caractéristiques spatiales et locales de manière plus efficace.
Dans ce TD, nous allons (de nouveau) travailler sur un jeu de données de classification bien connu : le MNIST. Pour rappel, il s’agit d’un jeu de données contenant des images de chiffres manuscrits stockés sous forme d’une matrice de 28x28 pixels avec une valeur entre de niveaux de gris (entre 0 et 255, que nous allons normaliser entre 0 et 1), ainsi que les étiquettes (labels) associées (de 0 à 9, que nous allons représenter en tant que one hot).
Tous les exercices de ce TD vont faire une classification, l’objectif sera de reconstruire une fonction \(f: R^{784} \rightarrow R^{10}\) à partir des échantillons :
\[ f(X) = Y \]
Les données d’entrée \(X\) vivent donc l’espace à 784 dimensions, et la sortie \(y\) dans un espace à 10 dimensions.
Le premier objectif de ce TD est de comprendre comment on peut appliquer des FCNN et des CNN pour apprendre la prédiction pour le jeu de données MNIST.
Le deuxième objectif est de comprendre et construire une intuition pour le fonctionnement des CNN, et en particulier le fonctionnement de leurs couches de convolutions.
Le troisième objectif est d’implémenter des réseaux FCNN et CNN dans un notebook Jupyterlab python, en utilisant les bibliothèques vu dans le dernier TD (Tensorflow keras, Matplotlib, Numpy et Pandas), et de comparer la performance entre ces deux types de réseaux de neurones.
Exercice 3.0.A : Une visualisation d’un FCNN pour le MNIST
Dans cet exercice, vous allez explorer un réseaux de neurones entièrement connecté (FCNN), qui est déjà entraîné, pour prédire des chiffres manuscrits que vous allez vous même écrire :

Pour cela, avec votre navigateur, ouvrez l’URL suivant :
https://adamharley.com/nn_vis/mlp/2d.html
Avec votre souris, dessinez un chiffre en haut à gauche, et essayez de comprendre le résultat. Survolez les “neurones” de ce réseau, et visualisez les connections.
- Combien de couches cachées (hidden layers) a ce réseaux de neurones pleinement connecté ?
Exercice 3.0.B : Préparation de l’environnement JupyterHub
Nous allons maintenant travailler sur le JupyterHub comme dans le dernier TD. Avec votre navigateur préféré, ouvrez l’URL suivant et authentifiez-vous :
https://jupyterhub.emi.u-bordeaux.fr/
Sur votre espace, vous avez déjà créée un dossier s’appelant deeplearning. Dans ce dossier, créez un sous-dossier td3. Dans ce sous-dossier, créez un nouveau notebook sous le nom td3-fc-et-cnn.ipynb.
Nous allons importer maintenant toutes les bibliothèques dont on aura besoin pour ce TD. Pour cela, créez une nouvelle cellule Code dans votre notebook, mettez le code suivant et exécutez.
# calcul numérique et analyse de données
import numpy as np
import pandas as pd
# plotting
import matplotlib.pyplot as plt
# tensorflow keras
import tensorflow as tf
from tensorflow import keras
from keras import layers
from keras.layers import Dense
from keras.layers import Conv2D
from keras.models import Sequential
from keras.optimizers import AdamVérifiez qu’il n’y ait pas d’erreurs, sinon faites signe au plus vite.
Exercice 3.1 : Etape 1 : Collecter les données
Dans ce TD, nous allons charger directement le jeu de données avec les données étiquettées MNIST, qui est déjà séparé en données d’entrainement et de données de test.
Créez une nouvelle cellule Markdown dans votre notebook, et ajouter le titre suivant, et exécutez la cellule :
# 1. Collecter les données (MNIST)
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant et exécutez la cellule :
# Lire des données de la bibliothèque keras
(X_train, Y_train), (X_test, Y_test) = keras.datasets.mnist.load_data()
# Montrer le format de données
print("Training Images:", X_train.shape)
print("Testing Images:", X_test.shape)
print("Training Labels:", Y_train.shape)
print("Test Labels:", Y_test.shape)- Combien de données étiquettées (labeled data) sont maintenant chargé au total (entrainement et tests ensemble) ?
Exercice 3.2 : Etape 2 : Survoler les données
Dans cet exercice, nous allons utiliser la bibliothèque Matplotlib pour afficher les données afin de pouvoir les survoler et examiner.
Pour la lisibilité de votre notebook, créez une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 2. Survoler les données
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, exécutez la cellule :
no_donnee_entrainement = 10
# Montrer une donnée d'entraînement d'exemple
plt.imshow(X_train[no_donnee_entrainement], cmap="bwr")
plt.colorbar()
plt.show()
print("Y_train : Etiquette (label) pour cette donnée d'entraînement : ", Y_train[no_donnee_entrainement])
print("X_train : Donneés (features) : ", X_train[no_donnee_entrainement])Combien de pixels contient l’image visualisée ?
Changez le numéro de données d’entrainement en 59998. Quel est l’étiquette (label) pour cette donnée ?
Exercice 3.3 : Etape 3 : Préparer les données
Dans cet étape, nous allons transfomer les données pour que les données d’entrée soit normalisées entre [0,1], et que les données de sortie soient codé sous la forme de “One Hot”. Nous n’avons pas besoin de nettoyer les données.
Créez donc une nouvelle cellule Markdown avec le titre et texte suivant.
# 3. Préparer les données
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, exécutez la cellule :
# Normaliser les données d'entrée entre 0 et 1 et changer de type :
X_train_normalized = X_train.astype('float32') / 255
X_test_normalized = X_test.astype('float32') / 255
# convertir les sorties en one-hot
Y_train_one_hot = tf.keras.utils.to_categorical(Y_train)
Y_test_one_hot = tf.keras.utils.to_categorical(Y_test)
# Montrer une donnée d'entraînement d'exemple
plt.imshow(X_train_normalized[no_donnee_entrainement], cmap="bwr")
plt.colorbar()
plt.show()
print("X_train : Donneés (features) normalisées : ", X_train_normalized[no_donnee_entrainement])
print("Y_train : Etiquette (label) pour cette donnée d'entraînement : ", Y_train_one_hot[no_donnee_entrainement])
# Show the shapes of the data.
print("Training Images normalized:", X_train_normalized.shape)
print("Testing Images normalized:", X_test_normalized.shape)
print("Training Labels one hot:", Y_train_one_hot.shape)
print("Test Labels one hot:", Y_test_one_hot.shape)Analysez la sortie.
Créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, et exécutez la cellule :
# applatir les données (de 28x28 à 784)
X_train_normalized_flattened = X_train_normalized.reshape((60000, 784))
X_test_normalized_flattened = X_test_normalized.reshape((10000, 784))
# Show the shapes of the data.
print("Training Images normalized flattened:", X_train_normalized_flattened.shape)
print("Testing Images normalized flattened:", X_test_normalized_flattened.shape)
print("Training Label one hot:", Y_train_one_hot.shape)
print("Test Labels one hot: ", Y_test_one_hot.shape)
print("Y_train : Etiquette (label) pour cette donnée d'entraînement : ", Y_train_one_hot[no_donnee_entrainement])
print("X_train : Donneés (features) normalisées flattened : ", X_train_normalized_flattened[no_donnee_entrainement])Exercice 3.4 : Etape 4 : Séparer les données
Comme vous l’aviez vu, les données MNIST fournis avec Keras sont déjà séparées en données d’entraînement et données de tests.
# 4. Séparer les données
Les données sont déjà séparées en données d'entraînement et données de tests.
- Dans cet exemple, combien de pourcent des données représente la base de test (qui ne sera pas utilisé pendant l’entrainement) ?
Exercice 3.5 : Etape 5 : Choisir et entrainer un modèle
Dans cet exercice, nous allons choisir le modèle de deeplearning : le modèle le plus classique, à savoir un modèle séquentiel FCNN (FCNN - fully connected neural network), non bouclé (feed forward net), avec apprentissage supervisé (supervised learning).
Nous allons reproduire l’architecture du réseau de neurones utilisé dans la visualisation de l’exercice 3.0.A.
Créez une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 5. Choisir et entrainer un modèle
Puis, créez une nouvelle cellule Code dans votre notebook, et ajoutez le code suivant :
# 5. choisir un modèle
# Créer un modèle sequentiel model_fc, fc pour "fully connected"
model_fc = Sequential()
# Première couche cachée avec 300 neurones et fonction d'activation 'tanh'
model_fc.add(layers.Dense(units=300, activation='tanh', input_shape=(28*28,),name="layer1"))
# Deuxième couche cachée avec 100 neurones et fonction d'activation 'tanh'
model_fc.add(layers.Dense(units=100, activation='tanh', name="layer2"))
# Couche de sortie avec 10 neurones et fonction d'activation 'softmax'
model_fc.add(layers.Dense(units=10, activation='softmax',name="layer3"))
# Optimisation Adam avec un taux d'apprentissage spécifique
custom_optimizer = Adam(learning_rate=0.001)
# Compiler le réseau avec une fonction de perte d'entropie croisée et taux de prédictions correctes
model_fc.compile(optimizer=custom_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
# Montrer un résumé du modèle
model_fc.summary()Combien de neurones (“units”) au total contient donc ce réseaux de neurones dans les couches cachées et de sorties ?
Exécutez maintenant la cellule. Quel est le nombre de paramètres total ?
Nous allons maintenant lancer l’entrainement.
history_fc = model_fc.fit(X_train_normalized_flattened, Y_train_one_hot, epochs=10, validation_split=0.375, batch_size=256)- Analysez la sortie de votre cellule. Quelle est la perte des données de validation après la dernière époque (validation loss) ?
Exercice 3.6 : Etape 6 : Evaluer le modèle
Il y a plusieurs façons d’évaluer le modèle. Généralement, on étudie l’évolution de la perte de données d’entraînement et de validation, pour savoir si le modèle a bien “appris” (donc trouvé les bons paramètres de poids et biais), ou s’il s’agit d’un sous-apprentissage (underfitting) ou sur-apprentissage (overfitting), par exemple. Nous allons faire cela en étudiant les courbes d’apprentissage (learning curves) avec la bibliothèque Matplotlib.
Créez donc une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 6. Evaluer le modèle
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, exécutez la cellule. C’est quasiment le même code que le TD précédent (on utilise juste model_fc au lieu de model, afin de pouvoir distinguer avec un autre modèle que nous allons élaborer après) :
print(history_fc.history.keys())
plt.plot(history_fc.history['loss'], label="Training loss")
plt.plot(history_fc.history['val_loss'], label="Validation loss")
plt.title("Données d'entrâinement (FC)")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()- A votre avis, en regardant les courbes, est-ce que le fait d’ajouter quelques époques d’apprentissage améliorera significativement la perte de données de validation ?
Exercice 3.7 : Etape 7 : Finetuning
Comme dans le dernier TD, nous allons omettre cette étape.
Créez donc juste une nouvelle cellule Markdown avec le titre et texte suivant.
# 7. Finetuning.
Omis dans cet exemple.
Exercice 3.8 : Etape 8 : Evaluer sur la base de test
Nous allons maintenant évaluer sur la base de test.
Créez donc une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 8. Evaluer sur la base de test
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, exécutez la cellule :
print("Evaluer sur la base de test")
results = model_fc.evaluate(X_test_normalized_flattened, Y_test_one_hot)
print("test loss, test accuracy :", results)Exercice 3.9 : Etape 9 : Production : faire les prédictions
Nous allons maintenant passer à la phase production : le modèle est entrainé, et nous allons utiliser le modèle entraîné afin de faire des prédictions pour de nouvelles données, c’est aussi appelé inférence.
Créez donc une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 9. Production : faire les prédictions
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, exécutez la cellule :
#choisir une donnée de test particuluère
no_donnee_test_exemple = 45
predictions_donnees_test = model_fc.predict(X_test_normalized_flattened)
# Montrer l'image
plt.imshow(X_test_normalized[no_donnee_test_exemple], cmap="bwr")
plt.colorbar()
plt.show()
# Montrer les prédictions
print("Prédictions : ", predictions_donnees_test[no_donnee_test_exemple])
# Montrer la prédictions de classe
print("Prédiction de classe : ", np.argmax(predictions_donnees_test[no_donnee_test_exemple]))Exercice 3.10 : Etape 10 : Maintenir le modèle
Nous allons omettre cette étape dans cet exercice comme il s’agit de données synthétisées.
Créez donc juste une nouvelle cellule Markdown avec le titre et texte suivant.
# 10. Maintenir le modèle.
Omis dans cet exemple.
Exercice 3.11 : Filtres de convolution et de CNN
Nous allons maintenant travailler sur des CNN.
Les CNN utilisent un opérateur de convolution pour traiter les données. Plus précisément, ils utilise des filtres (ou kernels) sur les données d’entrée. Ces filtres sont de petites matrices de poids (par exemple, 3x3 ou 5x5), qui glissent ou se déplacent sur l’image d’entrée.
À chaque position, le filtre effectue un produit élément par élément avec la région correspondante de l’image, puis additionne les résultats pour produire un seul nombre en sortie. Ce processus s’appelle convolution.
Pour une explication visuelle et interactive de ce filtre de convolution, ouvrez l’URL suivant :

https://setosa.io/ev/image-kernels/
Essayez de comprendre les différents filtres (ici 3x3), notamment les 2 filtres sobel.
Lors de la phase d’apprentissage, c’est ces filtres de convolution qui sont appris.
Les CNNs sont particulièrement performants car ils sont capables d’apprendre des caractéristiques spatiales et locales de manière plus efficace. Parfois on dit que les CNN apprennent une “hiérarchie de caractéristiques” : les premières couches capturent des caractéristiques simples (comme les bords et les textures), tandis que les couches plus profondes détectent des structures plus complexes (comme des formes et des objets entiers).
Nous allons de nouveau commencer par une exploration interactive d’un CNN qui est déjà entraîné, et qui permet de prédire des chiffres manuscrits que vous allez vous même écrire. Cette étape d’inférence est calculé en emps réel :

Pour cela, avec votre navigateur, ouvrez l’URL suivant :
https://adamharley.com/nn_vis/cnn/2d.html
Avec votre souris, dessinez un chiffre en haut à gauche, et essayez de comprendre le résultat. Survolez les “neurones” de ce réseau, et visualisez les connections. Cliquez aussi sur plusieurs “neurones” (ou pixels) d’une couche de convolution. Notez que la première couche de convolution ici a 6 canaux (channels), et que les 6 images résultants de l’application des fitres de convolution sont appelées des feature maps.
Cliquez sur un pixel d’une des feature maps de la première couche de convolution. Quelle est la taille du filtre de convolution qui a été appris (est-ce que c’est 3x3 ou 5x5 ou 7x7 ou 10x5) ?
Cliquez sur deux différents pixels d’une même feature map de la première couche de convolution. Est-ce que le filtre de convolution, qui a été appris dans ce canal, et le même filtre pour tous les pixels de cette feature map ?
Cliquez sur un pixel d’une feature map d’un autre canal, mais toujours dans la même couche de convolution. Est-ce que c’est le même filtre de convolution pour tous les canaux ?
Exercice 3.12 : Etape 5 : Choisir et entrainer un modèle
Nous allons continuer à ajouter des cellules dans votre notebook actuel.
Premièrement, nous allons maintenant définir un modèle CNN qui reproduira l’architecture du réseau de neurones utilisé dans la visualisation CNN de l’exercice précédent.
Créez une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# 5. Choisir et entrainer un modèle (CNN)
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant :
# Créer un modèle sequentiel
model_cnn = Sequential()
# Entrée
model_cnn.add(keras.Input(shape=(28, 28, 1)))
# Première couche de convolution
model_cnn.add(layers.Conv2D(6, kernel_size=(5, 5), activation="relu", strides=(1, 1), padding="same"))
model_cnn.add(layers.MaxPooling2D(pool_size=(2, 2)))
# Deuxième couche de convolution
model_cnn.add(layers.Conv2D(16, kernel_size=(5, 5), activation="relu"))
model_cnn.add(layers.MaxPooling2D(pool_size=(2, 2)))
model_cnn.add(layers.Flatten())
#Couches pleinement connectées
model_cnn.add(layers.Dense(120, activation="tanh"))
model_cnn.add(layers.Dense(100, activation="tanh"))
model_cnn.add(layers.Dense(10, activation="softmax"))
# Optimisation Adam avec un taux d'apprentissage spécifique
custom_optimizer = Adam(learning_rate=0.001)
# Compiler le réseau avec une fonction de perte d'entropie croisée et taux de prédictions correctes
model_cnn.compile(optimizer=custom_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
# Montrer un résumé du modèle
model_cnn.summary()Combien de couches y-a-t’il au total ?
Exécutez maintenant la cellule. Quel est le nombre de paramètres total ?
Combien de paramètres sont dans la première couche de convolution ?
Pour ce nombre de paramètres dans la première couche de convolution, expliquez comment ils sont constitués (donc pourquoi il y a le nombre de paramètres que vous avez trouvé).
Nous allons maintenant lancer l’entrainement.
#5. (CNN) Entrainer un modèle
history_cnn = model_cnn.fit(X_train_normalized, Y_train_one_hot, epochs=10, validation_split=0.375, batch_size=256)- Analysez la sortie de votre cellule. Quelle est la perte des données de validation après la dernière époque (val loss) ?
Exercice 3.13 : Etape 6 : Evaluer le modèle
Dans une cellule markdown et une cellule code, évaluez votre modèle en affichant les courbes d’apprentissage du modèle model_cnn.
Exercice 3.14 : Etape 8 : Evaluer sur la base de test
Dans une cellule markdown et une cellule code, évaluer sur la base de test.
- Quelle modèle est plus performant, le modèle FCNN ou le modèle CNN ? Expliquez.
Exercice 3.15 : Data Labeling
Dans cet exercice, vous allez participer à constituer une base d’apprentissage pour classifier (et plus tard, générer) des mini-chiffres représentés par seulement 16 pixels.
Cette étape est appelé Data labeling ou en français, étiquetage de données.
Pour maximiser notre chance de réussite, bien que vous n’ayez pas énormément de possibilités, soyez créatif dans votre création de chiffres, tout en restent tout à fait lisible pour un humain, pour avoir un maximum de diversité de chiffres.
Veuillez svp créer vos chiffres ici :
https://patrick-reuter.emi.u-bordeaux.fr/dl2025/td3/td3-data-labeling.php
Après avoir cliqué sur “Envoyer”, vous recevez un mot d’identification.
- Quel est votre mot d’identification ?
2 Pour aller plus loin
Exercice optionnel : Etape 9 : Production : faire les prédictions
Très souvent, une fois le modèle entraîné, il est utilisé en production dans un environnement différent que celui d’entrainement. Donc, bien qu’on aie entrainé un modèle ici dans un envrionnement JupyterHub notebook avec Keras Tensorflow, nous pouvons l’utiliser dans un autre environnement, par exemple dans un objet connecté, une application mobile, ou simplement dans une page web, ce qu’on va faire maintenant.
Créez donc une nouvelle cellule Markdown et ajoutez le titre suivant, et exécutez la cellule :
# Exemple pour 9. Production : faire les prédictions dans un autre environnement
Puis, créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant, et exécutez la cellule :
# convertir un modèle TF en TFLite pour déployer dans une plateforme à faible ressources :
converter_fc = tf.lite.TFLiteConverter.from_keras_model(model=model_fc)
tfmodel_fc = converter_fc.convert()
open("td3-mnist-fc.tflite","wb").write(tfmodel_fc)
converter_cnn = tf.lite.TFLiteConverter.from_keras_model(model=model_cnn)
tfmodel_cnn = converter_cnn.convert()
open("td3-mnist-cnn.tflite","wb").write(tfmodel_cnn)Ce code sauvegarde les 2 modèles entraînés dans des fichiers .tflite, que vous pouvez ensuite utiliser dans un autre environnement.
Vous pouvez trouver un exemple de page web utilisant les deux fichiers .tflite ici (faire clic droit et enregistrer la cible du lien, pour sauvegarder ce fichier html) :
Souvenez-vous, pour mettre vos pages web sur internet, il faut les mettre dans le dossier espaces/www/ au CREMI. Vous pouvez donc créer un dossier dl2025 dans ce dossier, et y mettre les 3 fichiers (td3-mnist-inference.html et bien sûr vos modèles entraînés td3-mnist-fc.tflite et td3-mnist-cnn.tflite).
Testez. L’URL à ouvrir sera quelque chose de type
https://prenom-nom.emi.u-bordeaux.fr/dl2025/td3-mnist-inference.html
Exercice optionnel : Exploration du réseau en 3D
L’application interactive permettant de visualiser les réseaux de neurones pour la reconnaissance de chiffres manuscrits existe aussi en version 3D. Explorez le lien suivant pour les CNN :
https://adamharley.com/nn_vis/cnn/2d.html
à suivre.
3 TD à rendre
A rendre sur moodle avant le 17 Novembre 2025 à 23h59. Pour cela, il y a des liens sur la page du cours (https://moodle.u-bordeaux.fr/course/view.php?id=22721) :
4 Exemple chiffres 0 et 1
Pour être parfaitement clair, imaginez de classifier des chiffres 0 et 1, représentées dans une image de 4x4 pixels. Voici un exemple d’un réseau de neurones FC (fully-connected) avec les 16 pixels à l’entrée, et les 2 dimensions à la sortie :

16 dimensions à l’entrée
2 dimensions à la sortie (pour chiffres 0 et 1)