Retour au blog
SécuritéPour les développeurs6 min de lecture

Entraîner notre propre modèle de détection de PII juridique : le récit d'ingénierie complet

Six itérations d'entraînement, plusieurs bugs de production découverts en cours de route, et une leçon centrale : mesurer avant d'ajouter des données. Le détail technique de notre pipeline de pseudonymisation.

Équipe KGMIngénierie IAPublié le 11 juillet 2026

Cet article détaille le travail technique derrière notre système d'anonymisation de documents juridiques. Si l'article précédent explique pourquoi c'est difficile, celui-ci explique comment nous l'avons résolu — avec les chiffres, les hypothèses rejetées, et les bugs qu'il a fallu trouver un par un.

Le point de départ : un modèle générique, deux limites connues

Notre pipeline de pseudonymisation repose sur GLiNER, une architecture de détection d'entités "zero-shot" : on lui donne une liste de catégories (personne, organisation, IBAN, numéro de sécurité sociale...) et il les détecte sans avoir besoin d'être ré-entraîné pour chaque nouvelle catégorie. Nous utilisions en production un modèle pré-entraîné généraliste (gliner_multi_pii-v1).

Deux limites connues nous poussaient à agir : le rappel sur les personnes physiques (la proportion de personnes réellement présentes dans un document que le modèle parvient à détecter) était insuffisant sur nos documents juridiques réels, et le rappel sur les organisations souffrait d'une régression après un changement antérieur.

Avant de nous lancer dans un entraînement coûteux, nous avons testé l'option la plus rapide : essayer un autre modèle pré-entraîné (fastino/gliner2-privacy-filter-PII-multi), spécialisé PII. Résultat : +2,7 points de rappel sur les personnes, mais −8,9 points de précision (score global F1 net inférieur), et une détection des organisations absente structurellement de sa taxonomie — pas un simple manque de réglage, une case vide. Ce test a validé notre décision : il fallait entraîner notre propre modèle sur notre propre taxonomie, en partant du modèle de production existant.

Construire un vrai benchmark avant de toucher au modèle

Avant tout entraînement, nous avons durci notre méthodologie d'évaluation interne : un jeu de test figé de plusieurs centaines de documents et plusieurs milliers d'entités annotées, jamais utilisé pour l'entraînement, avec des seuils de validation stricts (rappel personnes ≥ 85 %, organisations ≥ 75 %, données structurées ≥ 90 %) et un rapport détaillé par catégorie de document et par domaine juridique (droit des affaires, pénal, famille, logement, travail...).

Cette étape s'est révélée décisive : elle a permis de détecter qu'un gain apparent sur le score global cachait parfois une régression totale sur une sous-catégorie spécifique — notamment les décisions de justice réelles, où le rappel sur les organisations était resté à 0 % pendant plusieurs itérations sans que personne ne le voie dans le score agrégé.

Quatre itérations, quatre hypothèses éliminées une par une

