feat(phase2): Intégration CamemBERT-bio ONNX comme 3e signal NER (vote triple)
- camembert_ner_manager.py : inférence ONNX CPU (~10ms), predict/predict_long/validate_eds_entities - Vote triple NER : EDS-Pseudo (confiance) + GLiNER (zero-shot) + CamemBERT-bio (fine-tuné F1=89%) - CamemBERT-bio peut sauver un vrai nom à basse confiance EDS (camembert_confirmed=True) - CamemBERT-bio confirme le rejet des FP médicaux (Paracétamol, Tramadol → False) - Intégré dans process_pdf via paramètre camembert_manager - run_batch_30_audit.py mis à jour pour charger le modèle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1943,20 +1943,21 @@ def _mask_with_eds_pseudo(text: str, ents: List[Dict[str, Any]], cfg: Dict[str,
|
||||
# Vérifier si c'est un médicament connu
|
||||
if w.lower() in _MEDICATION_WHITELIST:
|
||||
continue
|
||||
# Chantier 3+4 : Confiance NER + vote croisé GLiNER + gazetteers INSEE
|
||||
# Chantier 3+4+5 : Confiance NER + vote croisé GLiNER + CamemBERT-bio + gazetteers INSEE
|
||||
# Sécurité d'abord : haute confiance NER → toujours masquer
|
||||
# GLiNER peut rejeter SEULEMENT si confiance NER basse
|
||||
gliner_vote = e.get("gliner_confirmed") # True=PII, False=médical, None=neutre
|
||||
# GLiNER/CamemBERT peuvent rejeter SEULEMENT si confiance NER basse
|
||||
gliner_vote = e.get("gliner_confirmed") # True=PII, False=médical, None=neutre
|
||||
camembert_vote = e.get("camembert_confirmed") # True=PII confirmé, False=non détecté, None=neutre
|
||||
if label in ("NOM", "PRENOM"):
|
||||
score = e.get("score", 1.0)
|
||||
# Gazetteer INSEE : prénom connu = renforcement confiance (ne pas filtrer)
|
||||
is_known_prenom = w.lower() in _INSEE_PRENOMS
|
||||
if isinstance(score, float) and score < 0.70 and not is_known_prenom:
|
||||
# Basse confiance NER + pas un prénom connu : GLiNER peut trancher
|
||||
if gliner_vote is False:
|
||||
continue # NER pas sûr + GLiNER dit "médical" → skip
|
||||
if score < 0.30:
|
||||
continue # Très basse confiance → skip même sans GLiNER
|
||||
# Basse confiance NER + pas un prénom connu
|
||||
if gliner_vote is False and camembert_vote is not True:
|
||||
continue # GLiNER dit "médical" + CamemBERT ne confirme pas → skip
|
||||
if score < 0.30 and camembert_vote is not True:
|
||||
continue # Très basse confiance + CamemBERT ne confirme pas → skip
|
||||
# Chantier 2 : Safe patterns contextuels (Philter-style)
|
||||
# Token suivi/précédé de dosages ou formes pharma → jamais un nom de personne
|
||||
pos = text.find(w)
|
||||
@@ -1994,7 +1995,8 @@ def _mask_with_eds_pseudo(text: str, ents: List[Dict[str, Any]], cfg: Dict[str,
|
||||
|
||||
|
||||
def apply_eds_pseudo_on_narrative(text_out: str, cfg: Dict[str, Any], manager: "EdsPseudoManager",
|
||||
gliner_mgr: Any = None) -> Tuple[str, List[PiiHit]]:
|
||||
gliner_mgr: Any = None,
|
||||
camembert_mgr: Any = None) -> Tuple[str, List[PiiHit]]:
|
||||
"""Applique EDS-Pseudo sur le narratif avec validation croisée GLiNER optionnelle."""
|
||||
if manager is None or not manager.is_loaded():
|
||||
return text_out, []
|
||||
@@ -2021,6 +2023,10 @@ def apply_eds_pseudo_on_narrative(text_out: str, cfg: Dict[str, Any], manager: "
|
||||
if gliner_mgr is not None and hasattr(gliner_mgr, 'validate_entities') and gliner_mgr.is_loaded():
|
||||
for i, (para, ents) in enumerate(zip(paras, ents_per_para)):
|
||||
ents_per_para[i] = gliner_mgr.validate_entities(para, ents, threshold=0.4)
|
||||
# Chantier 5 : Validation croisée CamemBERT-bio (vote NER fine-tuné)
|
||||
if camembert_mgr is not None and hasattr(camembert_mgr, 'validate_eds_entities') and camembert_mgr.is_loaded():
|
||||
for i, (para, ents) in enumerate(zip(paras, ents_per_para)):
|
||||
ents_per_para[i] = camembert_mgr.validate_eds_entities(para, ents, threshold=0.3)
|
||||
buf = []
|
||||
for para, ents in zip(paras, ents_per_para):
|
||||
masked = _mask_with_eds_pseudo(para, ents, cfg, hits)
|
||||
@@ -2465,6 +2471,7 @@ def process_pdf(
|
||||
ogc_label: Optional[str] = None,
|
||||
vlm_manager=None,
|
||||
gliner_manager=None,
|
||||
camembert_manager=None,
|
||||
) -> Dict[str, str]:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
cfg = load_dictionaries(config_path)
|
||||
@@ -2487,7 +2494,7 @@ def process_pdf(
|
||||
if use_hf and ner_manager is not None and ner_manager.is_loaded():
|
||||
# Détecter le type de manager et appeler la bonne fonction
|
||||
if EdsPseudoManager is not None and isinstance(ner_manager, EdsPseudoManager):
|
||||
final_text, hf_hits = apply_eds_pseudo_on_narrative(final_text, cfg, ner_manager, gliner_mgr=gliner_manager)
|
||||
final_text, hf_hits = apply_eds_pseudo_on_narrative(final_text, cfg, ner_manager, gliner_mgr=gliner_manager, camembert_mgr=camembert_manager)
|
||||
else:
|
||||
final_text, hf_hits = apply_hf_ner_on_narrative(final_text, cfg, ner_manager, ner_thresholds)
|
||||
anon.audit.extend(hf_hits)
|
||||
|
||||
Reference in New Issue
Block a user