Skip to content

Transfer Learning

1. Principes

Le transfer learning consiste à réutiliser un modèle déjà entraîné sur un large dataset (ImageNet, ~14 M d'images, 1 000 classes) et à l'adapter à une nouvelle tâche avec peu de données.

Pourquoi ça marche ?

Les premières couches d'un réseau de neurones apprennent des features génériques (contours, textures, motifs) qui sont utiles pour presque toutes les tâches visuelles. Seules les dernières couches sont spécialisées pour la tâche d'origine.

Deux approches

Approche Principe Quand l'utiliser
Feature Extraction Backbone gelé, on entraîne uniquement le classifier head Petit dataset, premier essai
Fine-Tuning On dégèle tout ou partie du backbone et on réentraîne Quand le feature extraction plafonne

Anti-spoofing

Les textures de moiré, les reflets d'écran, les artefacts de compression JPEG sont des features de bas/moyen niveau que le backbone pré-entraîné sait déjà détecter. Il suffit d'entraîner un classifier head pour dire "real" ou "fake".

Workflow type en Keras

# 1. Charger le backbone pré-entraîné SANS la tête de classification
base_model = keras.applications.MobileNetV3Large(
    weights="imagenet",
    input_shape=(224, 224, 3),
    include_top=False,
)

# 2. Geler le backbone
base_model.trainable = False

# 3. Ajouter un classifier head
inputs = keras.Input(shape=(224, 224, 3))
x = base_model(inputs, training=False)  # training=False → BatchNorm en mode inférence
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dropout(0.2)(x)
outputs = keras.layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs, outputs)

BatchNormalization

Toujours passer training=False au backbone, même après le dégel pour le fine-tuning. Sinon les statistiques BatchNorm seront mises à jour et détruiront les features apprises.


2. Architectures de base

MobileNetV3

MobileNetV3 existe en deux variantes : Large (meilleure accuracy) et Small (ultra-léger).

Caractéristique Détail
Taille ~22 MB (ImageNet)
Paramètres ~5.4 M
Innovation Depthwise separable conv + squeeze-and-excitation + hard-swish + NAS
Vitesse Très rapide, conçu pour le mobile
Accuracy (ImageNet) ~75.2% top-1
Caractéristique Détail
Taille ~10 MB (ImageNet)
Paramètres ~2.5 M
Innovation Même architecture que Large, optimisée pour la latence minimale
Vitesse Extrêmement rapide, idéal pour les appareils très contraints
Accuracy (ImageNet) ~67.7% top-1

EfficientNet (B0 → B7)

Caractéristique Détail
Taille (B0) ~29 MB
Paramètres (B0) ~5.3 M
Innovation Compound scaling (résolution × profondeur × largeur)
Vitesse Plus lent que MobileNet, mais meilleure accuracy
Accuracy (B0, ImageNet) ~77.1% top-1

3. Dataset — Préparation & Bonnes Pratiques

Taille recommandée

Situation Taille minimale Taille idéale
Transfer learning (binaire) 500–1 000 par classe 2 000–5 000 par classe
Transfer learning (multi-classe) 200–500 par classe 1 000+ par classe
Entraînement from scratch 10 000+ par classe 50 000+ par classe

Règle empirique

Pour du transfer learning binaire (real/fake), 1 000 images par classe est un bon minimum. En dessous, la data augmentation devient indispensable.

Répartition des données

Split Ratio recommandé Rôle
Train 70–80% Entraînement du modèle
Validation 10–15% Suivi pendant l'entraînement, tuning des hyperparamètres
Test 10–15% Évaluation finale, jamais vu pendant l'entraînement

Erreur fréquente

Ne jamais tuner les hyperparamètres sur le test set. Si vous le faites, vous n'avez plus de mesure fiable de la performance réelle.

Équilibrage des classes

Un dataset déséquilibré (ex : 90% real, 10% fake) biaise le modèle vers la classe majoritaire.

Techniques de rééquilibrage :

Réduire la classe majoritaire au niveau de la classe minoritaire.

  • ✅ Simple et rapide
  • ❌ Perte de données potentiellement utiles

Dupliquer ou augmenter la classe minoritaire.

  • ✅ Pas de perte de données
  • ❌ Risque de sur-apprentissage sur les doublons

Pondérer la loss pour pénaliser davantage les erreurs sur la classe minoritaire.

