AnonyNER v3.1 : bilan d'entraînement — 30 labels, 16 000 exemples, évaluation par label
AnonyNER est le composant NER du projet Victor — un modèle spaCy entraîné spécifiquement sur des entités sensibles de logs de sécurité. La version 3.1 marque un changement d’échelle significatif : passage de 12 à 30 labels, corpus multiplié par 8, et pour la première fois une évaluation complète par label sur un jeu de test indépendant.
Cet article est un bilan technique, pas un tutoriel. L’objectif est de montrer où en est le modèle, ce qui fonctionne réellement, et ce qui reste à faire pour une utilisation en production.
Ce qui a changé dans v3.1
Labels : de 12 à 30, répartis en quatre groupes.
La v3 couvrait les entités réseau et firewall (IP, hostname, interface, CVE…). La v3.1 ajoute les entités Linux/Unix (UNIX_USER, UNIX_GROUP), Windows (WIN_USER, WIN_HOST, WIN_SID, WIN_GROUP, EVENT_ID, REGISTRY_KEY, SCHEDULED_TASK, WIN_SERVICE) et des labels génériques cross-plateformes (PID, PROCESS_NAME, COMMAND_LINE, FILE_HASH, ACTION, ASN, PROTOCOL).
Corpus : de ~2 000 à 15 851 exemples annotés.
| Source | Exemples | Taux d’annotation auto |
|---|---|---|
| Logs OPNsense (corpus existant) | 1 997 | — |
SSH Linux — Elastic auth.log |
6 966 | 97.8% |
| Windows Security Events — OTRF | 4 191 | 83.8% |
| Apache access logs — Elastic | 2 977 | 99.2% |
| Synthétiques (labels rares) | 92 | 100% (généré) |
L’annotation est LLM-assistée : chaque ligne de log est envoyée à qwen2.5-coder:7b via Ollama, avec un prompt listant les 30 labels et leurs exemples. Un score de confiance composite décide si l’annotation passe automatiquement ou part en revue humaine.
Architecture du pipeline :
Logs bruts
└─► annotate_corpus.py (LLM → JSONL annoté)
└─► clean_corpus.py (filtre labels invalides, déduplique)
└─► prepare_spacy_dataset.py (JSONL → .spacy train/dev)
└─► train_anonyner.py (fine-tuning spaCy, GPU auto-détecté)
└─► model-best (meilleur checkpoint)
Source de vérité unique : labels.py centralise les 30 labels avec description et exemples. Le prompt LLM, les filtres de validation et les scripts d’entraînement en dérivent tous — ajouter un label revient à ajouter une entrée dans un dictionnaire.
Résultats globaux
F1 : 86.46% Precision : 85.95% Recall : 86.97%
Le modèle est entraîné par fine-tuning depuis en_anonyner (v3) — les poids existants sont préservés, seuls les deltas sont appris. L’early stopping intervient vers le step 5 400 (epoch 8), avec convergence réelle dès l’epoch 2.
Évaluation par label
Le F1 global à 86% cache des disparités importantes. Voici les résultats sur le dev set (~3 200 exemples, 20% du corpus, non vu pendant l’entraînement) :
| Label | P | R | F1 |
|---|---|---|---|
REGISTRY_KEY |
99.7% | 99.7% | 99.7% |
UNIX_USER |
96.6% | 96.9% | 96.8% |
DOMAIN |
95.7% | 97.8% | 96.7% |
WIN_USER |
93.7% | 98.3% | 96.0% |
CVE |
91.7% | 100% | 95.7% |
ACTION |
100% | 91.7% | 95.7% |
WIN_SID |
86.0% | 100% | 92.5% |
MAC_ADDRESS |
93.5% | 87.9% | 90.6% |
SERVICE_ACCOUNT |
88.9% | 90.6% | 89.7% |
FILE_PATH |
81.3% | 95.6% | 87.8% |
INTERFACE |
84.4% | 91.5% | 87.8% |
IP_ADDRESS |
89.8% | 84.7% | 87.2% |
WIN_HOST |
76.7% | 94.3% | 84.6% |
VPN_USER |
92.3% | 80.0% | 85.7% |
ASN |
100% | 75.0% | 85.7% |
IP_SUBNET |
92.0% | 86.8% | 89.3% |
URL_URI |
75.3% | 78.2% | 76.7% |
PID |
75.9% | 75.9% | 75.9% |
FIREWALL_RULE |
79.2% | 55.9% | 65.5% |
PORT_NUMBER |
62.6% | 64.0% | 63.3% |
PROCESS_NAME |
50.6% | 79.2% | 61.8% |
FILE_HASH |
100% | 40.0% | 57.1% |
HOSTNAME |
51.9% | 58.7% | 55.1% |
PROTOCOL |
92.3% | 34.3% | 50.0% |
COMMAND_LINE |
61.9% | 28.9% | 39.4% |
SCHEDULED_TASK |
— | — | 0% |
EVENT_ID |
— | — | 0% |
WIN_GROUP |
— | — | 0% |
Lectures
9 labels dépassent F1 90% : REGISTRY_KEY, UNIX_USER, DOMAIN, WIN_USER, CVE, ACTION, WIN_SID, MAC_ADDRESS, IP_SUBNET. Ce sont les labels les mieux représentés dans le corpus et les plus stables en termes de format.
Precision haute, Recall faible (PROTOCOL P=92%, R=34% ; ASN P=100%, R=75%) : le modèle reconnaît correctement ces entités quand il les prédit, mais il en manque la majorité. Cause : trop peu d’exemples variés — le modèle est prudent, pas ignorant.
Labels à 0% (SCHEDULED_TASK, EVENT_ID, WIN_GROUP) : le corpus contient respectivement 3, 14 et 1 exemples — insuffisant pour l’apprentissage. Ces labels existent dans le modèle mais ne sont jamais prédits avec assez de confiance.
HOSTNAME à 55% : collision entre noms de machines Linux (srv-db01), Windows (DESKTOP-AB12CD) et domaines (corp.local). Le modèle hésite sur la frontière — les critères d’annotation sont à clarifier.
Distance avec la production
Pour un usage en anonymisation de logs sensibles, le Recall est la métrique critique : une entité manquée est une fuite d’information. Les cibles de production sont F1 ≥ 90% global et R ≥ 93%, avec aucun label à F1 < 80%.
État actuel : F1=86.5%, R=87.0%. Environ 4 points sous la cible de production.
Trois axes d’amélioration identifiés :
1. Données pour les labels déficitaires — PROTOCOL (R=34%), COMMAND_LINE (R=29%), SCHEDULED_TASK (0%), EVENT_ID (0%), WIN_GROUP (0%) ont besoin de plusieurs centaines d’exemples supplémentaires depuis des logs réels, pas de données synthétiques.
2. Résoudre la confusion HOSTNAME/WIN_HOST/DOMAIN — revoir les critères d’annotation pour distinguer clairement ces trois types sur des exemples ambigus.
3. Migration vers spaCy Transformers — l’architecture tok2vec atteint un plafond naturel vers 87-88% F1 sur un corpus aussi hétérogène. Passer à DistilBERT ou un transformer spécialisé logs permettrait de franchir ce plafond. Gain estimé : +4-6% F1. Prérequis : session GPU dédiée (~4h sur RTX 4070 Ti).
Accélération GPU sur WSL2 sans toolkit CUDA
La machine de développement tourne sous WSL2 avec une RTX 4070 Ti. Le CUDA toolkit n’est pas installé système — seul Ollama embarque ses propres .so. spaCy/thinc exige CuPy, qui exige libcublas, libcurand, libnvrtc.
La solution : les paquets pip NVIDIA fournissent ces librairies directement dans le venv :
pip install cupy-cuda12x \
nvidia-curand-cu12 \
nvidia-cuda-nvrtc-cu12 \
nvidia-cublas-cu12 \
nvidia-cuda-runtime-cu12
train_anonyner.py construit dynamiquement le LD_LIBRARY_PATH depuis les sous-dossiers nvidia/*/lib/ du venv avant de lancer spacy train --gpu-id 0. La détection GPU est transparente — si CuPy n’est pas disponible, l’entraînement bascule silencieusement sur CPU.
Prochaines étapes
- Annotation ciblée sur les logs contenant des
PROTOCOL,COMMAND_LINE,SCHEDULED_TASKetEVENT_IDexplicites - Résolution de la confusion
HOSTNAME/WIN_HOST/DOMAINdans les critères d’annotation - Intégration du modèle v3.1 dans le pipeline de production Victor et mesure des gaps résiduels sur logs réels
- Étude de faisabilité spaCy Transformers (DistilBERT) pour franchir le seuil de 90% F1
Le code source du pipeline d’entraînement est disponible dans le dépôt Victor sur GitHub, répertoire training/.
Références datasets : LogHub (Linux, Apache), OTRF Security Datasets (Windows Security Events), Elastic Examples (auth.log, Apache).