Le premier entraînement (600 à 800 documents) a échoué aux seuils de validation : rappel personnes à 42,5 % (objectif ≥ 85 %), et une régression sur les données structurées. Les itérations suivantes ont chacune testé une hypothèse différente :

  1. Le taux d'apprentissage était-il trop conservateur ? Doubler le taux d'apprentissage de l'encodeur n'a fait bouger le rappel que de 2 points. Hypothèse écartée.
  2. Manquait-il de volume de données ? Ajouter davantage de décisions de justice réelles et découper les documents longs (au-delà de la limite technique du modèle) n'a fait gagner qu'un point de rappel. Hypothèse écartée — mais l'analyse a révélé un fait plus grave : aucune décision de justice réelle n'existait dans les données d'entraînement. Chaque document réel jamais récupéré était allé directement dans le jeu de test, jamais dans l'entraînement.
  3. La qualité des annotations elle-même était-elle en cause ? En creusant les faux négatifs, nous avons trouvé un bug dans notre méthode de reconstruction des données d'entraînement : lorsqu'un nom de personne était représenté par plusieurs fragments adjacents dans les décisions anonymisées par Judilibre, notre script générait un nom complet aléatoire par fragment au lieu d'un seul nom par personne — créant des données d'entraînement corrompues (deux noms complets collés en un seul). Corrigé, ré-annoté, ré-entraîné. Amélioration réelle mais encore insuffisante.
  4. Le vrai problème n'était pas l'entraînement. En lisant individuellement les phrases où le modèle manquait une personne, un motif est apparu : 80 % de ces cas étaient des références partielles (nom de famille seul, sans le prénom) à une personne déjà détectée ailleurs dans le même document sous sa forme complète. Ce n'était pas un problème de modèle — c'était un problème de pipeline de production. Un mécanisme de propagation, ajouté directement dans notre code de détection (et non dans l'entraînement), a permis de re-détecter ces références partielles à partir des entités déjà trouvées.

Ce dernier correctif a fait franchir au système les deux premiers seuils de validation (rappel personnes 85,5 %, organisations 100 %) — pour la première fois depuis le début du projet.

Le dernier obstacle : l'oubli catastrophique

Il restait un problème : les données structurées (numéros de TVA, IBAN, identifiants d'entreprise) avaient régressé après le fine-tuning. En creusant, nous avons trouvé la cause : ces catégories étaient 30 fois moins représentées dans nos données d'entraînement que les personnes physiques. Un modèle qu'on entraîne beaucoup sur une chose "oublie" progressivement les catégories qu'il voit rarement — un phénomène classique en apprentissage automatique appelé oubli catastrophique.

La solution rapide aurait été de dupliquer les documents existants contenant ces catégories rares. Nous l'avons écartée : dupliquer un document n'ajoute que de la fréquence, pas de la diversité, et le modèle risque de mémoriser des motifs plutôt que d'apprendre à généraliser. Nous avons choisi la solution plus coûteuse mais plus saine : générer de nouveaux documents synthétiques diversifiés spécifiquement pour ces catégories sous-représentées, en doublant leur volume sans jamais toucher au jeu de test.

Un dernier bug, découvert en creusant plutôt qu'en ré-entraînant

Malgré ce correctif, un écart persistait sur les numéros de TVA. Plutôt que de lancer un nouvel entraînement, nous avons analysé individuellement chaque cas manqué — et trouvé un bug de fusion des détections en production : quand une expression régulière détecte correctement un numéro de TVA, et que le modèle détecte (à tort) un numéro de sécurité sociale exactement au même endroit avec la même longueur de caractères, notre logique de fusion des chevauchements ne comparait que la longueur, jamais le type d'entité ni la source de la détection — et laissait parfois gagner la mauvaise étiquette.

Une fois corrigé (en cas d'égalité de longueur, la détection par expression régulière — plus fiable pour les données structurées — l'emporte toujours), le rappel sur les données structurées est passé de 78,0 % à 95,7 %, sans dépenser une seconde de calcul supplémentaire. Ce correctif a même amélioré les performances du modèle de base non ré-entraîné, confirmant que ce bug préexistait à tout ce projet.

Résultat final

MétriqueRésultatSeuil de validation
Rappel personnes physiques85,5 %≥ 85 % ✅
Rappel organisations95,7 %≥ 75 % ✅
Rappel données structurées95,7 %≥ 90 % ✅

Le modèle final a été entraîné exclusivement sur des données publiques (décisions de justice anonymisées, registres d'entreprises, marchés publics) et des documents synthétiques — aucun document client réel n'a été utilisé pour l'entraînement. L'inférence en production reste sur notre infrastructure existante (aucun changement d'architecture ni de coût récurrent) ; seul l'entraînement, ponctuel, a utilisé un calcul GPU externe temporaire.

Ce qu'on en retient

Sur six itérations d'entraînement, quatre échecs ont chacun révélé une cause différente — jamais celle qu'on avait supposée au départ. Le seul point commun : chaque avancée réelle est venue d'une analyse individuelle des erreurs, jamais d'un ajustement à l'aveugle. C'est la méthode qu'on applique à chaque décision technique chez KGM : mesurer précisément, comprendre la cause réelle, corriger la bonne chose — même quand la bonne chose n'est pas celle qu'on avait prévu d'aller corriger.

Questions fréquentes

Pourquoi partir de GLiNER plutôt que d'un LLM généraliste pour cette tâche ?

GLiNER est une architecture de détection d'entités « zero-shot » : on définit une taxonomie de catégories sans avoir besoin de ré-entraîner un modèle complet pour chaque nouvelle catégorie. C'est plus rapide, moins coûteux en inférence, et plus facile à auditer qu'un LLM généraliste pour une tâche de détection structurée comme celle-ci.

Le fine-tuning a-t-il nécessité une infrastructure GPU permanente ?

Non. L'entraînement a utilisé un calcul GPU externe temporaire et ponctuel (quelques heures par cycle). L'inférence en production reste sur notre infrastructure CPU existante, sans changement d'architecture ni coût récurrent supplémentaire.

Comment savez-vous que le modèle ne régresse pas sur d'autres catégories en corrigeant une catégorie spécifique ?

Notre méthodologie d'évaluation interne mesure le rappel et la précision par catégorie et par domaine juridique sur un jeu de test figé, jamais utilisé pour l'entraînement. Tout seuil de validation qui régresse de plus de quelques points bloque la mise en production du modèle, quel que soit le gain obtenu ailleurs.