from sklearn.utils.class_weight import compute_class_weight
import numpy as np

weights = compute_class_weight(
    "balanced",
    classes=np.array([0, 1]),
    y=train_labels
)
class_weight = {0: weights[0], 1: weights[1]}

model.fit(train_ds, class_weight=class_weight)
  • ✅ Pas besoin de modifier le dataset
  • ❌ Peut être insuffisant si le déséquilibre est extrême (> 10:1)

Anti-spoofing : pièges courants

  • Biais de capture : si toutes les photos "fake" viennent du même écran, le modèle apprend à reconnaître l'écran, pas le fake. → Varier les sources.
  • Biais temporel : si les photos real sont récentes et les fake anciennes, le modèle apprend la qualité JPEG. → Mixer les dates.
  • Ratio déséquilibré : en production, les fakes sont rares (~5%). Entraîner à 50/50, puis évaluer avec le ratio réel.

4. Data Augmentation

La data augmentation crée des variations artificielles des images d'entraînement pour améliorer la généralisation.

Augmentations recommandées pour l'anti-spoofing

Augmentation Utilité anti-spoofing Range recommandé
Horizontal Flip Invariance gauche-droite des sujets 50% de probabilité
Brightness Jitter Simule différentes conditions de luminosité ± 0.2
Contrast Jitter Simule les variations d'écran/caméra ± 0.2
Saturation Jitter Simule les variations de balance des blancs ± 0.2

Implémentation Keras

augmentation_layers = keras.Sequential([
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomBrightness(0.2),
    keras.layers.RandomContrast(0.2),
])

Quand activer l'augmentation ?

  • Dataset < 2 000 images par classe → Indispensable
  • Dataset 2 000 – 10 000 → Recommandé
  • Dataset > 10 000 → Utile mais moins critique

5. Pipeline d'entraînement en 2 phases

Vue d'ensemble

graph LR
    A[Backbone pré-entraîné<br/>ImageNet] --> B[Phase 1<br/>Feature Extraction]
    B --> C{Accuracy<br/>suffisante ?}
    C -->|Oui| D[✅ Export]
    C -->|Non| E[Phase 2<br/>Fine-Tuning]
    E --> D

Phase 1 — Transfer Learning (backbone gelé)

Paramètre Valeur recommandée Notes
Backbone Gelé (trainable = False) Préserve les features ImageNet
Learning Rate 1e-3 (Adam) Standard pour un classifier head
Epochs 10–20 Suffisant pour converger
Batch Size 32 Bon compromis mémoire/stabilité
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-3),
    loss="binary_crossentropy",
    metrics=["accuracy"],
)
model.fit(train_ds, validation_data=val_ds, epochs=10)

Phase 2 — Fine-Tuning (backbone dégelé)

Paramètre Valeur recommandée Notes
Backbone Dégelé (trainable = True) Toutes les couches entraînables
Learning Rate 1e-5 10–100x plus faible que Phase 1
Epochs 5–10 Surveiller l'overfitting de près
BatchNorm training=False Critique : ne pas mettre à jour les stats
# Dégeler le backbone
base_model.trainable = True

# Recompiler avec un LR très faible
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-5),
    loss="binary_crossentropy",
    metrics=["accuracy"],
)
model.fit(train_ds, validation_data=val_ds, epochs=10)

Piège classique

Si vous dégelez le backbone sans baisser le learning rate, les grands gradients vont détruire les poids pré-entraînés. Toujours utiliser un LR ≤ 1e-5.

Diagnostic des courbes d'apprentissage

  • Train accuracy et val accuracy montent ensemble
  • Train loss et val loss descendent ensemble
  • Les courbes se stabilisent (plateau)
  • Train accuracy monte, val accuracy stagne ou descend
  • Train loss descend, val loss remonte
  • Remèdes : réduire les epochs, augmenter le dataset, ajouter du dropout, activer la data augmentation
  • Train accuracy et val accuracy restent basses (< 70%)
  • Remèdes : augmenter les epochs, passer à un modèle plus puissant (EfficientNet), vérifier la qualité du dataset

Anti-spoofing : config type

Avec un dataset de 3 000 images (1 500 real + 1 500 fake) :

  • Phase 1 : MobileNetV3-Large, 10 epochs, LR=1e-3 → ~93% val accuracy
  • Phase 2 : Fine-tuning 5 epochs, LR=1e-5 → ~95% val accuracy
  • Augmentation : flip horizontal + brightness ± 0.2

