Protocole de réentraînement

Comment le flux de production de chaque trimestre devient un meilleur modèle, sans se raconter d’histoires : golden set, curation des données, règles métier, quality gates et qualification.
Auteur
Affiliation

SSP Lab, Insee

Réentraîner un modèle, c’est facile. Le réentraîner en prouvant qu’il est au moins aussi bon que celui en production, avec des annotations produites en partie par ce même modèle, c’est beaucoup plus dur. Ce chapitre décrit le protocole que nous mettons en place pour y arriver : c’est le cœur méthodologique du pipeline.

Le protocole suit un cycle trimestriel. Pendant le trimestre \(n\), le modèle \(f_n\) est en production avec un seuil \(\tau_n\), et le cycle produit \(f_{n+1}\) et \(\tau_{n+1}\). Toutes les notations sont rassemblées dans le glossaire.

NoteUne cible, en partie en place

Ce chapitre décrit le protocole que nous mettons en place, pas seulement ce qui tourne aujourd’hui. L’état des lieux ci-dessous indique, pour chaque brique, ce qui est en place, ce qui l’est en partie et ce qui reste à faire.

1 Où en sommes-nous ?

Table 1: État du protocole.
Composant État Aujourd’hui
Moteur de règles avec logs d’audit, appliqué avant chaque entraînement En place Tourne dans le workflow de preprocessing (Données)
Entraînement reproductible, jeux de données loggés avec chaque run En place Fichiers de configuration, environnements figés, jeux de données loggés dans MLflow (Modèle)
Métriques loggées à chaque run En partie Accuracy à chaque niveau, ECE, score de Brier, histogramme des confiances et courbe accuracy–taux d’automatisation. Reliability diagram, AURC, macro-F1 et métriques par trimestre restent à faire (Évaluation)
Pondération des observations En partie Sert à pondérer les lignes créées par les règles métier. Les poids par source et par ancienneté restent à faire
Golden set En partie Des campagnes d’annotation par les experts ont eu lieu (NAF rév. 2, NAF 2025), mais pas encore d’échantillon aléatoire continu du flux, ni de split figé en \(\bar{G}^{\text{val}}\) et \(\bar{G}^{\text{test}}\)
Curation des codifications automatiques par désaccord avec des moteurs indépendants En partie Prototypée avec GRAAL sur des échantillons de test ; pas encore appliquée au flux codé automatiquement
Monitoring sans annotation En partie Un dashboard existe ; la collecte des logs de production et les tests de drift restent à faire (Monitoring)
Cycle trimestriel Cible Les modèles sont réentraînés à la demande
Contrôles des cas connus avant entraînement, extraction de règles à partir des reprises manuelles Cible Les règles sont écrites à la main à partir des retours des experts
Quality gates bloquantes avant mise en production (cas connus, non-infériorité, couverture à \(p^*\)) Cible Tests appariés et intervalles par bootstrap existent dans le module d’évaluation de GRAAL, pas encore pour le modèle de production
Seuil et calibration dans l’artefact du modèle, champion et challenger dans le model registry Cible Les modèles sont référencés par leur numéro de version dans les manifestes de déploiement
Qualification automatisée (latence, cohérence online/offline), shadow mode Cible Voir Défis organisationnels et d’infrastructure
Têtes multiniveaux, label attention Cible Disponibles dans torchTextClassifiers, pas utilisées par le modèle de production

2 Ce que fournit le flux de production

Le flux de déclarations \(F_n\) reçu pendant le trimestre \(n\) se découpe naturellement en plusieurs ensembles, chacun avec son intérêt et son propre biais de sélection :

Table 2: Les quatre sources d’annotations d’un cycle de production.
Ensemble Définition Qui l’annote Biais Usage
Golden set \(G_n\) Un échantillon aléatoire de 1 à 3 % du flux, annoté à l’aveugle (sans voir la prédiction du modèle) Experts métier Aucun Évaluation uniquement, jamais l’entraînement
Reprises manuelles \(M_n\) Cas sous le seuil (20 à 25 % du flux environ) Gestionnaires Oui : les cas difficiles pour \(f_n\) Entraînement
Codifications automatiques \(A_n\) Cas au-dessus du seuil Le modèle \(f_n\) lui-même Oui : les cas faciles pour \(f_n\) Entraînement, après curation
Cas connus \(K_n\) Règles métier validées (par exemple « LMNP » → location meublée) Experts métier Oui : erreurs signalées Doivent passer à 100 % ou presque

