Transfer Learning avec Keras
Maintenant que nous avons compris le concept général du transfer learning, voyons comment l'appliquer concrètement en utilisant Keras. Keras rend le processus de transfer learning remarquablement accessible grâce à ses modules intégrés et à sa flexibilité.
Principes du Transfer Learning
Revenons sur les principes fondamentaux qui rendent le transfer learning possible et efficace :
-
Exploitation des Connaissances Pré-acquises :
- L'idée centrale est qu'un modèle entraîné sur un grand jeu de données (par exemple, ImageNet pour les images, qui contient des millions d'images réparties en milliers de classes) a déjà appris à reconnaître une vaste gamme de caractéristiques visuelles.
- Ces caractéristiques apprises ne sont pas spécifiques à la tâche initiale pour laquelle le modèle a été entraîné ; beaucoup d'entre elles sont génériques et peuvent être utiles pour d'autres tâches.
-
Hiérarchie des Caractéristiques (Surtout en Vision) :
- Dans un réseau profond (comme un CNN), les premières couches apprennent généralement des caractéristiques de bas niveau, très génériques : bords, coins, textures, couleurs, etc. Ces caractéristiques sont utiles pour presque toutes les tâches de vision par ordinateur.
- Les couches intermédiaires apprennent à combiner ces caractéristiques de bas niveau pour former des motifs plus complexes : formes simples, parties d'objets (un œil, une roue, etc.). Ces caractéristiques sont un peu plus spécifiques mais restent souvent transférables.
- Les dernières couches (en particulier les couches entièrement connectées à la fin) apprennent des caractéristiques de haut niveau, très spécifiques à la tâche d'entraînement initiale (par exemple, combiner des yeux, un nez et une bouche pour reconnaître un visage spécifique, ou combiner des caractéristiques pour classer une image dans l'une des 1000 classes d'ImageNet).
-
Transférabilité des Couches :
- Les caractéristiques apprises par les premières couches sont très transférables à de nouvelles tâches.
- Les caractéristiques apprises par les dernières couches sont moins transférables car elles sont plus spécifiques à la tâche d'origine.
-
Adaptation à la Nouvelle Tâche :
- Le transfer learning consiste à réutiliser les poids des couches qui ont appris des caractéristiques génériques (généralement les premières couches convolutives).
- On adapte ensuite les dernières couches (ou on en ajoute de nouvelles) pour qu'elles apprennent à utiliser ces caractéristiques génériques pour résoudre la nouvelle tâche (qui peut avoir des classes différentes et nécessiter moins de données).
-
Hypothèses Clés :
- La tâche source (sur laquelle le modèle a été pré-entraîné) doit être suffisamment reliée à la tâche cible (la nouvelle tâche). Transférer un modèle entraîné sur des images pour faire de l'analyse de sentiments de texte n'aurait pas de sens.
- Le jeu de données source doit être grand et diversifié pour que le modèle ait appris des caractéristiques robustes et générales.
Le succès du transfer learning repose sur l'idée que les représentations apprises par les réseaux profonds sur de grands jeux de données capturent des connaissances fondamentales sur le monde (visuel, textuel, etc.) qui peuvent être réutilisées pour de nouvelles tâches.
Utilisation de Modèles Pré-Entraînés Disponibles dans Keras (tf.keras.applications)
Keras facilite grandement l'utilisation de modèles pré-entraînés grâce au module tf.keras.applications. Ce module contient plusieurs architectures de réseaux de neurones de pointe, principalement pré-entraînées sur le jeu de données ImageNet.
Qu'est-ce qu'ImageNet ?
ImageNet est une très grande base de données visuelle conçue pour être utilisée dans la recherche sur la reconnaissance d'objets visuels. Elle contient plus de 14 millions d'images annotées manuellement selon la hiérarchie WordNet. Le sous-ensemble le plus couramment utilisé pour l'entraînement et la compétition (ImageNet Large Scale Visual Recognition Challenge - ILSVRC) contient environ 1,2 million d'images réparties en 1000 classes d'objets (par exemple, différentes races de chiens, de chats, types de voitures, d'avions, d'objets du quotidien, etc.).
Implication pour le Transfer Learning :
Étant donné que la plupart des modèles dans tf.keras.applications sont pré-entraînés sur ImageNet, ils ont appris à reconnaître une très large gamme de caractéristiques visuelles. C'est pourquoi ils sont particulièrement efficaces pour les tâches de vision par ordinateur :
- Classification d'images (même avec de nouvelles classes non présentes dans ImageNet).
- Détection d'objets.
- Segmentation d'images.
- Extraction de caractéristiques d'images pour d'autres tâches.
Important : Utiliser ces modèles pré-entraînés pour des tâches non visuelles (comme l'analyse de sentiments de texte, la prédiction de séries temporelles, etc.) n'aurait généralement pas de sens. Les caractéristiques apprises sur les images ne seraient pas pertinentes pour ces autres types de données. Pour ces tâches, il faudrait utiliser des modèles pré-entraînés spécifiques au domaine (par exemple, des modèles de langage pré-entraînés comme BERT, GPT pour le texte).
Modèles Pré-Entraînés Courants
Voici quelques-uns des modèles pré-entraînés les plus populaires disponibles dans tf.keras.applications, tous entraînés sur ImageNet :
- VGG16 / VGG19 :
- Développés par le Visual Geometry Group de l'Université d'Oxford.
- Performants pour leur époque, mais lourds (beaucoup de paramètres).
- ResNet (ResNet50, ResNet101, ResNet152, ResNet50V2, etc.) :
- Développés par Microsoft Research.
- Ont révolutionné l'entraînement de réseaux très profonds grâce aux connexions résiduelles.
- Excellent compromis performance/complexité.
- Inception (InceptionV3, InceptionResNetV2) :
- Développés par Google.
- Utilisent des "modules Inception" pour capturer des caractéristiques à différentes échelles.
- Très performants.
- MobileNet (MobileNet, MobileNetV2) :
- Développés par Google.
- Optimisés pour la performance sur les appareils mobiles (légers et rapides).
- EfficientNet (EfficientNetB0 à B7) :
- Développés par Google.
- Utilisent une technique de "mise à l'échelle composée" pour optimiser l'architecture.
- Très haute performance pour une taille de modèle donnée.
- Autres : Xception, DenseNet, NASNet, etc. (également entraînés sur ImageNet).
Charger un Modèle Pré-Entraîné
Vous pouvez charger un modèle pré-entraîné très facilement en Keras :
import tensorflow as tf
# Charger VGG16 avec les poids pré-entraînés sur ImageNet
# include_top=False : On exclut la couche de classification finale (Dense)
base_model = tf.keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
Explication des arguments :
weights='imagenet': Charge les poids pré-entraînés sur ImageNet. Siweights=None, le modèle est initialisé aléatoirement (vous n'utilisez que l'architecture).include_top=False: C'est l'option la plus importante pour le transfer learning. Elle permet d'exclure la ou les dernières couches du modèle, qui sont spécifiques à la tâche d'entraînement initiale (la classification ImageNet à 1000 classes). Nous allons ajouter nos propres couches de classification adaptées à notre nouvelle tâche. Siinclude_top=True, le modèle complet est chargé, y compris la couche de classification ImageNet.input_shape=(224, 224, 3): La forme des images d'entrée attendue par le modèle. La plupart des modèles pré-entraînés sur ImageNet attendent des images de taille 224x224 avec 3 canaux de couleur. Si vos images ont une taille différente, vous devrez les redimensionner. Siinclude_top=False, vous pouvez spécifier une autreinput_shape, mais il est souvent préférable d'utiliser la taille pour laquelle le modèle a été entraîné.
Geler les Couches de Base
Une fois le modèle de base chargé, il est courant de geler ses poids, au moins initialement. Cela signifie que les poids des couches convolutives pré-entraînées ne seront pas mis à jour pendant l'entraînement de votre nouveau modèle. Vous ne ferez qu'entraîner les couches que vous ajoutez par-dessus.
# Geler les poids du modèle de base
base_model.trainable = False
Ajouter de Nouvelles Couches
Après avoir chargé et éventuellement gelé le modèle de base, vous pouvez l'intégrer dans un nouveau modèle Sequential (ou fonctionnel) et ajouter vos propres couches par-dessus :
model = tf.keras.Sequential([
base_model, # Le modèle VGG16 pré-entraîné
tf.keras.layers.Flatten(), # Aplatir la sortie du modèle de base
tf.keras.layers.Dense(units=256, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(units=num_classes, activation='softmax') # num_classes = nombre de classes de votre nouvelle tâche
])
Compilation et Entraînement
Vous compilez et entraînez ensuite ce nouveau modèle comme d'habitude, mais en utilisant vos propres données et vos propres étiquettes.
model.compile(optimizer='adam',
loss='categorical_crossentropy', # Ou sparse_... si étiquettes entières
metrics=['accuracy'])
# Entraînement (avec vos données x_train, y_train, x_val, y_val)
history = model.fit(x_train, y_train, epochs=..., validation_data=(x_val, y_val), ...)
Le module tf.keras.applications rend l'utilisation de modèles pré-entraînés très simple. En quelques lignes de code, vous pouvez charger une architecture de pointe avec des poids appris sur des millions d'images, et l'adapter à votre propre tâche. N'oubliez pas de spécifier include_top=False pour pouvoir ajouter vos propres couches de classification.
Assurez-vous que la tâche pour laquelle vous souhaitez utiliser un modèle pré-entraîné est compatible avec la tâche sur laquelle il a été entraîné. La plupart des modèles dans tf.keras.applications sont conçus pour la vision par ordinateur.
Modèles Pré-Entraînés pour d'Autres Tâches (NLP, Audio...)
Si la plupart des modèles dans tf.keras.applications sont optimisés pour la vision par ordinateur (car entraînés sur ImageNet), il existe de nombreux modèles pré-entraînés pour d'autres types de tâches, en particulier en Traitement Automatique du Langage Naturel (NLP).
Traitement Automatique du Langage Naturel (NLP)
Le NLP a connu une véritable révolution ces dernières années grâce aux modèles pré-entraînés, souvent basés sur l'architecture Transformer. Ces modèles sont entraînés sur d'énormes quantités de texte (des milliards de mots provenant de livres, d'articles, de sites web, etc.) et apprennent des représentations riches du langage.
Modèles Pré-Entraînés Courants en NLP :
- Word Embeddings (Concepts Précurseurs) :
- Word2Vec, GloVe, FastText : Bien qu'il ne s'agisse pas de modèles complets au sens des CNNs, ce sont des représentations vectorielles de mots pré-entraînées. Elles capturent des relations sémantiques entre les mots (par exemple, "roi" - "homme" + "femme" ≈ "reine"). On peut charger ces embeddings comme poids initiaux pour une couche
Embeddingdans Keras.
- Word2Vec, GloVe, FastText : Bien qu'il ne s'agisse pas de modèles complets au sens des CNNs, ce sont des représentations vectorielles de mots pré-entraînées. Elles capturent des relations sémantiques entre les mots (par exemple, "roi" - "homme" + "femme" ≈ "reine"). On peut charger ces embeddings comme poids initiaux pour une couche
- Modèles Basés sur les Transformers :
- BERT (Bidirectional Encoder Representations from Transformers) : Développé par Google. Excellent pour les tâches de compréhension du langage (classification de texte, réponse à des questions, reconnaissance d'entités nommées). Il apprend le contexte d'un mot en regardant à la fois les mots qui le précèdent et ceux qui le suivent.
- GPT (Generative Pre-trained Transformer) : Développé par OpenAI (GPT-2, GPT-3, GPT-4, etc.). Excellent pour les tâches de génération de texte (complétion de texte, traduction, résumé, écriture créative). Il apprend à prédire le mot suivant dans une séquence.
- T5 (Text-to-Text Transfer Transformer) : Développé par Google. Unifie toutes les tâches NLP sous un format texte-vers-texte (l'entrée est un texte décrivant la tâche, la sortie est le résultat textuel).
- BART (Bidirectional and Auto-Regressive Transformers) : Développé par Facebook AI. Combine des idées de BERT et GPT. Bon pour la génération et la compréhension.
- Et bien d'autres : RoBERTa, ALBERT, XLNet, DistilBERT (version plus légère de BERT), etc.
Où les Trouver ?
Ces modèles ne sont généralement pas inclus directement dans tf.keras.applications. La communauté utilise massivement des bibliothèques spécialisées comme :
- Hugging Face Transformers : Une bibliothèque Python très populaire qui fournit un accès facile à des milliers de modèles pré-entraînés (pour TensorFlow et PyTorch) et à des outils pour le fine-tuning. C'est la référence dans le domaine. (https://huggingface.co/models)
- TensorFlow Hub : Une plateforme de Google pour partager et réutiliser des modèles pré-entraînés, y compris des modèles NLP. (https://tfhub.dev/)
Comment les Utiliser ?
Le principe est similaire au transfer learning en vision :
- Charger le modèle pré-entraîné (par exemple, BERT ou GPT depuis Hugging Face Transformers).
- Adapter le modèle à votre tâche spécifique :
- Souvent, on ajoute une ou plusieurs couches de classification/régression à la sortie du modèle pré-entraîné.
- On effectue ensuite un fine-tuning : on ré-entraîne une partie (ou la totalité) du modèle pré-entraîné sur vos propres données, généralement avec un faible taux d'apprentissage.
Traitement Audio
Des modèles pré-entraînés existent aussi pour le traitement audio, bien que le domaine soit peut-être un peu moins standardisé que le NLP.
- Wav2Vec 2.0, HuBERT : Modèles entraînés sur de grandes quantités de données audio non étiquetées (apprentissage auto-supervisé). Ils apprennent des représentations utiles du signal audio qui peuvent ensuite être fine-tunées pour des tâches comme la reconnaissance vocale.
- D'autres modèles existent pour la classification de sons, la séparation de sources, la génération de musique, etc.
Ces modèles sont également souvent disponibles via des bibliothèques comme Hugging Face ou TensorFlow Hub.
Fine-tuning vs. Feature Extraction
Lorsque vous utilisez un modèle pré-entraîné pour le transfer learning, il existe deux stratégies principales pour l'adapter à votre nouvelle tâche :
- Feature Extraction (Extraction de Caractéristiques)
- Fine-tuning (Ajustement Fin)
Feature Extraction
-
Principe : Vous utilisez le modèle pré-entraîné comme un extracteur de caractéristiques fixe. Vous prenez les couches convolutives du modèle pré-entraîné (souvent appelées la "base convolutive") et vous gelez leurs poids. Vous ne faites qu'entraîner les nouvelles couches de classification que vous avez ajoutées par-dessus (le "classifieur" ou la "tête").
-
Quand l'utiliser ? :
- Quand votre nouveau jeu de données est petit et similaire au jeu de données sur lequel le modèle a été pré-entraîné (par exemple, ImageNet).
- Quand les ressources de calcul sont limitées (l'entraînement est plus rapide car moins de poids sont mis à jour).
-
Implémentation (Keras) :
- Charger le modèle pré-entraîné avec
include_top=False. - Geler les poids de la base convolutive :
base_model.trainable = False. - Ajouter vos propres couches de classification par-dessus.
- Compiler et entraîner le modèle (seuls les poids des nouvelles couches seront mis à jour).
# 1. Charger et geler la base
base_model = tf.keras.applications.VGG16(weights='imagenet', include_top=False, input_shape=(...))
base_model.trainable = False
# 2. Ajouter le classifieur
model = tf.keras.Sequential([
base_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dense(num_classes, activation='softmax')
])
# 3. Compiler et entraîner
model.compile(...)
model.fit(...) # Seuls les poids des couches Dense ajoutées sont entraînés - Charger le modèle pré-entraîné avec
Fine-tuning
-
Principe : Vous commencez par entraîner uniquement le classifieur (comme dans la feature extraction), puis vous dégelez quelques-unes des couches supérieures de la base convolutive pré-entraînée et vous continuez l'entraînement avec un faible taux d'apprentissage. Cela permet d'ajuster finement les caractéristiques apprises par les couches pré-entraînées pour les adapter plus spécifiquement à votre nouveau jeu de données.
-
Quand l'utiliser ? :
- Quand votre nouveau jeu de données est plus grand et/ou moins similaire au jeu de données d'origine.
- Quand vous avez suffisamment de ressources de calcul.
- Souvent utilisé après une phase de feature extraction.
-
Implémentation (Keras) :
- Effectuer d'abord l'étape de feature extraction (entraîner le classifieur avec la base gelée).
- Dégeler quelques couches supérieures de la base convolutive :
base_model.trainable = True # Dégeler toute la base
# Optionnel mais recommandé : geler les couches inférieures
# (par exemple, geler toutes les couches sauf les 4 dernières)
for layer in base_model.layers[:-4]:
layer.trainable = False - Re-compiler le modèle avec un très faible taux d'apprentissage (pour éviter de "détruire" les poids pré-entraînés).
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-5) # Très petit !
model.compile(optimizer=optimizer, ...) - Continuer l'entraînement pendant quelques époques supplémentaires.
model.fit(...) # Entraîne maintenant les couches dégelées et le classifieur
Comparaison
| Stratégie | Description | Quand Utiliser ? | Avantages | Inconvénients |
|---|---|---|---|---|
| Feature Extraction | Utiliser le modèle pré-entraîné comme extracteur fixe. Entraîner seulement le nouveau classifieur. | Petit dataset, similaire au dataset d'origine. | Rapide, moins de données nécessaires, moins de surapprentissage. | Performance limitée si les datasets sont très différents. |
| Fine-tuning | Entraîner d'abord le classifieur, puis dégeler quelques couches supérieures et ré-entraîner avec un faible LR. | Grand dataset, et/ou moins similaire au dataset d'origine. | Potentiellement de meilleures performances. | Plus lent, plus de risque de surapprentissage, nécessite un réglage fin du LR. |
Quelle Stratégie Choisir ?
La meilleure stratégie dépend de la taille de votre jeu de données et de sa similarité avec le jeu de données sur lequel le modèle a été pré-entraîné (ImageNet dans la plupart des cas) :
- Petit dataset, similaire : Feature extraction seule est souvent suffisante.
- Petit dataset, différent : La situation la plus délicate. Feature extraction est un point de départ, mais le fine-tuning peut être difficile. Il pourrait être préférable d'essayer de ne fine-tuner que très peu de couches, voire aucune. L'augmentation de données est cruciale ici.
- Grand dataset, similaire : Fine-tuning est susceptible de donner les meilleures performances. Commencez par la feature extraction, puis effectuez le fine-tuning.
- Grand dataset, différent : Fine-tuning est la meilleure approche. Comme vous avez beaucoup de données, vous pouvez potentiellement fine-tuner une plus grande partie du réseau (voire l'entraîner à partir de zéro, mais le transfer learning reste souvent bénéfique comme point de départ).
La feature extraction est une approche plus simple et plus rapide, idéale lorsque les données sont limitées. Le fine-tuning est plus complexe et nécessite plus de données, mais peut potentiellement conduire à de meilleures performances en adaptant plus finement le modèle pré-entraîné à la nouvelle tâche. Il est courant de commencer par la feature extraction, puis de passer au fine-tuning.
Exemple Pratique : Classification Chiens vs Chats avec un Modèle Pré-Entraîné
Dans cet exemple, nous allons utiliser le transfer learning pour construire un classifieur d'images capable de distinguer les chiens des chats. Nous utiliserons un modèle pré-entraîné sur ImageNet comme point de départ et l'adapterons à cette nouvelle tâche binaire. Nous utiliserons la stratégie de feature extraction.
Le Jeu de Données "cats_vs_dogs"
Ce jeu de données, accessible via TensorFlow Datasets (TFDS), contient des milliers d'images couleur de tailles variables, étiquetées comme "chat" (classe 0) ou "chien" (classe 1). C'est un problème de classification binaire.
Frature extraction
Pour utiliser TensorFlow Datasets, vous devrez l'installer :
pip install tensorflow-datasets
# Importation des librairies
import tensorflow as tf
import tensorflow_datasets as tfds
import numpy as np
import matplotlib.pyplot as plt
# Pour la reproductibilité (optionnel)
tf.random.set_seed(42)
Nous importons les librairies nécessaires, y compris tensorflow_datasets pour charger notre jeu de données.
# 1. Chargement et Prétraitement des Données avec TFDS
IMG_SIZE = 160 # Taille des images que nous utiliserons (adaptée à MobileNetV2)
BATCH_SIZE = 32 # Taille des lots pour l'entraînement
# Charger le jeu de données 'cats_vs_dogs'
# 'split' divise en entraînement (80%) et test (20%)
# 'with_info=True' nous donne des informations sur le dataset
# 'as_supervised=True' charge les données sous forme de tuples (image, label)
dataset, info = tfds.load('cats_vs_dogs', split=['train[:80%]', 'train[80%:]'],
with_info=True, as_supervised=True)
train_dataset = dataset[0]
test_dataset = dataset[1]
num_train_examples = info.splits['train[:80%]'].num_examples
num_test_examples = info.splits['train[80%:]'].num_examples
print(f"Nombre d'exemples d'entraînement: {num_train_examples}")
print(f"Nombre d'exemples de test: {num_test_examples}")
# Fonction de prétraitement
def format_example(image, label):
image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE)) # Redimensionner
return image, label
# Appliquer le prétraitement et configurer les datasets pour la performance
train_batches = train_dataset.map(format_example).shuffle(num_train_examples//4).batch(BATCH_SIZE).prefetch(1)
test_batches = test_dataset.map(format_example).batch(BATCH_SIZE).prefetch(1)
# Visualiser quelques images du premier batch d'entraînement
for image_batch, label_batch in train_batches.take(1):
plt.figure(figsize=(10,10))
for i in range(9):
ax = plt.subplot(3,3,i+1)
plt.imshow(image_batch[i].numpy().astype("uint8"))
label = info.features['label'].int2str(label_batch[i])
plt.title(label)
plt.axis("off")
plt.show()

Explications (Chargement et Prétraitement) :
tfds.load('cats_vs_dogs', ...): Charge le jeu de données.split=['train[:80%]', 'train[80%:]']: Divise automatiquement les données d'entraînement disponibles dans TFDS en 80% pour l'entraînement (train_dataset) et 20% pour le test (test_dataset). Notez que ce dataset n'a pas de split de validation prédéfini, nous utiliseronsvalidation_data=test_batchesdansfitpour cet exemple, bien qu'en pratique il soit préférable d'avoir un ensemble de validation distinct.with_info=True: Récupère des informations sur le dataset (nombre d'exemples, noms des classes, etc.).as_supervised=True: Charge les données sous forme de tuples(image, label).
format_example(image, label): Fonction simple pour redimensionner chaque image àIMG_SIZE(160x160).train_dataset.map(format_example): Applique la fonction de redimensionnement à chaque exemple du dataset d'entraînement..shuffle(num_train_examples//4): Mélange les données d'entraînement.buffer_sizeest mis à 1/4 de la taille pour un bon mélange sans utiliser trop de mémoire..batch(BATCH_SIZE): Regroupe les données en lots de tailleBATCH_SIZE(32)..prefetch(1): Prépare le prochain batch pendant que le modèle traite le batch actuel, pour optimiser les performances.- Visualisation : Affiche 9 images du premier batch d'entraînement avec leurs étiquettes correspondantes.
# 2. Chargement du Modèle Pré-Entraîné (Feature Extraction)
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
# Utilisons MobileNetV2
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=False, # Important !
weights='imagenet')
# Geler la base convolutive
base_model.trainable = False
# Visualisation de l'architecture de la base
base_model.summary()
Total params: 2,257,984 (8.61 MB)
Trainable params: 0 (0.00 B)
Non-trainable params: 2,257,984 (8.61 MB)
Explications (Chargement et Gel) :
tf.keras.applications.MobileNetV2: Nous chargeons MobileNetV2, un modèle efficace pré-entraîné sur ImageNet.input_shape=IMG_SHAPE: Définit la forme des images d'entrée (160x160x3).include_top=False: Crucial. Exclut la couche de classification ImageNet (1000 classes).weights='imagenet': Charge les poids pré-entraînés.base_model.trainable = False: Très important. Gèle les poids de la base MobileNetV2. Nous allons l'utiliser uniquement comme extracteur de caractéristiques. Les poids de cette partie ne seront pas mis à jour pendant l'entraînement.
# 3. Création du Nouveau Modèle (Ajout du Classifieur)
# Fonction de prétraitement spécifique à MobileNetV2
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
model = tf.keras.Sequential([
# Note : On n'inclut pas la fonction de prétraitement ici,
# car il est préférable de l'appliquer aux données *avant*
# de les passer au modèle pour des raisons de performance.
# Cependant, on pourrait l'ajouter comme première couche :
# tf.keras.layers.InputLayer(input_shape=IMG_SHAPE),
# tf.keras.layers.Lambda(preprocess_input),
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(units=1, activation='sigmoid') # Couche de sortie pour classification binaire
])
Explications (Nouveau Modèle) :
preprocess_input: Chaque modèle pré-entraîné a souvent sa propre fonction de prétraitement recommandée (par exemple, pour mettre à l'échelle les pixels dans une plage spécifique, comme [-1, 1] pour MobileNetV2). Il est préférable d'appliquer cette fonction avant d'entraîner ou d'utiliser le modèle. Nous l'appliquerons aux datasets plus bas.base_model: Notre base MobileNetV2 gelée.tf.keras.layers.GlobalAveragePooling2D(): Calcule la moyenne spatiale sur les cartes de caractéristiques en sortie de la base. Cela réduit la dimensionnalité de manière significative avant la couche Dense finale.tf.keras.layers.Dense(units=1, activation='sigmoid'): Notre couche de classification finale. 1 neurone car c'est une classification binaire, et 'sigmoid' pour obtenir une probabilité entre 0 et 1.
# Appliquer la fonction de prétraitement aux datasets
train_batches_processed = train_batches.map(lambda x, y: (preprocess_input(x), y))
test_batches_processed = test_batches.map(lambda x, y: (preprocess_input(x), y))
# 4. Compilation
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001), # Un LR plus faible est souvent bon pour le transfer learning
loss='binary_crossentropy',
metrics=['accuracy'])
# Affichage du résumé
model.summary()
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ mobilenetv2_1.00_160 │ (None, 5, 5, 1280) │ 2,257,984 │
│ (Functional) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d │ (None, 1280) │ 0 │
│ (GlobalAveragePooling2D) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense) │ (None, 1) │ 1,281 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 2,259,265 (8.62 MB)
Trainable params: 1,281 (5.00 KB)
Non-trainable params: 2,257,984 (8.61 MB)
Explications (Prétraitement final et Compilation) :
- Application de
preprocess_input: Nous utilisons.map()pour appliquer la fonctionpreprocess_inputde MobileNetV2 à nos datasetstrain_batchesettest_batches. - Compilation :
- Optimiseur : Adam, mais avec un taux d'apprentissage (
learning_rate) plus faible (0.0001). C'est souvent une bonne pratique lors de l'utilisation du transfer learning, même en feature extraction, pour éviter de perturber les caractéristiques extraites. - Perte :
binary_crossentropy(classification binaire). - Métrique :
accuracy.
- Optimiseur : Adam, mais avec un taux d'apprentissage (
model.summary(): Affiche l'architecture. Observez le grand nombre de paramètres non entraînables (ceux de MobileNetV2) par rapport aux paramètres entraînables (ceux de la couche Dense finale).
# 5. Entraînement (Feature Extraction)
EPOCHS = 10
history = model.fit(train_batches_processed,
epochs=EPOCHS,
validation_data=test_batches_processed) # Utilisation de test_batches comme validation ici
Explications (Entraînement) :
- Nous entraînons le modèle sur les
train_batches_processed. - Nous utilisons
test_batches_processedcomme données de validation pour surveiller la performance (en pratique, il faudrait un ensemble de validation distinct). - Seuls les poids de la dernière couche
Densesont mis à jour. L'entraînement devrait être relativement rapide.
# 6. Évaluation
loss, accuracy = model.evaluate(test_batches_processed, verbose=0)
print(f"Perte (test) : {loss:.4f}")
print(f"Exactitude (test) : {accuracy * 100:.2f}%")
# 7. Visualisation
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
Perte (test) : 0.0459
Exactitude (test) : 98.39%

Explications (Évaluation et Visualisation) :
- Nous évaluons le modèle sur les données de test (qui ont aussi été prétraitées). L'exactitude devrait être assez élevée (>90%) après seulement quelques époques, démontrant l'efficacité de la feature extraction.
- Nous visualisons les courbes d'apprentissage.
Analyse :
Cet exemple montre comment utiliser un modèle pré-entraîné (MobileNetV2) comme extracteur de caractéristiques pour une nouvelle tâche (classification chiens/chats). En gelant la base pré-entraînée et en entraînant seulement une petite couche de classification, nous obtenons de bons résultats rapidement et avec peu de données (par rapport à l'entraînement d'un CNN complet à partir de zéro).
Fine-tuning
Après avoir entraîné notre modèle en mode feature extraction (où la base MobileNetV2 était gelée), nous pouvons maintenant essayer d'améliorer encore les performances en effectuant un fine-tuning. Cela consiste à dégeler une partie de la base pré-entraînée et à continuer l'entraînement avec un très faible taux d'apprentissage.
Prérequis : Nous supposons que vous avez déjà exécuté le code de la partie "Feature Extraction" ci-dessus et que vous disposez du modèle entraîné (model) et de la base pré-entraînée (base_model).
# Reprendre le modèle entraîné en mode feature extraction
# (Supposons que 'model' et 'base_model' existent déjà)
# 1. Dégeler la base convolutive
base_model.trainable = True
# Optionnel mais recommandé : Geler les couches inférieures
# Décidons de ne fine-tuner que les couches à partir du bloc 100 (par exemple)
# (Le nombre exact de couches à dégeler est un hyperparamètre à régler)
fine_tune_at = 100
# Geler toutes les couches avant la couche `fine_tune_at`
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
# 2. Re-compiler le modèle avec un faible taux d'apprentissage
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-5) # Très faible LR !
model.compile(optimizer=optimizer,
loss='binary_crossentropy',
metrics=['accuracy'])
# Afficher le résumé pour voir les paramètres entraînables mis à jour
model.summary()
Explications (Dégel et Re-compilation) :
base_model.trainable = True: Nous dégelons d'abord l'ensemble de la base MobileNetV2. Cela active tous ses poids pour l'entraînement.- Geler les couches inférieures (Optionnel mais important) :
- Nous ne voulons généralement pas ré-entraîner toutes les couches de la base. Les premières couches ont appris des caractéristiques très génériques (bords, textures) qui sont probablement déjà très utiles. Modifier ces poids risquerait de les "casser" (catastrophic forgetting).
- Nous choisissons donc de ne fine-tuner que les couches supérieures de la base (par exemple, à partir de la couche 100). Le choix de
fine_tune_at = 100est arbitraire ; c'est un hyperparamètre que vous pourriez ajuster. - Nous parcourons les couches de
base_modelet mettonslayer.trainable = Falsepour toutes les couches avant l'indicefine_tune_at.
- Re-compilation :
- Il est crucial de re-compiler le modèle après avoir modifié le statut
trainabledes couches. - Nous utilisons un très faible taux d'apprentissage (
learning_rate=1e-5). C'est essentiel pour le fine-tuning. Nous voulons seulement faire de petits ajustements aux poids pré-entraînés, sans les modifier radicalement. Un taux d'apprentissage trop élevé détruirait les caractéristiques apprises pendant le pré-entraînement. - Nous conservons la même fonction de perte et les mêmes métriques.
- Il est crucial de re-compiler le modèle après avoir modifié le statut
model.summary(): Le résumé du modèle montrera maintenant un plus grand nombre de paramètres entraînables, car nous avons dégelé une partie de la base.
# 3. Continuer l'entraînement (Fine-tuning)
# Nombre d'époques pour le fine-tuning (généralement moins que pour la feature extraction)
fine_tune_epochs = 10
total_epochs = EPOCHS + fine_tune_epochs # EPOCHS vient de la phase de feature extraction
# Continuer l'entraînement
history_fine = model.fit(train_batches_processed,
epochs=total_epochs,
initial_epoch=history.epoch[-1] + 1, # Démarrer à l'époque suivante
validation_data=test_batches_processed)
# Pas besoin de spécifier les callbacks ici si on veut juste quelques époques
Explications (Continuer l'entraînement) :
- Nous utilisons à nouveau
model.fit(). epochs=total_epochs: Nous spécifions le nombre total d'époques souhaitées (celles de la feature extraction + celles du fine-tuning).initial_epoch=history.epoch[-1] + 1: Important. Indique àfit()de reprendre l'entraînement là où il s'était arrêté (en termes de numéro d'époque pour les courbes d'historique).history.epoch[-1]donne la dernière époque atteinte lors de la phase de feature extraction.- Nous entraînons pendant
fine_tune_epochsépoques supplémentaires (ici, 10).
# 4. Évaluation après Fine-tuning
loss, accuracy = model.evaluate(test_batches_processed, verbose=0)
print(f"Perte (test) après fine-tuning : {loss:.4f}")
print(f"Exactitude (test) après fine-tuning : {accuracy * 100:.2f}%")
Perte (test) après fine-tuning : 0.0491
Exactitude (test) après fine-tuning : 98.13%
Analyse :
Le fine-tuning peut améliorer légèrement l'exactitude par rapport à la feature extraction seule, en adaptant plus finement les caractéristiques aux données spécifiques des chiens et des chats. Cependant, l'amélioration n'est pas toujours garantie et peut être faible.
- Si la performance s'améliore pendant le fine-tuning (l'exactitude de validation augmente), c'est un succès.
- Si la performance stagne ou diminue, cela peut signifier que :
- Le taux d'apprentissage pour le fine-tuning était trop élevé.
- Trop de couches ont été dégelées.
- La feature extraction était déjà suffisante pour ce problème et ces données.
Ici en l'occurence, l'exactitude a diminué, le fine-tuning n'était pas vraiment nécessaire car on avait déjà une très bonne exactitude après le feature extraction.
Le fine-tuning nécessite souvent plus d'expérimentation (choix des couches à dégeler, taux d'apprentissage) que la simple feature extraction.
Vous avez tout à fait raison de pointer les difficultés potentielles avec CIFAR-10, surtout si l'on combine le redimensionnement (qui peut dégrader les petites images) et la complexité inhérente du jeu de données. CIFAR-10 est notoirement plus difficile que MNIST pour les CNNs simples et même pour le transfer learning de base.
Prenons un autre jeu de données, peut-être un peu plus simple que CIFAR-10 mais plus complexe et plus adapté au transfer learning que MNIST : le jeu de données Beans de TensorFlow Datasets (TFDS). Il contient des images de feuilles de haricots avec 3 classes (2 maladies et 1 feuille saine). Les images sont plus grandes et plus "naturelles" que MNIST.
Exercice : Classification de Feuilles de Haricot avec Transfer Learning (Keras)
Dans cet exercice, vous allez appliquer les techniques de transfer learning (feature extraction et fine-tuning) pour classer des images de feuilles de haricot en fonction de leur état (sain ou malade). Nous utiliserons le jeu de données "Beans" disponible via TensorFlow Datasets (TFDS).
Le Jeu de Données "Beans"
- Contient des images couleur de feuilles de haricot.
- Taille des images : 500x500 pixels.
- 3 classes :
- Angular Leaf Spot (Maladie)
- Bean Rust (Maladie)
- Healthy (Sain)
- Le jeu de données est déjà divisé en ensembles d'entraînement, de validation et de test.
N'oubliez pas d'installer TensorFlow Datasets si ce n'est pas déjà fait :
pip install tensorflow-datasets
Ce que Vous Devez Faire
Partie 1 : Préparation
-
Chargement et Prétraitement des Données :
- Chargez le jeu de données 'beans' avec
tfds.load(). Assurez-vous de récupérer les ensembles 'train', 'validation', et 'test'. Récupérez également les informations du dataset (with_info=True). - Explorez les données :
- Affichez les informations sur le dataset (nombre d'exemples, noms des classes).
- Visualisez quelques images de chaque ensemble.
- Créez une fonction de prétraitement qui :
- Redimensionne les images à une taille compatible avec le modèle pré-entraîné choisi (par exemple, 160x160).
- Normalise les pixels (par exemple, entre 0 et 1, ou en utilisant la fonction de prétraitement spécifique au modèle choisi).
- Appliquez cette fonction de prétraitement aux trois ensembles de données (train, validation, test) en utilisant
dataset.map(). - Configurez les datasets pour la performance en utilisant
.batch()et.prefetch().
- Chargez le jeu de données 'beans' avec
-
Choix du Modèle Pré-entraîné :
- Sélectionnez un modèle pré-entraîné (par exemple,
MobileNetV2). - Chargez-le avec
weights='imagenet'etinclude_top=False. Spécifiez l'input_shapecorrespondant à la taille de vos images redimensionnées.
- Sélectionnez un modèle pré-entraîné (par exemple,
Partie 2 : Feature Extraction
- Geler la Base :
base_model.trainable = False. - Créer le Nouveau Modèle :
- Utilisez
tf.keras.Sequential. - Ajoutez une couche
Lambdapour appliquer la fonction de prétraitement spécifique au modèle choisi (si vous ne l'avez pas déjà fait à l'étape 1). - Ajoutez le
base_modelgelé. - Ajoutez
GlobalAveragePooling2D. - Ajoutez éventuellement du
Dropout. - La couche de sortie doit avoir 3 neurones (une pour chaque classe) et une activation
softmax.
- Utilisez
- Compiler le Modèle :
- Optimiseur :
Adam(avec unlearning_ratede ~0.001). - Fonction de perte :
sparse_categorical_crossentropy(car les étiquettes de TFDS sont généralement des entiers). - Métrique :
accuracy.
- Optimiseur :
- Entraîner le Modèle (Feature Extraction) :
- Utilisez
model.fit()avec les datasets d'entraînement et de validation. - Utilisez le callback
EarlyStopping.
- Utilisez
- Évaluer et Analyser (Feature Extraction) :
- Évaluez sur l'ensemble de test.
- Affichez les courbes. Notez l'exactitude.
Partie 3 : Fine-tuning
- Dégeler (une partie de) la Base :
base_model.trainable = True.- Choisissez un point de départ pour le fine-tuning (par exemple,
fine_tune_at = 100) et gelez les couches inférieures.
- Re-compiler le Modèle :
- Re-compilez avec un très faible taux d'apprentissage (par exemple, 1e-5 ou 2e-5).
- Continuer l'Entraînement (Fine-tuning) :
- Entraînez pendant quelques époques supplémentaires (
initial_epoch).
- Entraînez pendant quelques époques supplémentaires (
- Évaluer et Analyser (Fine-tuning) :
- Évaluez le modèle final.
- Affichez les courbes combinées. Le fine-tuning a-t-il amélioré l'exactitude ?
Partie 4 : Prédictions
- Affichez quelques prédictions sur des images de test.
Conseils
- La taille initiale des images (500x500) est grande. Le redimensionnement est important. Expérimentez avec différentes tailles (96, 128, 160, 192, 224), mais commencez peut-être par une taille plus petite comme 128x128 ou 160x160 pour accélérer l'entraînement initial.
Solution
Solution
Voici une solution possible utilisant MobileNetV2 et le jeu de données "Beans". N'oubliez pas que l'expérimentation avec différents hyperparamètres est encouragée !
# Importation des librairies nécessaires
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import tensorflow_datasets as tfds # Utilisé ici pour simplifier le chargement
# Pour la reproductibilité (optionnel)
np.random.seed(42)
tf.random.set_seed(42)
Nous commençons par importer les librairies essentielles : TensorFlow pour le deep learning, NumPy pour les opérations numériques, Matplotlib pour la visualisation, et TensorFlow Datasets (tfds) pour charger facilement le jeu de données "Beans". Fixer les graines aléatoires (seed) permet d'obtenir des résultats reproductibles.
# 1. Chargement et Prétraitement des Données
IMG_SIZE = 160 # Taille cible pour les images (adaptée à MobileNetV2)
BATCH_SIZE = 32 # Taille des lots
# Charger le jeu de données 'beans' avec ses splits et informations
(train_ds, val_ds, test_ds), info = tfds.load(
'beans',
split=['train', 'validation', 'test'],
with_info=True,
as_supervised=True) # Charge en tuples (image, label)
# Afficher les informations
num_classes = info.features['label'].num_classes
class_names = info.features['label'].names
print(f"Nombre de classes: {num_classes}")
print(f"Noms des classes: {class_names}")
print(f"Nombre d'exemples d'entraînement: {info.splits['train'].num_examples}")
print(f"Nombre d'exemples de validation: {info.splits['validation'].num_examples}")
print(f"Nombre d'exemples de test: {info.splits['test'].num_examples}")
Nous chargeons le jeu de données "beans" directement depuis TensorFlow Datasets.
tfds.load(...): Cette fonction gère le téléchargement et la préparation du dataset.split=['train', 'validation', 'test']: Nous spécifions que nous voulons les trois ensembles prédéfinis.with_info=True: Récupère un objetinfocontenant des métadonnées utiles (nombre d'exemples, noms des classes, etc.).as_supervised=True: Charge les données sous forme de tuples(image, label), ce qui est pratique pour l'entraînement.
Nous affichons ensuite les informations clés pour comprendre la structure du jeu de données.
# Visualiser quelques images
plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1): # Prend un seul batch du dataset d'entraînement
for i in range(9): # Affiche les 9 premières images du batch
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8")) # Affiche l'image
plt.title(class_names[labels[i]]) # Affiche le nom de la classe correspondante
plt.axis("off") # Masque les axes
plt.show()

Il est toujours utile de visualiser les données pour avoir une idée de ce à quoi elles ressemblent. Nous prenons un batch de l'ensemble d'entraînement (train_ds.take(1)) et affichons les 9 premières images avec leurs étiquettes textuelles correspondantes.
# Fonction de prétraitement
def preprocess(image, label):
image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE)) # Redimensionner à la taille cible
# La normalisation spécifique à MobileNetV2 sera appliquée via une couche Lambda dans le modèle
return image, label
# Préparer les datasets pour l'entraînement
AUTOTUNE = tf.data.AUTOTUNE # Permet à TensorFlow d'optimiser les pipelines
train_dataset = train_ds.map(preprocess, num_parallel_calls=AUTOTUNE)
train_dataset = train_dataset.shuffle(1000).batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)
validation_dataset = val_ds.map(preprocess, num_parallel_calls=AUTOTUNE)
validation_dataset = validation_dataset.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)
test_dataset = test_ds.map(preprocess, num_parallel_calls=AUTOTUNE)
test_dataset = test_dataset.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)
Nous définissons une fonction de prétraitement preprocess qui redimensionne chaque image à la taille IMG_SIZE (160x160). Nous appliquons cette fonction à tous nos ensembles de données (train_ds, val_ds, test_ds) en utilisant map().
Ensuite, nous préparons les datasets pour l'entraînement :
.shuffle(1000): Mélange l'ensemble d'entraînement pour éviter que le modèle n'apprenne l'ordre des données..batch(BATCH_SIZE): Regroupe les données en lots (batches) de tailleBATCH_SIZE..prefetch(buffer_size=AUTOTUNE): Prépare les prochains lots de données pendant que le modèle traite le lot actuel, ce qui améliore les performances.
# 2. Choix et Chargement du Modèle Pré-entraîné
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
# Utilisons MobileNetV2 comme base
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=False, # Ne pas inclure le classifieur ImageNet
weights='imagenet') # Charger les poids pré-entraînés
Nous choisissons MobileNetV2 comme modèle pré-entraîné. C'est un bon choix car il est relativement léger tout en étant performant.
input_shape=IMG_SHAPE: Nous spécifions la taille des images que notre modèle recevra (160x160x3).include_top=False: Crucial. Nous retirons la dernière couche de classification du modèle MobileNetV2, car elle est spécifique aux 1000 classes d'ImageNet. Nous allons ajouter notre propre couche pour nos 3 classes de feuilles de haricot.weights='imagenet': Nous chargeons les poids qui ont été appris lors de l'entraînement sur le gigantesque jeu de données ImageNet.
# 3. Geler la Base (Feature Extraction)
base_model.trainable = False
Nous mettons trainable à False pour le base_model. Cela signifie que tous les poids à l'intérieur de MobileNetV2 sont gelés et ne seront pas mis à jour pendant la première phase d'entraînement (feature extraction). Nous utilisons le modèle pré-entraîné uniquement pour extraire des caractéristiques pertinentes des images.
# 4. Créer le Nouveau Modèle
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
model = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=IMG_SHAPE), # Couche d'entrée explicite
tf.keras.layers.Lambda(lambda x: preprocess_input(x)), # Applique le prétraitement MobileNetV2
base_model, # Base convolutive gelée
tf.keras.layers.GlobalAveragePooling2D(), # Pooling global moyen
tf.keras.layers.Dropout(0.3), # Un peu de Dropout pour la régularisation
tf.keras.layers.Dense(units=num_classes, activation='softmax') # Couche de sortie (3 classes)
])
Nous construisons notre modèle final en utilisant l'API Sequential :
InputLayer: Spécifie la forme d'entrée.Lambda(preprocess_input): Une couche spéciale qui applique la fonctionpreprocess_inputde MobileNetV2 aux images d'entrée. Cette fonction normalise les pixels dans la plage [-1, 1], comme attendu par MobileNetV2.base_model: Le modèle MobileNetV2 gelé, qui agit comme extracteur de caractéristiques.GlobalAveragePooling2D: Réduit la dimensionnalité spatiale de la sortie debase_model. Au lieu d'aplatir toutes les cartes de caractéristiques (ce qui créerait un très grand vecteur), cette couche calcule la moyenne de chaque carte, résultant en un vecteur beaucoup plus petit. Cela aide à réduire le nombre de paramètres et le surapprentissage.Dropout(0.3): Ajoute une couche de dropout (30%) pour régulariser et réduire le surapprentissage.Dense(units=num_classes, activation='softmax'): La couche de sortie finale.num_classes(qui vaut 3 ici) est le nombre de neurones, etsoftmaxest utilisé pour obtenir une distribution de probabilité sur les 3 classes.
# 5. Compiler le Modèle (Feature Extraction)
base_learning_rate = 0.001 # Taux d'apprentissage pour cette phase
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate),
loss='sparse_categorical_crossentropy', # car les labels sont des entiers
metrics=['accuracy'])
model.summary() # Afficher l'architecture et les paramètres
Nous compilons le modèle pour la phase de feature extraction :
optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate): Nous utilisons l'optimiseur Adam. Un taux d'apprentissage comme 0.001 est un bon point de départ pour entraîner le nouveau classifieur.loss='sparse_categorical_crossentropy': La fonction de perte adaptée à la classification multiclasse lorsque les étiquettes sont des entiers.metrics=['accuracy']: Nous suivons l'exactitude.model.summary(): Affiche les couches du modèle et le nombre de paramètres. Notez le grand nombre de paramètres non entraînables (ceux debase_model) et le petit nombre de paramètres entraînables (ceux des couchesDenseetDropoutajoutées).
# 6. Entraîner le Modèle (Feature Extraction)
EPOCHS_FE = 10 # Nombre d'époques pour la feature extraction
early_stop_fe = tf.keras.callbacks.EarlyStopping(
monitor='val_accuracy', patience=5, mode='max', restore_best_weights=True)
history_fe = model.fit(train_dataset,
epochs=EPOCHS_FE,
validation_data=validation_dataset, # Utiliser l'ensemble de validation
callbacks=[early_stop_fe])
Nous entraînons le modèle pendant EPOCHS_FE époques.
- Seuls les poids des couches que nous avons ajoutées (
GlobalAveragePooling2D,Dropout,Densede sortie) sont mis à jour. - Nous utilisons l'ensemble de validation (
validation_dataset) pour surveiller la performance et guider les callbacks. - Les callback et
EarlyStoppingsont utilisés pour sauvegarder le meilleur modèle et arrêter l'entraînement si la performance de validation ne s'améliore plus.
# 7. Évaluer et Analyser (Feature Extraction)
loss_fe, accuracy_fe = model.evaluate(test_dataset)
print(f"Exactitude après Feature Extraction : {accuracy_fe * 100:.2f}%")
# Sauvegarder l'historique pour la visualisation combinée plus tard
acc_fe = history_fe.history['accuracy']
val_acc_fe = history_fe.history['val_accuracy']
loss_fe_hist = history_fe.history['loss']
val_loss_fe_hist = history_fe.history['val_loss']
Nous évaluons la performance du modèle sur l'ensemble de test après la phase de feature extraction. Nous sauvegardons également les métriques d'entraînement et de validation. L'exactitude devrait déjà être assez bonne.
# --- Fine-tuning ---
# 8. Dégeler (une partie de) la Base
base_model.trainable = True
# Choisissons de fine-tuner à partir de la couche 100 (par exemple)
fine_tune_at = 100
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
Nous préparons le fine-tuning :
base_model.trainable = True: Nous rendons l'ensemble de la base entraînable.- Geler les couches inférieures: Nous décidons de ne fine-tuner que les couches supérieures (à partir de l'indice 100 dans cet exemple). Nous gelons donc toutes les couches avant cet indice. Cela préserve les caractéristiques génériques apprises par les premières couches.
# 9. Re-compiler le Modèle (Fine-tuning)
fine_tune_learning_rate = base_learning_rate / 10 # Réduire le LR (ex: 0.0001)
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=fine_tune_learning_rate),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.summary() # Le nombre de paramètres entraînables a augmenté
Il est essentiel de re-compiler le modèle après avoir modifié l'attribut trainable des couches.
- Très faible taux d'apprentissage: Nous utilisons un taux d'apprentissage beaucoup plus petit (
fine_tune_learning_rate, ici 10 fois plus petit que le précédent) pour le fine-tuning. Cela permet de faire de petits ajustements aux poids pré-entraînés sans les perturber de manière significative. - Le résumé du modèle montre maintenant beaucoup plus de paramètres entraînables.
# 10. Continuer l'Entraînement (Fine-tuning)
EPOCHS_FT = 10 # Nombre d'époques pour le fine-tuning
total_epochs = EPOCHS_FE + EPOCHS_FT
early_stop_ft = tf.keras.callbacks.EarlyStopping(
monitor='val_accuracy', patience=5, mode='max', restore_best_weights=True)
history_ft = model.fit(train_dataset,
epochs=total_epochs,
initial_epoch=history_fe.epoch[-1] + 1, # Reprendre l'entraînement
validation_data=validation_dataset,
callbacks=[early_stop_ft])
Nous continuons l'entraînement pour EPOCHS_FT époques supplémentaires.
initial_epoch=history_fe.epoch[-1] + 1: Permet à Keras de continuer le comptage des époques et de tracer correctement les courbes d'apprentissage.- Pendant cette phase, les poids du classifieur et les poids dégelés de la base sont mis à jour.
# 11. Évaluer et Analyser (Fine-tuning)
loss_ft, accuracy_ft = model.evaluate(test_dataset)
print(f"Exactitude après Fine-tuning : {accuracy_ft * 100:.2f}%")
Nous évaluons le modèle final sur l'ensemble de test pour voir si le fine-tuning a amélioré les performances par rapport à la feature extraction seule.
# 12. Visualisation Combinée
acc = acc_fe + history_ft.history['accuracy']
val_acc = val_acc_fe + history_ft.history['val_accuracy']
loss = loss_fe_hist + history_ft.history['loss']
val_loss = val_loss_fe_hist + history_ft.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.plot([EPOCHS_FE-1, EPOCHS_FE-1], plt.ylim(), label='Début Fine-Tuning') # Ligne verticale
plt.ylim([min(plt.ylim()), 1]) # Ajuster les limites si nécessaire
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.plot([EPOCHS_FE-1, EPOCHS_FE-1], plt.ylim(), label='Début Fine-Tuning') # Ligne verticale
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

Nous combinons les historiques des deux phases (feature extraction et fine-tuning) pour visualiser l'évolution complète. La ligne verticale marque le début du fine-tuning. On peut ainsi voir si le fine-tuning a permis d'améliorer l'exactitude ou de réduire la perte.
# 13. Prédictions
# Prendre un batch du dataset de test
image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch)
predicted_indices = np.argmax(predictions, axis=1)
plt.figure(figsize=(10, 10))
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(image_batch[i].astype("uint8"))
true_label = class_names[label_batch[i]]
predicted_label = class_names[predicted_indices[i]]
plt.title(f"P: {predicted_label}\nR: {true_label}")
plt.axis("off")
plt.show()
Enfin, nous visualisons quelques prédictions du modèle final sur des images de l'ensemble de test, en affichant la classe prédite et la vraie classe.
Analyse Finale : Le transfer learning, et plus spécifiquement le fine-tuning, ont-ils apporté une amélioration significative ? Quels facteurs pourraient expliquer les résultats (taille du dataset, similarité avec ImageNet, choix des hyperparamètres) ?