6. Quantification Post-Training (PTQ)

Pourquoi quantifier ?

Métrique Float32 INT8 (PTQ)
Taille du modèle 1x ~4x plus petit
Vitesse d'inférence 1x ~2–4x plus rapide
Consommation 1x Significativement réduite
Perte d'accuracy Généralement < 1%

Méthodes de quantification

Technique Réduction taille Calibration requise Matériel cible
Dynamic Range ~4x Non CPU
Full Integer (INT8) ~4x Oui (~100–500 samples) CPU, Edge TPU, microcontrôleurs
Float16 ~2x Non GPU

Dataset de calibration

Pour la quantification INT8, un dataset de calibration est nécessaire :

  • Taille : 100 à 500 échantillons représentatifs
  • Contenu : un sous-ensemble du train ou du val set
  • Rôle : déterminer les plages min/max des activations pour optimiser la quantification
def representative_dataset():
    for image, _ in train_ds.take(100):
        yield [image]

converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_model = converter.convert()

PTQ vs QAT — Quand choisir quoi ?

  • ✅ Aucune modification de l'entraînement
  • ✅ Rapide à appliquer
  • ✅ Suffisant pour le transfer learning (backbone robuste)
  • ❌ Peut perdre un peu de précision sur des petits modèles
  • ✅ Meilleure précision après quantification
  • ❌ Nécessite de modifier le pipeline d'entraînement
  • ❌ Plus complexe et plus long
  • ❌ Peu utile en transfer learning (on entraîne < 1% des poids)

Pourquoi PTQ suffit en transfer learning

En transfer learning, le backbone (99%+ des poids) provient d'ImageNet et est déjà robuste à la quantification. Seul le classifier head (~1 000 params) est entraîné. La PTQ donne d'excellents résultats dans ce cas.


7. Conversion & Déploiement TFLite

Pipeline de conversion

graph LR
    A[Modèle Keras<br/>.keras] --> B[TFLite Float32<br/>.tflite]
    A --> C[TFLite INT8<br/>_qt.tflite]
    B --> D[Déploiement<br/>maximum accuracy]
    C --> E[Déploiement<br/>mobile / edge]

Code de conversion

converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

with open("model.tflite", "wb") as f:
    f.write(tflite_model)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_model = converter.convert()

with open("model_qt.tflite", "wb") as f:
    f.write(tflite_model)

Vérification post-conversion

Toujours vérifier l'accuracy après conversion

Il est possible que la quantification dégrade l'accuracy de manière inacceptable. Comparer systématiquement :

  • Accuracy Keras vs TFLite Float32 → devrait être identique
  • Accuracy TFLite Float32 vs TFLite INT8 → delta acceptable < 1–2%

Si le delta INT8 est trop grand, envisager la quantification Float16 comme compromis.


8. Checklist & Anti-patterns

✅ Checklist avant entraînement

  • Dataset équilibré (ou class weights configurés)
  • Split train / val / test effectué (pas de fuite de données)
  • Images redimensionnées à la taille du backbone (224×224)
  • Pixels normalisés dans la plage attendue par le backbone
  • Data augmentation activée si dataset < 5 000 images/classe
  • Backbone gelé pour la Phase 1

✅ Checklist avant déploiement

  • Courbes de learning vérifiées (pas d'overfitting)
  • Modèle évalué sur le test set (jamais vu pendant le training)
  • Conversion TFLite testée (Float32 + INT8)
  • Accuracy post-quantification vérifiée (delta < 2%)
  • Taille du modèle acceptable pour la cible (mobile, edge…)

❌ Anti-patterns courants

Anti-pattern Conséquence Solution
LR trop haute en fine-tuning Destruction des poids pré-entraînés Utiliser 1e-5 max
Pas de validation set Impossible de détecter l'overfitting Toujours réserver 10–15%
Dataset déséquilibré sans compensation Modèle biaisé vers la classe majoritaire Class weights ou rééchantillonnage
BatchNorm mis à jour en fine-tuning Ruine les features apprises training=False au backbone
Trop d'epochs en fine-tuning Overfitting rapide 5–10 epochs max, surveiller val_loss
Pas de test set séparé Surestimation des performances Toujours 3 splits séparés
Data augmentation trop agressive Images irréalistes, confusion du modèle Rester dans des ranges modérés (±0.2)

Références