\(G_n\), \(M_n\) et \(A_n\) forment une partition du flux ; \(K_n\), lui, vient de l’extérieur. Les cas connus ont deux origines :

  • les retours métier, surtout des erreurs repérées après coup parmi les cas codés automatiquement ;
  • les erreurs récurrentes extraites des reprises manuelles (voir plus bas).

On note \(\bar{X}_n = \bigcup_{k \le n} X_k\) l’ensemble cumulé au fil des trimestres, éventuellement sur une fenêtre glissante.

flowchart LR
  F["Flux F_n"] -->|"aléatoire 1 à 3 %"| G["G_n : annotation experte à l'aveugle"]
  F --> R{"IC ≥ τ_n ?"}
  R -->|"non (20 à 25 %)"| M["M_n : reprise manuelle"]
  R -->|"oui"| A["A_n : codification automatique"]
  A -.->|"erreurs signalées"| K["K_n : règles validées"]
  M -.->|"erreurs récurrentes, après revue"| K
Figure 1: Décomposition du flux de production.
ImportantLe golden set n’est pas facultatif

Sans échantillon aléatoire du flux annoté à l’aveugle, pas d’estimation sans biais de l’accuracy de la codification automatique. Impossible alors de fixer le seuil, de comparer deux modèles ou de mesurer le drift à partir d’annotations. Les reprises manuelles ne peuvent pas le remplacer, puisqu’elles sont sélectionnées sur les échecs du modèle. Annoter le flux en continu, même en petite quantité, est le prérequis organisationnel numéro un du protocole.

Le golden set est coupé une fois pour toutes en deux moitiés, à partir d’un hash de l’identifiant de chaque déclaration. Le split reste ainsi stable à mesure que l’ensemble grossit :

  • \(\bar{G}^{\text{val}}\) sert à tous les choix : seuil, calibration, architecture et hyperparamètres, sélection des moteurs auxiliaires et des règles candidates ;
  • \(\bar{G}^{\text{test}}\) ne sert qu’au verdict final.

3 Construire le prochain jeu d’entraînement

3.1 On peut estimer l’accuracy de la codification automatique

On ne sait pas quels codes automatiques sont faux, mais on peut estimer combien le sont. Comme le golden set est un échantillon aléatoire du flux, sa partie au-dessus du seuil, \(G_n^A = \{x \in G_n : \text{IC}_n(x) \ge \tau_n\}\), est un échantillon aléatoire de \(A_n\). L’accuracy de \(f_n\) sur \(G_n^A\), avec un intervalle de confiance (Wilson ou bootstrap), estime donc l’accuracy de la codification automatique. En cumulant plusieurs trimestres, on a assez de données pour cette estimation.

3.2 Curation des codifications automatiques : se concentrer sur les désaccords

Réinjecter \(A_n\) dans l’entraînement avec les codes de \(f_n\), c’est faire du self-training : le modèle n’apprend rien de nouveau et renforce ses erreurs à forte confiance. Pour casser cette boucle, on compare \(f_n\) à des moteurs indépendants \(E\), dont aucun n’est un oracle :

  • le moteur de règles de codif-ape-preprocess applique des règles déterministes, qui s’abstiennent souvent, étendues prudemment aux libellés similaires ;
  • les agents LLM de GRAAL parcourent la nomenclature et lisent les notes explicatives. Ils sont lents, mais la latence compte peu dans un job batch hors ligne.

L’accuracy globale d’un moteur sur le golden set n’est pas la bonne question. Ce qui compte, c’est qui a raison quand \(E\) et \(f_n\) ne sont pas d’accord sur les codifications automatiques :

