- Ajout documentation VLM (Ollama qwen2.5vl:7b) - Pipeline complet: Regex → VLM → EDS-Pseudo → CamemBERT → Contextuel - Nouvelles exigences REQ-013/REQ-014 pour optimisation VLM - Tâches Phase 2.5: amélioration prompt, validation croisée, perf - Document ARCHITECTURE_REELLE.md avec détails complets - Matériel: AMD Ryzen 9 9950X, 128GB RAM, RTX 5070 12GB - Objectifs: Rappel ≥99.5%, Précision ≥97%, F1 ≥0.98
474 lines
11 KiB
Markdown
474 lines
11 KiB
Markdown
# Architecture Réelle du Système d'Anonymisation
|
|
|
|
## Vue d'Ensemble
|
|
|
|
Le système d'anonymisation combine **5 couches de détection** pour garantir une couverture maximale des PII dans les documents médicaux PDF.
|
|
|
|
## 1. Extraction de Texte (5 passes)
|
|
|
|
### Méthodes d'extraction (ordre de priorité)
|
|
|
|
1. **pdfplumber** : Extraction textuelle native (PDF textuels)
|
|
2. **pdfminer** : Extraction alternative avec LAParams
|
|
3. **PyMuPDF** : Fallback si artefacts `(cid:xx)` détectés
|
|
4. **docTR OCR** : OCR deep learning pour PDF scannés (parallèle avec tesseract)
|
|
5. **tesseract OCR** : OCR complémentaire
|
|
|
|
### OCR Word Map
|
|
|
|
Pour les PDF scannés, génération d'une carte de mots avec coordonnées normalisées (0→1) :
|
|
```python
|
|
OcrWordMap = Dict[int, List[Tuple[str, float, float, float, float]]]
|
|
# {page_idx: [(word_text, x0_norm, y0_norm, x1_norm, y1_norm), ...]}
|
|
```
|
|
|
|
Utilisée pour :
|
|
- Matching flou des identifiants numériques manuscrits (VLM)
|
|
- Redaction raster précise
|
|
|
|
---
|
|
|
|
## 2. Détection PII (5 couches)
|
|
|
|
### 2.1 Regex (Couche 1)
|
|
|
|
**Fichier** : `anonymizer_core_refactored_onnx.py`
|
|
|
|
**PII détectés** :
|
|
- EMAIL, TEL (formats fragmentés), IBAN, NIR (avec validation clé modulo 97)
|
|
- IPP, FINESS, RPPS, OGC
|
|
- Dates (multiples formats), dates de naissance
|
|
- Adresses postales, codes postaux, BP
|
|
- Âges, numéros de dossier, épisodes
|
|
- Établissements de santé, services hospitaliers
|
|
|
|
**Patterns contextuels** :
|
|
- `Dr. NOM`, `Patient: NOM`, `Rédigé par NOM`
|
|
- Listes virgulées : `Dr. X, Y, DUPONT`
|
|
- Champs structurés Trackare
|
|
|
|
**Configuration** : `config/dictionnaires.yml`
|
|
- Listes blanches (sections_titres, noms_maj_excepts)
|
|
- Force-mask (termes/regex à masquer systématiquement)
|
|
- Regex overrides personnalisées
|
|
|
|
### 2.2 VLM - Vision Language Model (Couche 2)
|
|
|
|
**Fichier** : `vlm_manager.py`
|
|
|
|
**Modèle** : Ollama avec qwen2.5vl:7b (ou qwen3-vl:8b)
|
|
|
|
**Fonctionnement** :
|
|
1. Conversion de chaque page PDF en image (150 DPI)
|
|
2. Redimensionnement si > 2048px (côté le plus long)
|
|
3. Envoi à Ollama avec prompt structuré
|
|
4. Parsing de la réponse JSON (avec réparation si tronquée)
|
|
|
|
**20+ catégories détectées** :
|
|
- Identité : NOM, PRENOM, DATE_NAISSANCE, NIR, AGE
|
|
- Contact : ADRESSE, TELEPHONE, EMAIL, CODE_POSTAL, VILLE
|
|
- Identifiants médicaux : IPP, NDA, RPPS, NUMERO_PATIENT, NUMERO_LOT, NUMERO_ORDONNANCE, NUMERO_SEJOUR
|
|
- Contexte : ETABLISSEMENT, SERVICE, DATE
|
|
|
|
**Gestion des pages manuscrites** :
|
|
- Si < 100 mots OCR détectés → masquage total de la page
|
|
- Évite les hallucinations du VLM sur pages complexes
|
|
|
|
**Matching flou** :
|
|
- Pour identifiants numériques manuscrits
|
|
- Compare séquences de chiffres (ratio ≥ 0.7)
|
|
- Utilise l'OCR word map pour localisation
|
|
|
|
**Configuration** :
|
|
```python
|
|
@dataclass
|
|
class VlmConfig:
|
|
base_url: str = "http://localhost:11434"
|
|
model: str = "qwen2.5vl:7b"
|
|
timeout: int = 180
|
|
max_image_size: int = 2048
|
|
temperature: float = 0.1
|
|
num_predict: int = 8192
|
|
min_confidence: float = 0.5
|
|
```
|
|
|
|
**Prompt système** :
|
|
```
|
|
Tu identifies les données personnelles et identifiants traçables dans les documents
|
|
médicaux français. Réponds uniquement en JSON.
|
|
```
|
|
|
|
**Dégradation gracieuse** : Si Ollama indisponible, le pipeline continue sans VLM.
|
|
|
|
### 2.3 NER - Named Entity Recognition (Couche 3)
|
|
|
|
#### Option A : EDS-Pseudo (recommandé)
|
|
|
|
**Fichier** : `eds_pseudo_manager.py`
|
|
|
|
**Modèle** : AP-HP/eds-pseudo-public via edsnlp
|
|
|
|
**Performance** : F1=0.97 sur données cliniques AP-HP (11M documents)
|
|
|
|
**13 labels détectés** :
|
|
- NOM, PRENOM, MAIL, TEL, SECU (NIR)
|
|
- ADRESSE, ZIP, VILLE, HOPITAL
|
|
- DATE, DATE_NAISSANCE, IPP, NDA
|
|
|
|
**Mapping vers PLACEHOLDERS** :
|
|
```python
|
|
EDS_LABEL_MAP = {
|
|
"NOM": "NOM",
|
|
"PRENOM": "NOM",
|
|
"MAIL": "EMAIL",
|
|
"TEL": "TEL",
|
|
"SECU": "NIR",
|
|
# ...
|
|
}
|
|
```
|
|
|
|
#### Option B : CamemBERT-NER (fallback)
|
|
|
|
**Fichier** : `ner_manager_onnx.py`
|
|
|
|
**Modèles** :
|
|
- `cmarkea/distilcamembert-base-ner` (rapide, quantifié)
|
|
- `Jean-Baptiste/camembert-ner` (robuste)
|
|
|
|
**Runtime** : ONNX avec optimum.onnxruntime
|
|
|
|
**Tags supportés** : PER, LOC, ORG, DATE
|
|
|
|
**Seuils de confiance** :
|
|
```python
|
|
@dataclass
|
|
class NerThresholds:
|
|
per: float = 0.90
|
|
org: float = 0.90
|
|
loc: float = 0.90
|
|
date: float = 0.85
|
|
```
|
|
|
|
### 2.4 Extraction Trackare (Couche 4)
|
|
|
|
**Fonction** : `_extract_trackare_identity()`
|
|
|
|
**Champs structurés extraits** :
|
|
- Nom de naissance, Nom et Prénom
|
|
- Lieu de naissance, Ville de résidence
|
|
- Contacts (Conjoint, Parent, Époux, etc.)
|
|
- Personnel médical (Rédigé par, Aide:, IDE:, etc.)
|
|
- Pattern multi-lignes : `Prenom\nNOM`
|
|
|
|
**Regex spécialisées** :
|
|
- `RE_EXTRACT_PATIENT`, `RE_EXTRACT_NOM_NAISSANCE`
|
|
- `RE_EXTRACT_CONTACT`, `RE_EXTRACT_STAFF_ROLE`
|
|
- `RE_EXTRACT_DR_DEST`, `RE_EXTRACT_PR`
|
|
|
|
### 2.5 Détection Contextuelle (Couche 5)
|
|
|
|
**Patterns** :
|
|
- Noms après titres : `Dr. DUPONT`, `Pr. MARTIN`
|
|
- Noms en MAJUSCULES (hors stopwords médicaux)
|
|
- Listes virgulées : `le Dr X, Y, LAZARO`
|
|
|
|
**Stopwords médicaux** : ~4000 termes
|
|
- Médicaments (edsnlp drugs.json)
|
|
- Termes médicaux, anatomie, pathologies
|
|
- Abréviations, services hospitaliers
|
|
- Mots courants français
|
|
|
|
---
|
|
|
|
## 3. Consolidation Globale
|
|
|
|
**Fonction** : `_apply_global_pii_tokens()`
|
|
|
|
### Propagation des PII
|
|
|
|
Chaque PII détecté est propagé sur toutes les pages du document :
|
|
|
|
**Types propagés globalement** :
|
|
- `NOM_GLOBAL` : tous les tokens de noms
|
|
- `TEL_GLOBAL`, `EMAIL_GLOBAL`, `ADRESSE_GLOBAL`
|
|
- `CODE_POSTAL_GLOBAL`, `VILLE_GLOBAL`, `ETAB_GLOBAL`
|
|
- `VLM_SERVICE`, `VLM_ETAB`, `DATE_NAISSANCE`
|
|
|
|
### Noms compagnons
|
|
|
|
Détection de mots en MAJUSCULES adjacents à un nom connu :
|
|
```python
|
|
# Si "DUPONT" est détecté, et "JEAN-PIERRE DUPONT" apparaît
|
|
# → "JEAN-PIERRE" est ajouté comme nom compagnon
|
|
```
|
|
|
|
### Noms composés
|
|
|
|
Traitement en bloc : `JEAN-PIERRE`, `CAZELLES-BOUDIER`
|
|
|
|
### Rescan sélectif
|
|
|
|
Rescan des PII critiques ayant pu échapper au premier passage :
|
|
- EMAIL, TEL, IBAN, NIR
|
|
- Téléphones fragmentés sur plusieurs lignes
|
|
- Codes postaux orphelins
|
|
|
|
---
|
|
|
|
## 4. Redaction PDF
|
|
|
|
### 4.1 Vector Redaction (PDF textuels)
|
|
|
|
**Méthode** : `page.search_for()` (PyMuPDF)
|
|
|
|
1. Pour chaque PII dans l'audit
|
|
2. Recherche du texte exact dans la page
|
|
3. Si non trouvé et PII avec espaces → recherche mot par mot
|
|
4. Génération de rectangles de redaction
|
|
5. Application des rectangles (texte remplacé par noir)
|
|
|
|
### 4.2 Raster Redaction (PDF scannés)
|
|
|
|
**Méthode** : Conversion en image + redaction pixel
|
|
|
|
1. Conversion de chaque page en pixmap (DPI configurable)
|
|
2. Matching des PII via OCR word map
|
|
3. **Matching flou pour VLM** : identifiants numériques manuscrits
|
|
4. Dessin de rectangles noirs sur les zones PII
|
|
5. Reconstruction du PDF depuis les images
|
|
|
|
**Matching flou VLM** :
|
|
```python
|
|
def _search_ocr_words_fuzzy_digits(ocr_words, token, page_rect, min_ratio=0.7):
|
|
"""Compare les séquences de chiffres entre le token VLM et les mots OCR.
|
|
Accepte une correspondance si ≥ min_ratio des chiffres matchent."""
|
|
```
|
|
|
|
Appliqué aux catégories :
|
|
- `VLM_NUM_PATIENT`, `VLM_NUM_LOT`, `VLM_NUM_ORD`
|
|
- `VLM_NDA`, `VLM_NIR`, `VLM_IPP`, `VLM_RPPS`
|
|
|
|
---
|
|
|
|
## 5. Audit Trail
|
|
|
|
**Format** : `.audit.jsonl` (JSON Lines)
|
|
|
|
**Structure** :
|
|
```python
|
|
@dataclass
|
|
class PiiHit:
|
|
page: int
|
|
kind: str # Type de PII (EMAIL, NOM, VLM_TEL, etc.)
|
|
original: str # Texte original détecté
|
|
placeholder: str # Placeholder de remplacement
|
|
bbox_hint: Optional[Tuple[float, float, float, float]] # Coordonnées (optionnel)
|
|
```
|
|
|
|
**Traçabilité** :
|
|
- Chaque PII détecté est enregistré avec sa méthode de détection
|
|
- Permet l'analyse post-traitement
|
|
- Utilisé pour la validation post-anonymisation
|
|
|
|
---
|
|
|
|
## 6. Matériel et Performance
|
|
|
|
### Configuration actuelle
|
|
|
|
- **CPU** : AMD Ryzen 9 9950X (16 cœurs / 32 threads)
|
|
- **RAM** : 128 GB
|
|
- **GPU** : NVIDIA RTX 5070 (12 GB VRAM)
|
|
- **CUDA** : PyTorch 2.10.0 avec support CUDA
|
|
|
|
### Temps de traitement
|
|
|
|
**PDF textuels** :
|
|
- Extraction : < 1s
|
|
- Détection (Regex + NER GPU) : 2-5s
|
|
- Redaction : 1-2s
|
|
- **Total** : < 10s par PDF
|
|
|
|
**PDF scannés** :
|
|
- Extraction + OCR : 5-15s
|
|
- VLM (Ollama) : 10-30s par page (dépend du modèle)
|
|
- Détection (Regex + NER GPU) : 2-5s
|
|
- Redaction raster : 3-10s
|
|
- **Total** : 20-60s par PDF (selon nombre de pages)
|
|
|
|
### Optimisations possibles
|
|
|
|
1. **VLM** : Vérifier support GPU Ollama (CUDA)
|
|
2. **NER** : Batch processing optimisé (12 GB VRAM)
|
|
3. **Parallélisation** : 8-12 workers sur 16 cœurs
|
|
4. **Cache** : Résultats VLM par hash d'image
|
|
|
|
---
|
|
|
|
## 7. Points d'Attention
|
|
|
|
### Hallucinations VLM
|
|
|
|
Le VLM peut détecter des PII inexistants, surtout sur :
|
|
- Pages manuscrites complexes
|
|
- Documents mal orientés
|
|
- Tableaux denses
|
|
|
|
**Mitigation actuelle** :
|
|
- Masquage total si < 100 mots OCR
|
|
- Seuil de confiance (0.5)
|
|
|
|
**Amélioration proposée** :
|
|
- Validation croisée VLM ↔ NER
|
|
- Prompt amélioré avec exemples négatifs
|
|
- Rejet des détections non confirmées
|
|
|
|
### Faux Positifs NER
|
|
|
|
EDS-Pseudo peut détecter comme NOM :
|
|
- Médicaments (ex: "Eliquis", "Trulicity")
|
|
- Termes médicaux (ex: "Diabétique", "Cutanée")
|
|
- Mots courants (ex: "Toilette", "Repas")
|
|
|
|
**Mitigation** : Stopwords médicaux (~4000 termes)
|
|
|
|
### Performance VLM
|
|
|
|
Ollama en local peut être lent (10-30s par page).
|
|
|
|
**Solutions** :
|
|
- Vérifier support GPU
|
|
- Réduire `max_image_size` (trade-off qualité/vitesse)
|
|
- Cache des résultats
|
|
- Traitement parallèle (attention : charge GPU)
|
|
|
|
---
|
|
|
|
## 8. Dépendances Complètes
|
|
|
|
```
|
|
# Core
|
|
python>=3.12
|
|
pyyaml
|
|
|
|
# PDF
|
|
pymupdf
|
|
pdfplumber
|
|
pdfminer.six
|
|
pillow
|
|
|
|
# OCR
|
|
doctr[torch]
|
|
pytesseract
|
|
|
|
# NER
|
|
edsnlp[ml]>=0.12.0 # EDS-Pseudo
|
|
transformers
|
|
optimum
|
|
onnxruntime # ou onnxruntime-gpu
|
|
sentencepiece
|
|
|
|
# VLM
|
|
# Aucune dépendance Python (utilise urllib)
|
|
# Nécessite Ollama installé localement : https://ollama.ai/
|
|
|
|
# Tests & Qualité (nouveaux)
|
|
pytest
|
|
pytest-cov
|
|
pydantic
|
|
structlog
|
|
jinja2
|
|
matplotlib
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Configuration Ollama
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Linux
|
|
curl -fsSL https://ollama.ai/install.sh | sh
|
|
|
|
# macOS
|
|
brew install ollama
|
|
|
|
# Windows
|
|
# Télécharger depuis https://ollama.ai/download
|
|
```
|
|
|
|
### Téléchargement du modèle
|
|
|
|
```bash
|
|
ollama pull qwen2.5vl:7b
|
|
# ou
|
|
ollama pull qwen3-vl:8b
|
|
```
|
|
|
|
### Vérification
|
|
|
|
```bash
|
|
# Lister les modèles disponibles
|
|
ollama list
|
|
|
|
# Tester le modèle
|
|
ollama run qwen2.5vl:7b
|
|
```
|
|
|
|
### Configuration GPU (optionnel)
|
|
|
|
Ollama détecte automatiquement le GPU CUDA si disponible.
|
|
|
|
Vérifier dans les logs :
|
|
```bash
|
|
ollama serve
|
|
# Chercher : "GPU detected: NVIDIA GeForce RTX 5070"
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Workflow Complet
|
|
|
|
```
|
|
1. Chargement PDF
|
|
↓
|
|
2. Extraction texte (5 passes)
|
|
↓
|
|
3. Détection Regex
|
|
↓
|
|
4. VLM (si PDF scanné)
|
|
↓
|
|
5. NER (EDS-Pseudo ou CamemBERT)
|
|
↓
|
|
6. Extraction Trackare
|
|
↓
|
|
7. Détection contextuelle
|
|
↓
|
|
8. Consolidation globale
|
|
↓
|
|
9. Rescan sélectif
|
|
↓
|
|
10. Redaction PDF (vector ou raster)
|
|
↓
|
|
11. Génération audit.jsonl
|
|
↓
|
|
12. Validation post-anonymisation (à implémenter)
|
|
↓
|
|
13. Certificat de conformité (à implémenter)
|
|
```
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
Le système actuel est **très sophistiqué** avec 5 couches de détection complémentaires. Les principaux axes d'amélioration sont :
|
|
|
|
1. **Métriques de qualité** : Créer un dataset annoté et mesurer Précision/Rappel/F1
|
|
2. **Validation VLM** : Réduire les hallucinations via validation croisée
|
|
3. **Optimisation GPU** : Accélérer NER et vérifier support GPU Ollama
|
|
4. **Tests de régression** : Suite automatique pour éviter les régressions
|
|
5. **Validation post-anonymisation** : Scanner de fuite automatique
|
|
|
|
La spécification mise à jour reflète maintenant cette architecture réelle.
|