VeilleAnalytics — Classifieur thématique zero-shot + fine-tuné
Classe un article de veille tech (titre + résumé) dans 7 thèmes. Deux approches
cohabitent dans ce dépôt :
- Zero-shot multilingue (
MoritzLaurer/mDeBERTa-v3-base-mnli-xnli),
composant ML du projet veille-analytics
(Étape 13). Le contenu étant en français, on utilise un modèle multilingue avec des
hypothèses NLI descriptives en français plutôt que le modèle anglophone initialement prévu.
- Fine-tuné local (
camembert-base, Étape 17) — entraîné sur les labels faibles Mistral
pour tenter de rattraper l'écart avec la baseline LLM. Voir section dédiée plus bas.
Multi-étiquette : un article peut relever de plusieurs thèmes (scores sigmoïdes indépendants).
Thèmes : IA/ML · DevOps/Infrastructure · Architecture · Sécurité · Développement ·
Pratiques/Qualité · Productivité/Outils.
Fichiers
classifier.py — cœur partagé zero-shot (thèmes, hypothèses FR, pipeline zero-shot, classify()).
app.py — UI Gradio + API auto-exposée (point d'entrée du Space).
evaluate.py — évaluation locale (zero-shot vs Mistral, + fine-tuné via --model-dir).
train.py — fine-tuning multi-label de camembert-base (Étape 17).
finetuned.py — pendant de classifier.py pour le modèle fine-tuné (classify_finetuned()).
model-finetuned/ — poids du modèle fine-tuné (généré par train.py, gitignoré).
eval_results.json — artefact produit par evaluate.py : métriques micro/macro par système
(zero-shot, fine-tuné, Mistral) et par thème, réutilisé tel quel dans le M3.2.
train_results.json — artefact produit par train.py : courbe de validation par epoch et
best.threshold, relu ensuite par evaluate.py pour le report « au seuil de validation ».
requirements.txt — dépendances (torch CPU). Inchangé par l'Étape 17 : la boucle
d'entraînement est écrite en PyTorch pur (pas de Trainer HF), pour éviter la dépendance
accelerate et les changements d'API transformers v5.
ruff.toml — configuration du lint Python (qualité de code, C19), voir ci-dessous.
Versionnement. Ce dépôt
n'a pas de remote GitHub : il vit sur le Space Hugging Face
(
hf, déploiement différé — cf.
Hébergement) et est versionné localement. La
qualité de code n'est donc pas portée par une action CI + SonarCloud comme sur
veille-analytics, mais par
ruff en local.
Développement local
1uv venv --python 3.12
2uv pip install torch --index-url https://download.pytorch.org/whl/cpu
3uv pip install transformers sentencepiece protobuf gradio
4
5.venv/bin/python app.py # UI locale sur http://127.0.0.1:7860
6.venv/bin/python evaluate.py # précision vs ../veille-analytics/data/annotations.csv
7
8uv pip install ruff # qualité de code (C19)
9.venv/bin/ruff check . # lint local — doit sortir « All checks passed! »
10
11# Étape 17 — fine-tuning + éval à 3 (zero-shot / fine-tuné / Mistral)
12.venv/bin/python train.py --data-dir ../veille-analytics/data
13.venv/bin/python evaluate.py --data-dir ../veille-analytics/data --model-dir ./model-finetuned
14
15# Orchestration des deux étapes ci-dessus (pendant de run-spark-batch.sh côté veille-analytics)
16./run-eval.sh # pipeline complet (fine-tuning ~25 min + éval)
17./run-eval.sh --skip-train # rejoue l'éval sur le modèle existant
Évaluation (100 articles annotés à la main — Étape 12)
Précision / rappel / F1 (micro) sur l'espace des 7 thèmes, jeu de validation indépendant.
Depuis l'Étape 17, evaluate.py --model-dir ./model-finetuned ajoute une troisième colonne
(le fine-tuné, cf. section suivante) et régénère train_results.json / eval_results.json.
| Approche | Point opérant | Précision | Rappel | F1 (micro) |
|---|
| ML zero-shot (mDeBERTa) | seuil 0,7 | 0,49 | 0,57 | 0,525 |
Baseline Mistral (themes_mistral) | — | 0,57 | 0,81 | 0,667 |
Le zero-shot prêt-à-l'emploi fournit une base honnête mais reste sous la classification
Mistral (LLM plus grand + prompt dédié). Fort sur Sécurité (F1 0,75), IA/ML (0,62) et
DevOps (0,62) ; faible sur Pratiques/Qualité (0,22). Détail complet dans eval_results.json
(généré par evaluate.py). Le fine-tuning (Étape 17, ci-dessous) est le levier testé pour
combler l'écart — avec un résultat en demi-teinte.
Fine-tuning CamemBERT — Étape 17
Peut-on rattraper Mistral en entraînant un petit modèle local sur ses propres étiquettes ?
train.py fine-tune
camembert-base (110M
paramètres) en multi-label sur les 7 thèmes, à partir des labels
faibles themes_mistral
(pas des annotations manuelles, réservées au test).
Méthode
- Modèle :
camembert-base, tête de classification multi-label (BCEWithLogitsLoss),
boucle d'entraînement PyTorch manuelle — pas de Trainer Hugging Face, pour éviter la
dépendance accelerate et les changements d'API transformers v5. Aucune dépendance
ajoutée : requirements.txt est inchangé par cette étape.
- Données : 529 articles exportés de D1. Après exclusion des 100 articles annotés à la
main (le test set — les mêmes textes au train constitueraient une fuite) et des articles
sans thème
themes_mistral canonique, il reste 426 candidats, séparés en train 383 /
val 43 (seed 42, VAL_FRACTION = 0.10).
- Hyperparamètres : LR 2e-5, batch 16, longueur max 256 tokens, AdamW (weight decay
0,01), warmup linéaire 10 %, jusqu'à 10 epochs avec early stopping (patience 2) sur le
micro-F1 val — balayé sur les seuils 0,3 à 0,7 via le même
prf() que evaluate.py.
- Coût : ~25 min sur CPU (WSL2, 4 cœurs). Modèle sauvé dans
model-finetuned/ (~445 Mo,
gitignoré — reproductible via le seed 42 ; un push vers le Hub HF est possible mais pas
requis pour ce projet).
1.venv/bin/python train.py --data-dir ../veille-analytics/data
2.venv/bin/python evaluate.py --data-dir ../veille-analytics/data --model-dir ./model-finetuned
Courbe d'entraînement (train_results.json)
Micro-F1 val (43 articles, labels Mistral) au fil des epochs :
0,495 (ep. 1) → 0,565 (ep. 4) → 0,685 (ep. 8, meilleur, seuil 0,4) → early stopping ep. 10.
Résultats sur la vérité terrain (100 articles annotés — eval_results.json)
| Système | Précision | Rappel | F1 (micro) |
|---|
| Zero-shot mDeBERTa (seuil 0,7) | 0,490 | 0,565 | 0,525 |
| Fine-tuné CamemBERT (seuil 0,5, optimisé sur le test*) | 0,742 | 0,548 | 0,630 |
| Fine-tuné CamemBERT (seuil 0,4, retenu sur la validation) | 0,465 | 0,702 | 0,559 |
Baseline Mistral (themes_mistral) | 0,567 | 0,810 | 0,667 |
* Le seuil 0,5 est choisi en balayant directement les 100 articles annotés : il surajuste,
exactement comme le seuil 0,7 du zero-shot ci-dessus. Le chiffre méthodologiquement propre
est celui au seuil 0,4, fixé sur la validation (383/43, jamais sur le test).
F1 par thème (au meilleur seuil test de chaque système) :
| Thème | Zero-shot | Fine-tuné | Mistral |
|---|
| IA/ML | 0,618 | 0,737 | 0,839 |
| DevOps/Infrastructure | 0,615 | 0,650 | 0,610 |
| Architecture | 0,531 | 0,630 | 0,676 |
| Sécurité | 0,754 | 0,824 | 0,885 |
| Développement | 0,424 | 0,702 | 0,615 |
| Pratiques/Qualité | 0,222 | 0,000 | 0,407 |
| Productivité/Outils | 0,316 | 0,000 | 0,556 |
Bilan honnête
- Le fine-tuning bat nettement le zero-shot (+0,105 de micro-F1 au meilleur seuil, +0,034
au seuil validation) et dépasse même Mistral sur 2 thèmes — Développement (0,702 vs
0,615) et DevOps/Infrastructure (0,650 vs 0,610) — avec une précision micro bien meilleure
(0,742 vs 0,567 pour Mistral).
- Mais il reste sous Mistral en global (0,630 vs 0,667) et s'effondre sur les deux
thèmes les plus rares, Pratiques/Qualité et Productivité/Outils : F1 à 0,000 au seuil
0,5 (le modèle ne dépasse presque jamais 0,5 sur ces thèmes ; au seuil 0,4 ils réapparaissent
partiellement). Deux causes probables : seulement ~30-40 exemples positifs par classe rare
dans le train, et des labels d'entraînement bruités par Mistral (qui plafonne la qualité
atteignable).
- Leviers documentés mais non testés :
pos_weight par thème dans la BCE pour compenser
le déséquilibre, plus de données, seuils par thème plutôt qu'un seuil global.
- Enseignement (M3.2) : avec ~400 exemples faiblement labellisés, un petit modèle local
fine-tuné se hisse près du LLM qui l'a supervisé — mais la vérité terrain manuelle reste
indispensable pour le voir. Le micro-F1 val donnait 0,685 (contre Mistral comme juge), la
vérité terrain humaine ramène ça à 0,559-0,630 selon le seuil.
Hébergement
Statut : local pour l'instant. Le déploiement sur Hugging Face Spaces est différé :
depuis juillet 2026, HF gate CPU basic ET ZeroGPU derrière un compte PRO (le build ZeroGPU
refuse par ailleurs torch==2.13.0+cpu). Le code Gradio (app.py, en-tête YAML du Space,
requirements.txt) reste prêt si un compte PRO est pris plus tard.
Pour l'exécution : .venv/bin/python app.py (UI locale). Pour un endpoint cloud appelable
depuis le Worker (Étape 14), on visera l'Inference API serverless de HF (appel du modèle
hébergé via token, sans Space à héberger) — dispo à vérifier à ce moment-là.
Mise à jour juillet 2026 (Étape 14). Piste confirmée : le modèle est servi par
l'Inference API serverless de Hugging Face, provider hf-inference, endpoint
https://router.huggingface.co/hf-inference/models/MoritzLaurer/mDeBERTa-v3-base-mnli-xnli
(token avec permission « Inference Providers », free tier avec rate limits). C'est ce
qu'utilise désormais le Worker veille-analytics : module src/lib/classifyMl.ts, qui porte
le contrat de classifier.py (mêmes hypothèses FR, même template, seuil 0,7). Le Space Gradio
ci-dessus reste optionnel/différé — il n'est plus sur le chemin critique de la prod.
Déploiement Space (si compte PRO)
1hf auth login # token HF (write)
2# Créer le Space sur https://huggingface.co/new-space (SDK: Gradio, hardware CPU basic), puis :
3git init && git add . && git commit -m "Étape 13 — classifieur zero-shot"
4git remote add hf https://huggingface.co/spaces/<user>/<space>
5git push hf main
Le build lit l'en-tête YAML (sdk: gradio, app_file: app.py) et installe requirements.txt.