Table 3: Règle de décision pour les cas codés automatiquement.
Sur \(A_n\) \(E\) s’abstient \(E\) est d’accord avec \(f_n\) \(E\) est en désaccord avec \(f_n\)
Action Garder le code de \(f_n\) (son accuracy est connue), avec un poids réduit Garder, avec une confiance élevée Arbitrer

Les désaccords, ce sont très probablement les erreurs à forte confiance de \(f_n\), et donc les exemples les plus précieux. Trois façons de les traiter :

  1. Annotation humaine ciblée des désaccords. C’est sans doute le meilleur usage d’un budget d’annotation, dans l’esprit du query by committee.
  2. Adoption du code du moteur, si, sur \(\bar{G}^{\text{val}} \cap \{\text{IC}_n \ge \tau_n,\ E \ne f_n\}\), le moteur a raison significativement plus souvent que \(f_n\). Pas de seuil de fiabilité absolu : le critère est comparatif, et conditionnel à la zone de désaccord.
  3. Exclusion de l’entraînement des désaccords non arbitrés. C’est l’option par défaut, quand on n’a ni budget ni preuve. On perd les cas automatiques les plus difficiles, ce qui est acceptable puisque les reprises manuelles couvrent déjà la zone difficile.

Le même critère sert à choisir entre les variantes du moteur LLM (RAG, RAG agentique, agent complet, ou vote entre eux). Trois précautions :

  • mesurer sur \(\bar{G}^{\text{val}}\), jamais sur \(\bar{G}^{\text{test}}\), puisque ce choix modifie les données d’entraînement ;
  • comparer à couverture égale, car un moteur qui s’abstient souvent gagne mécaniquement sur la zone de désaccord ;
  • ne changer de variante que si l’écart sort des intervalles de confiance.

