Module 4 — Entraînement conscient de la quantification
Le module 3 a laissé le fil rouge à un compromis presque acceptable : 14 Mo → 3,7 Mo, exactitude 96,3 % → 95,7 %. Sur trente-huit classes de maladies, ces 0,6 point représentent environ une classe rare mal diagnostiquée par tranche de mille inférences. Pour un usage grand public, c'est tolérable ; pour un usage professionnel où un mauvais diagnostic d'anthracnose sur du manioc coûte une récolte, ce n'est plus le cas.
L'entraînement conscient de la quantification (QAT, pour Quantization
Aware Training) rattrape ce type de perte. Il coûte une nouvelle phase
d'entraînement, mais préserve les 3,7 Mo et récupère la quasi-totalité de
l'exactitude float32.
Le principe : simuler la perte avant qu'elle arrive
Un réseau entraîné en float32 puis converti en int8 a été optimisé pour
un régime numérique différent de celui dans lequel il finira par tourner. Les
poids trouvés sont optimaux pour des multiplications à virgule flottante, pas
pour leurs approximations quantifiées.
Le QAT insère, dans le graphe d'entraînement, des nœuds de fausse
quantification qui arrondissent chaque activation et chaque poids comme le
fera l'inférence int8. Le calcul reste en float32 — les gradients
peuvent toujours circuler — mais la valeur numérique subit exactement la
même perte d'information qu'à la conversion. Le réseau apprend alors à
compenser cet arrondi pendant l'entraînement, en déplaçant ses poids vers
des valeurs plus tolérantes à la quantification.
C'est une simulation, pas un vrai calcul entier : à chaque passage arrière, le gradient est calculé comme si le nœud d'arrondi était l'identité (astuce appelée estimateur passant tout droit, straight-through estimator). Sans cette astuce, la dérivée d'un arrondi serait nulle presque partout et le réseau n'apprendrait plus rien.
Le code : trois lignes ajoutées à un entraînement Keras
Le paquet tensorflow_model_optimization (TFMOT) fournit la fonction qui
enveloppe le modèle. À partir du MobileNetV2 déjà affiné :
import tensorflow_model_optimization as tfmot
modele = tf.keras.models.load_model("plantvillage_mobilenetv2.keras")
# Enveloppe chaque couche compatible avec des noeuds de fausse quantification
modele_qat = tfmot.quantization.keras.quantize_model(modele)
modele_qat.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
modele_qat.fit(donnees_entrainement, epochs=5, validation_data=donnees_validation)
Deux détails importants. Le taux d'apprentissage est réduit d'un ordre
de grandeur (1e-5 au lieu du 1e-4 de l'affinage initial), parce qu'on
polit un modèle déjà proche de l'optimum. Le nombre d'époques est court
— trois à cinq suffit — parce que la convergence sur la fausse quantification
est rapide dès lors que l'exactitude float32 est déjà atteinte.
Après l'entraînement, le modèle QAT se convertit en int8 complet comme au
module 3, sans jeu représentatif cette fois : les statistiques de
quantification ont été apprises pendant l'entraînement.
convertisseur = tf.lite.TFLiteConverter.from_keras_model(modele_qat)
convertisseur.optimizations = [tf.lite.Optimize.DEFAULT]
convertisseur.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
convertisseur.inference_input_type = tf.uint8
convertisseur.inference_output_type = tf.uint8
tflite = convertisseur.convert()
Effet mesuré sur le fil rouge : fichier à 3,7 Mo (identique à la
quantification post-training), exactitude à 96,2 % (soit 0,1 point
perdu par rapport au float32), latence identique. Le QAT récupère
essentiellement l'exactitude perdue en gardant tous les autres bénéfices.
Le coût réel : temps d'entraînement, pas complexité
L'ajout de la fausse quantification alourdit chaque passe d'entraînement de
20 à 40 %. Une époque qui prenait 10 minutes en float32 en prend 12 à
14 en QAT, sur le même matériel. Sur cinq époques, c'est une dizaine de
minutes supplémentaires, sans commune mesure avec le coût de l'affinage
initial.
Le vrai coût est plutôt organisationnel : le QAT exige que le pipeline d'entraînement soit reproductible, que les données d'entraînement soient disponibles, et qu'une machine avec accélérateur soit encore accessible. Dans certains contextes de conformité, les données d'entraînement ont été purgées après l'entraînement initial ; le QAT devient alors impraticable, et il faut revenir à la quantification post-training du module 3.
Quand faut-il vraiment le sortir ?
Le QAT n'est pas une bonne pratique par défaut. Il se justifie dans quatre cas de figure :
- La perte à la conversion
int8dépasse un seuil métier — typiquement 1 point d'exactitude ou 3 points de rappel sur une classe critique. - Le modèle est petit (moins de 5 Mo en
float32). Les petits modèles ont peu de redondance dans leurs poids et pardonnent moins la quantification. - La distribution des activations est fortement asymétrique — une couche
dont les activations vont de -20 à 200 sature en
int8symétrique. Le QAT apprend des poids qui ramènent ces activations dans une plage saine. - La cible matérielle est un accélérateur qui n'accepte que l'
int8(certains DSP, EdgeTPU) et qu'aucune perte n'est acceptable.
Hors ces cas, la quantification post-training du module 3 suffit et évite l'aller-retour d'entraînement.
Les couches BatchNormalization sont fusionnées avec la convolution qui les
précède pendant l'inférence. Un modèle qui subit le QAT sans ce repli
enveloppe chaque BatchNormalization de fausse quantification, ce qui n'a
aucun sens (elles disparaissent à la conversion). TFMOT gère ce cas
automatiquement pour les architectures standard ; pour une architecture
personnalisée, le repli doit être fait à la main avant l'enveloppement, sous
peine d'un modèle qui plante à la conversion.
Vérifier que la simulation est fidèle
Un piège classique du QAT est de croire au chiffre affiché par
modele_qat.evaluate() sur le jeu de validation. Cette évaluation utilise
la simulation float32 avec fausse quantification, pas le vrai int8 de
l'interprète. L'écart est habituellement faible, mais il existe.
Comme pour le module 3, la seule mesure qui compte est celle du .tflite
final sur l'interprète, avec le jeu de test complet :
interprete = tf.lite.Interpreter(model_path="modeles/plantvillage_qat.tflite")
interprete.allocate_tensors()
entree = interprete.get_input_details()[0]
sortie = interprete.get_output_details()[0]
correct = 0
for image, etiquette in jeu_de_test:
interprete.set_tensor(entree["index"], image.astype("uint8")[None, ...])
interprete.invoke()
predite = interprete.get_tensor(sortie["index"])[0].argmax()
if predite == etiquette:
correct += 1
print("exactitude sur .tflite QAT :", correct / len(jeu_de_test))
Un écart de plus de 0,3 point entre l'evaluate du modèle QAT et cette
mesure signale qu'une couche n'a pas été correctement enveloppée, ou qu'un
opérateur du graphe échappe à la simulation. Dans ce cas, il faut inspecter
le modèle avec modele_qat.summary() et repérer les couches qui n'ont pas
été transformées en QuantizeWrapperV2.
Le tableau de compromis après le module 4
| Variante | Taille | Latence Nokia G21 | Exactitude |
|---|---|---|---|
float32 (module 2) | 14 Mo | 240 ms | 96,3 % |
| Plage dynamique | 3,6 Mo | 175 ms | 96,1 % |
int8 post-training | 3,7 Mo | 145 ms | 95,7 % |
int8 QAT | 3,7 Mo | 145 ms | 96,2 % |
float16 | 7,1 Mo | 240 ms | 96,3 % |
La ligne QAT est la nouvelle référence du fil rouge : elle égale
pratiquement le float32 en exactitude, tout en pesant quatre fois moins
et en tournant 65 % plus vite. Le prochain module y ajoutera l'élagage pour
grignoter encore le fichier.
En résumé
- Le QAT simule la perte de quantification pendant l'entraînement avec
des nœuds de fausse quantification, en gardant les gradients
float32grâce à l'estimateur passant tout droit. - L'implémentation tient en trois lignes (
quantize_model, recompilation, quelques époques à taux réduit) et coûte 20 à 40 % de temps d'entraînement en plus. - Il se déclenche quand la perte post-training dépasse un seuil métier,
sur des modèles petits, avec des activations asymétriques, ou pour cibler
un accélérateur strictement
int8. - Toujours réévaluer sur le
.tflitefinal, pas sur le modèle QAT en mémoire : l'écart est faible mais réel.
Module suivant : l'élagage, seconde technique de réduction de taille, qui combine avec la quantification pour descendre sous les 2 Mo.