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
Protocole de réentraînement
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.
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 ?
| 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 :
| 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.
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 :
| 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 :
- 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.
- 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.
- 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 à :
- transformer chaque retour métier en règle, et l’appliquer à tout le jeu d’entraînement via le moteur de règles ;
- é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.
- 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.
- Rédiger la règle avec un LLM, qui a accès aux notes explicatives, sans le laisser décider.
- 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.
- 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\)
| 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"]