Pour limiter le coût des moteurs LLM, on peut ne les faire tourner que sur une bande de confiance intermédiaire, \(\tau_n \le \text{IC} < \tau'\) : les prédictions très confiantes sont rarement fausses.

3.3 Cas connus : propager plutôt que surpondérer

Se contenter de surpondérer les cas connus marche rarement : le jeu d’entraînement contient en général les mêmes libellés avec l’ancien code, et le modèle voit des codes contradictoires. Le protocole consiste donc à :

  1. transformer chaque retour métier en règle, et l’appliquer à tout le jeu d’entraînement via le moteur de règles ;
  2. éventuellement, pour la très courte liste de cas où l’on exige 100 % de bonnes réponses, ajouter en production une couche de règles en amont du modèle. La correction est alors garantie, traçable et immédiate, sans réentraînement. Cette couche doit rester petite : c’est l’empilement sans fin de règles qui a fait échouer Sicore.

Contrôles avant entraînement. La propagation est vérifiée automatiquement, et l’entraînement est bloqué si un contrôle échoue :

  • contradictions exactes : une jointure entre les cas connus et le nouveau jeu d’entraînement sur la clé de dédoublonnage (libellé normalisé et variables annexes) ne doit renvoyer aucune ligne avec un code différent ;
  • quasi-doublons : les plus proches voisins de chaque cas connu (TF-IDF sur n-grammes de caractères, ou embeddings du modèle) qui ont un autre code passent en revue humaine, car un libellé proche peut très bien relever d’un autre code ;
  • le nombre de conflits, par source et par trimestre, est loggé. On voit ainsi d’où viennent les erreurs, et on mesure le taux de contradictions internes du jeu d’entraînement, indicateur direct du bruit sur les labels.

Extraire des règles des corrections manuelles. Parmi les reprises manuelles, les lignes où le code humain diffère de la prédiction de \(f_n\) sont des erreurs corrigées. Une erreur isolée ne fait pas une règle, mais une erreur récurrente est une bonne candidate. Le principe : l’automatique propose, des filtres statistiques trient, un expert décide.

  1. Détecter les erreurs récurrentes sur le cumul des reprises manuelles :
    • libellés normalisés identiques, avec un support suffisant et un code dominant que \(f_n\) manque ;
    • mots discriminants, repérés par des règles d’association (support, confiance, lift), qui marchent bien sur des sigles comme « LMNP » ;
    • clusters d’erreurs sur un même couple (code prédit, code corrigé), dans l’espace des embeddings du modèle.
  2. Rédiger la règle avec un LLM, qui a accès aux notes explicatives, sans le laisser décider.
  3. Filtrer automatiquement. Appliquer chaque règle candidate au jeu d’entraînement et à \(\bar{G}^{\text{val}}\), puis mesurer combien de lignes elle touche et sa précision. Rejeter celles qui ratissent trop large ou se trompent trop.
  4. Faire valider par un expert, dans une file de revue qui présente la règle, ses exemples et ses contre-exemples.

Le dépôt des règles est versionné. Chaque règle indique son origine, sa date et qui l’a validée, pour pouvoir être auditée ou retirée.

3.4 Assemblage

Le prochain jeu d’entraînement est

\[ \mathcal{D}_{n+1} = \mathcal{D}_n \cup M_n \cup \tilde{A}_n \cup K_n, \]

où \(\tilde{A}_n\) désigne les codifications automatiques après curation. Ce jeu est ensuite :

  • Dédoublonné sur le couple (libellé normalisé, variables annexes). Les conflits sont tranchés par priorité de source (cas connus > reprises manuelles > codifications automatiques après curation > historique), puis en faveur du plus récent.
  • Taggé : chaque observation porte sa source et son trimestre. On peut ainsi pondérer selon la source (par exemple, les codes automatiques pèsent moins que les codes humains) et suivre la loss par source.
  • Pondéré selon l’ancienneté : l’historique est sous-pondéré, voire coupé, car les anciens codes sont plus bruités et reflètent un flux plus ancien.
  • Versionné : chaque jeu d’entraînement est un snapshot sur S3 avec son hash, loggé avec le run d’entraînement.

Deux règles s’ajoutent :

  • Le jeu d’entraînement n’est pas représentatif du flux, puisque les reprises manuelles surreprésentent les cas difficiles. C’est pourquoi le seuil et la calibration sont toujours fixés sur \(\bar{G}^{\text{val}}\), jamais sur un échantillon de validation tiré des données d’entraînement.
  • Le golden set n’entre jamais dans l’entraînement. Comme le flux est très répétitif, il est normal de retrouver des libellés identiques dans les deux. Les métriques sont donc aussi calculées sur les libellés jamais vus.

Un échantillon de validation aléatoire, tiré du jeu d’entraînement, sert uniquement à l’early stopping.

4 Quality gates : \(f_{n+1}\) face à \(f_n\)

Table 4: Gates qu’un modèle candidat doit passer.
Gate Données Objectif Critère
1 Cas connus \(\bar{K}_n\) et variantes reformulées \(K'_n\) absentes de l’entraînement Non-régression métier (tests unitaires) ≥ 99 % ; échecs listés un par un
2 \(\bar{G}^{\text{test}}\) Généralisation Non-infériorité par rapport à \(f_n\), avec un test apparié (McNemar ou bootstrap apparié)
3 \(\bar{G}^{\text{test}}\) Indicateur métier Taux de codification automatique à l’accuracy cible \(p^*\) ≥ celui de \(f_n\)

Sur les cas connus, un bon score peut juste vouloir dire que le modèle les a appris par cœur. Les variantes reformulées vérifient que la correction se généralise ; elles peuvent être générées par les agents Code2Text de GRAAL.

On n’évalue pas sur les reprises manuelles. Elles font partie des données d’entraînement de \(f_{n+1}\) et sont sélectionnées sur les échecs de \(f_n\) : par construction, elles désavantagent \(f_n\). Pour avoir un signal sur les cas difficiles, on utilise \(\bar{G} \cap \{\text{IC}_n < \tau_n\}\), échantillon aléatoire et sans biais de la zone difficile.

Métriques calculées sur \(\bar{G}^{\text{test}}\) :

  • accuracy à chaque niveau de la nomenclature ;
  • macro-F1 et performance sur les classes rares ;
  • calibration (ECE) ;
  • courbe risque–couverture et aire sous cette courbe (AURC) ;
  • couverture à l’accuracy cible \(p^*\), l’indicateur qui détermine la charge de reprise manuelle ;
  • tous ces indicateurs par trimestre du golden set, pour faire apparaître le drift.

Le seuil est recalibré pour chaque modèle sur \(\bar{G}^{\text{val}}\).

Trois principes guident la comparaison :

  • Comment faire mieux que \(f_n\). Les leviers sont les reprises manuelles (des annotations humaines pile là où le modèle se trompe, ce qui revient à de l’active learning), les désaccords arbitrés, des données plus propres et plus récentes. Itérer sur les architectures jusqu’à gagner sur le jeu de test reviendrait à overfitter ce jeu : ces itérations se font donc sur \(\bar{G}^{\text{val}}\).
  • La non-régression est la condition pour passer ; l’amélioration est l’objectif. Exiger une amélioration stricte à chaque trimestre n’est pas réaliste.
  • Entraîner sur les cas connus et les corrections manuelles, ce n’est pas du data leakage. Le leakage commence quand une métrique calculée sur les données d’entraînement est présentée comme une mesure de la généralisation. Tant que \(\bar{G}^{\text{test}}\) reste intact, le protocole tient.

Traçabilité. Le run MLflow de chaque candidat logge :

  • la version du jeu de données, la version du dépôt de règles et la variante du moteur LLM ;
  • les hyperparamètres et les métriques ;
  • un rapport de comparaison « \(n\) vs \(n+1\) » sous forme d’artefact HTML.

Le model registry identifie le champion et le challenger.

5 Qualification et mise en production

Même à architecture inchangée, la qualification n’est pas une formalité :

  • Qualification iso-production : latence p95 et mémoire, sur CPU, sur la plateforme de qualification.
  • Cohérence online/offline : le modèle servi doit reproduire exactement les prédictions hors ligne sur \(\bar{G}^{\text{test}}\). Ce test peu coûteux attrape les écarts de prétraitement ou de versions de librairies.
  • Le seuil et la calibration sont livrés dans l’artefact du modèle, pas dans une configuration à part.
  • Déploiement en shadow mode pendant quelques semaines : \(f_{n+1}\) traite le flux réel en parallèle de \(f_n\). On suit le taux effectif de codification automatique, l’accord avec \(f_n\) et la distribution des scores, et les désaccords partent en annotation.
  • Revenir en arrière, c’est refaire pointer la production vers \(f_n\).

6 Comparer les modèles dans le temps

Des scores obtenus sur des golden sets différents ne se comparent pas. L’historique des modèles se construit donc en réévaluant tous les anciens modèles sur le dernier \(\bar{G}^{\text{test}}\).

Tant que le golden set n’est pas alimenté en continu, les indicateurs sans annotation (taux de codification automatique, distribution de l’indice de confiance, vocabulaire nouveau, accord avec les moteurs auxiliaires) sont les seuls signaux de drift. Des seuils d’alerte sur ces indicateurs peuvent déclencher un réentraînement hors cycle.

7 Vue d’ensemble

flowchart TD
  subgraph Q["Trimestre n : la production utilise f_n, τ_n"]
    G["G_n"]
    M["M_n"]
    A["A_n"]
    K["K_n"]
  end
  A --> C["Curation : règles + moteurs LLM<br/>accord / désaccord"]
  C -->|"accords"| At["Ã_n"]
  C -->|"désaccords"| H["Annotation ciblée"] --> At
  M -.->|"erreurs récurrentes<br/>+ revue experte"| K
  K --> RL["Règles propagées à tout D"]
  D0["D_n"] & M & At & RL --> D1["D_n+1 : dédoublonnage, conflits, poids, version"]
  D1 --> T["Entraînement de f_n+1"]
  G --> GV["Ḡ val : seuil, calibration, sélection"] --> T
  G --> GT["Ḡ test"]
  T --> E{"Gates : K / K', non-infériorité,<br/>couverture à p*"}
  GT --> E
  E -->|"échec"| T
  E -->|"succès"| P["Qualification : latence, cohérence, shadow"]
  P --> PR["Production : f_n+1, τ_n+1"]
Figure 2: Un cycle de réentraînement.
Retour au sommet