veille-ml / classifier.py
antoinecoulon's picture
Étape 17/19: fine-tuning CamemBERT + qualité ruff + README (versionnement, artefacts)
b9d8a2e verified
Raw
History Blame Contribute Delete
2.47 kB
"""Cœur de classification zero-shot multilingue.
Partagé par l'app Gradio (app.py) et l'évaluation (evaluate.py) pour garantir
que le Space déployé et la mesure de précision utilisent exactement la même logique.
"""
from __future__ import annotations
from functools import lru_cache
MODEL_ID = "MoritzLaurer/mDeBERTa-v3-base-mnli-xnli"
# 7 thèmes canoniques — source de vérité (cf. veille-analytics/scripts/reclassify.js
# et veille-dashboard/shared/utils/themes.ts).
THEMES = [
"IA/ML",
"DevOps/Infrastructure",
"Architecture",
"Sécurité",
"Développement",
"Pratiques/Qualité",
"Productivité/Outils",
]
# Libellés canoniques -> hypothèses descriptives FR. Le NLI zero-shot est nettement
# plus précis avec des phrases explicites qu'avec des labels bruts (« IA/ML »).
LABEL_MAP = {
"IA/ML": "intelligence artificielle et machine learning",
"DevOps/Infrastructure": "DevOps, cloud et infrastructure",
"Architecture": "architecture logicielle et conception de systèmes",
"Sécurité": "sécurité informatique et cybersécurité",
"Développement": "développement logiciel et programmation",
"Pratiques/Qualité": "pratiques d'ingénierie, tests et qualité logicielle",
"Productivité/Outils": "productivité et outils pour développeurs",
}
_LABEL_INVERSE = {v: k for k, v in LABEL_MAP.items()}
CANDIDATE_LABELS = list(LABEL_MAP.values())
HYPOTHESIS_TEMPLATE = "Cet article parle de {}."
@lru_cache(maxsize=1)
def get_pipeline():
"""Charge le pipeline zero-shot une seule fois (coûteux : ~280M params)."""
from transformers import pipeline
return pipeline("zero-shot-classification", model=MODEL_ID)
def classify(titre: str, resume: str = "") -> dict[str, float]:
"""Classe un article dans les 7 thèmes. Retourne {theme: score} trié desc.
multi_label=True -> un score sigmoïde indépendant par thème (les scores ne
somment pas à 1), adapté au multi-étiquetage : un article peut relever de
plusieurs thèmes.
"""
text = f"{titre}. {resume}".strip()
out = get_pipeline()(
text,
candidate_labels=CANDIDATE_LABELS,
hypothesis_template=HYPOTHESIS_TEMPLATE,
multi_label=True,
)
scores = {
_LABEL_INVERSE[label]: float(score)
for label, score in zip(out["labels"], out["scores"], strict=True)
}
return dict(sorted(scores.items(), key=lambda kv: kv[1], reverse=True))