Boucle d’entraînement custom et fine-tuning d’un modèle
Dans cette section, nous allons :
- Créer une boucle d’entraînement sur mesure.
- Apprendre à ajuster un modèle existant avec un petit jeu de données (5 exemples).
- Contrôler les gradients et les mises à jour des poids pour améliorer les performances malgré la taille réduite des données.
1. Pourquoi utiliser une boucle d’entraînement personnalisée ?
Une boucle d’entraînement personnalisée permet de mieux contrôler le processus d’entraînement d’un modèle. Cela peut être utile quand :
- Tu as peu de données et que tu souhaites plus de flexibilité.
- Tu veux ajuster finement l’entraînement (choisir manuellement ce que tu fais à chaque itération).
- Les approches préconstruites (comme
model.fit
) ne suffisent pas ou sont trop rigides pour ton scénario.
Dans cet exemple, nous allons implémenter une boucle personnalisée pour affiner un modèle avec seulement 5 exemples d’entraînement.
2. Étape 1 : Préparer les données et le modèle
Nous allons utiliser un petit jeu de données fictif avec seulement 5 exemples d’entraînement. Supposons que nous utilisons ResNet-50 pré-entraîné sur ImageNet comme modèle de base, que nous allons affiner.
Préparer les données
Voici comment on pourrait créer un mini-jeu de données avec 5 exemples d’entraînement :
import numpy as np
# Créer 5 exemples de données (images 224x224 avec 3 canaux)x_train = np.random.rand(5, 224, 224, 3).astype(np.float32)
# Créer des labels fictifs (par exemple, pour 10 classes)y_train = np.random.randint(0, 10, size=(5,))
# Convertir les labels en format one-hoty_train = tf.keras.utils.to_categorical(y_train, num_classes=10)
Charger un modèle pré-entraîné
Nous allons utiliser ResNet-50 pré-entraîné, mais seulement pour extraire des caractéristiques. Nous ajouterons ensuite une petite tête de classification.
import tensorflow as tffrom tensorflow.keras.applications import ResNet50from tensorflow.keras import layers, models
# Charger ResNet-50 pré-entraîné sur ImageNet sans la dernière couche (top layer)base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
# Geler les couches du modèle de base pour ne pas les entraînerbase_model.trainable = False
# Ajouter une nouvelle tête de classificationmodel = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dense(256, activation='relu'), layers.Dropout(0.5), layers.Dense(10, activation='softmax') # 10 classes])
# Afficher la structure du modèlemodel.summary()
3. Étape 2 : Configurer la boucle d’entraînement personnalisée
Au lieu d’utiliser model.fit()
, nous allons définir manuellement une boucle d’entraînement. Cela nous donne un contrôle total sur le processus d’optimisation, les calculs de pertes, et la gestion des gradients.
Définir la fonction de perte et l’optimiseur
# Choisir une fonction de perte pour la classification multi-classesloss_fn = tf.keras.losses.CategoricalCrossentropy()
# Choisir un optimiseur pour ajuster les poidsoptimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
Dérouler une boucle d’entraînement personnalisée
Nous allons créer une boucle pour itérer sur nos 5 exemples, calculer les pertes et appliquer les gradients pour ajuster les poids du modèle.
Étape 3 : Mise en œuvre de la boucle d’entraînement
# Nombre d'epochsepochs = 10
# Itérer sur les epochsfor epoch in range(epochs): print(f'Start of epoch {epoch+1}')
# Initialiser une variable pour suivre la perte total_loss = 0.0
# Itérer sur chaque exemple d'entraînement (5 exemples dans ce cas) for i in range(len(x_train)): x = x_train[i:i+1] # Extraire un exemple d'entraînement y = y_train[i:i+1] # Extraire le label correspondant
with tf.GradientTape() as tape: # Faire une prédiction sur cet exemple predictions = model(x, training=True)
# Calculer la perte pour cet exemple loss = loss_fn(y, predictions)
# Calculer les gradients par rapport aux poids du modèle gradients = tape.gradient(loss, model.trainable_weights)
# Appliquer les gradients pour mettre à jour les poids optimizer.apply_gradients(zip(gradients, model.trainable_weights))
# Accumuler la perte pour l'afficher à la fin de l'epoch total_loss += loss.numpy()
# Afficher la perte moyenne de l'epoch print(f'Loss at epoch {epoch+1}: {total_loss / len(x_train)}')
Explication de la boucle :
- GradientTape : C’est un contexte dans lequel TensorFlow “enregistre” les opérations effectuées afin de calculer les gradients plus tard. Ici, nous utilisons
GradientTape
pour suivre le calcul de la perte. optimizer.apply_gradients
: Une fois les gradients calculés, nous les appliquons pour ajuster les poids du modèle en utilisant l’optimiseur choisi.- Itération manuelle : Contrairement à
model.fit()
, nous contrôlons ici chaque aspect de l’entraînement, en traitant un exemple à la fois et en calculant explicitement les gradients.
4. Étape 4 : Ajustements spécifiques pour peu de données
Avec seulement 5 exemples d’entraînement, le modèle risque de surapprendre (overfitting). Voici quelques techniques pour minimiser ce problème :
Régularisation et dropout :
- Dropout : Nous avons ajouté une couche de dropout pour ignorer aléatoirement certains neurones pendant l’entraînement. Cela aide à éviter le surapprentissage en simulant un ensemble de modèles différents.
- Poids pré-entraînés : En utilisant des poids pré-entraînés de ResNet-50, nous bénéficions des connaissances acquises sur de grands ensembles de données comme ImageNet, ce qui est crucial avec des petits jeux de données.
Ajuster le taux d’apprentissage :
Avec seulement quelques exemples, il est souvent utile d’utiliser un taux d’apprentissage très faible pour éviter que les poids ne changent trop drastiquement.
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-5) # Réduire le taux d'apprentissage
5. Étape 5 : Évaluation sur les 5 exemples
Après chaque epoch, nous pouvons évaluer le modèle sur nos 5 exemples pour vérifier les performances et éviter le surajustement.
Évaluation sur les exemples d’entraînement :
# Évaluer les prédictions après l'entraînementfor i in range(len(x_train)): x = x_train[i:i+1] y_true = y_train[i]
# Faire une prédiction predictions = model(x, training=False) predicted_class = tf.argmax(predictions, axis=-1) true_class = tf.argmax(y_true, axis=-1)
print(f'Exemple {i+1}: True class: {true_class.numpy()}, Predicted class: {predicted_class.numpy()}')
6. Conclusion
En résumé, voici les étapes pour mettre en œuvre une boucle d’entraînement personnalisée afin d’affiner un modèle avec seulement 5 exemples d’entraînement :
- Préparer un petit jeu de données (5 exemples dans ce cas).
- Charger un modèle pré-entraîné (comme ResNet-50) et ajouter des couches personnalisées.
- Configurer une boucle d’entraînement personnalisée qui inclut le calcul des gradients et l’application de mises à jour des poids.
- Utiliser des techniques pour éviter le surapprentissage (dropout, régularisation, faible taux d’apprentissage).
- Évaluer les performances sur les quelques exemples pour s’assurer que le modèle apprend correctement.
Malgré la petite taille du jeu de données, cette approche permet de tirer parti des poids pré-entraînés tout en ajustant finement le modèle sur une nouvelle tâche.