Raul59209/ParCanVox

0

stars

24

commits

Python

primary language

Aug 16, 2026

updated

README

# STT Benchmark — Guide de build et d'utilisation de WhisperX

Benchmark comparant plusieurs modèles STT (WhisperX, faster-whisper, Voxtral,
Canary, Scaleway Whisper API, Kyutai/Moshi) sur des audios médicaux en
français (majoritairement ORL — consultations et lettres dictées).

## Arborescence du dépôt

```
containers/
  base/                 # image de base partagée — normalizer.py, metrics.py, données médicaments
    Dockerfile
    src/
      normalizer.py      # normalisation du texte (le scoring WER/CER en dépend)
      metrics.py          # WER/CER, extraction d'entités, détection des erreurs critiques
      correct_transcriptions.py  # correction post-traitement via Qwen (API Scaleway)
      tag_consultation_type.py
      refresh_ground_truth_normalized.py
  whisperx/
    Dockerfile
    notebook_whisperx_chunked.py   # script principal (découpage par silence)
    notebook_whisperx.py           # variante audio entier (sans découpage)
dataset/
  test_set_frozen.json   # jeu d'évaluation figé — 18 segments, vérité terrain + métadonnées
audio/                    # fichiers source .m4a
results/                  # CSV de résultats, un par run
```

## Build

**Toujours reconstruire l'image après avoir modifié un fichier `.py`** —
`docker compose run` utilise ce qui est intégré dans l'image, pas le
système de fichiers de l'hôte. C'est de loin la cause la plus fréquente de
"pourquoi mon changement n'a rien fait" dans ce projet.

```bash
docker build -t stt-benchmark-base -f containers/base/Dockerfile containers/base/
docker compose build whisperx
```

`dataset/`, `audio/` et `results/` sont montés en volume, pas intégrés à
l'image — modifier ces fichiers sur l'hôte prend effet immédiatement, sans
rebuild.

## Lancer WhisperX

```bash
docker compose run --rm whisperx python3 notebook_whisperx_chunked.py [options]
```

| Option | Défaut | Notes |
|---|---|---|
| `--prompt-variant` | `generic` | Voir tableau ci-dessous |
| `--condition-on-previous-text` | désactivé | Conditionnement inter-fenêtres de Whisper. Désactivé = évite les boucles de répétition hallucinées, mais limite la portée d'`initial_prompt` à ~30s par chunk. **Actuellement forcé à désactivé dans le code, quelle que soit cette option** — voir le commentaire près de `load_whisperx_model()` si besoin de le réactiver. |
| `--chunk-max-s` | 180 | Longueur des chunks (découpage par silence) |

### Variantes de prompt (`--prompt-variant`)

| Variante | Description |
|---|---|
| `none` | Vraie base de référence, aucun `initial_prompt` |
| `generic` | Prompt générique multi-spécialités d'origine (prose naturelle) |
| `generic_v2` | + `antécédents`, explication des marqueurs de ponctuation dictés |
| `generic_v3` | **Actuellement le meilleur.** + termes ORL à forte valeur, fusionnés en un seul prompt |
| `orl_dictee` / `orl_assistant` / `orl_cro` | Listes de termes ORL fournies par le boss (séparées par virgules) — **moins performantes que `generic`**, à ne pas utiliser |
| `orl_dictee_v2` / `orl_assistant_v2` / `orl_cro_v2` | Mêmes termes, reformulés en prose — mieux, mais toujours en dessous de `generic_v3` |
| `auto` | Deux passes : transcription brouillon → Qwen classe le type de contenu → nouvelle transcription avec le prompt spécialisé correspondant. **Moins performant que `generic_v3`** (coût x2, le classificateur se trompe sur ~40% des segments de dictée) — non recommandé actuellement |

**À retenir : utiliser `generic_v3`.** Les approches par routage/prompt
spécialisé ont systématiquement perdu face à un seul prompt général bien
écrit, sur tous les tests effectués jusqu'ici.

Chaque variante écrit dans son propre fichier
`results_whisperx_chunked_prompt_<variante>.csv` — rien n'est écrasé.

## Étape de correction (`correct_transcriptions.py`)

Post-traite un CSV de résultats avec Qwen (Scaleway Generative APIs) pour
corriger noms de médicaments/dosages. Nécessite la variable d'environnement
`SCW_API_KEY`.

```bash
python3 correct_transcriptions.py results/results_whisperx_chunked_prompt_generic_v3.csv [--use-orl-lexicon]
```

Produit **deux variantes séparées pour la sécurité**, par segment :
- `hypothesis_llm_corrected` — fiable, uniquement basé sur l'audio. Un nom
  de médicament non reconnu est marqué `[DRUG UNCLEAR: ...]`, jamais deviné.
- `hypothesis_llm_corrected_with_est` — idem + estimations de dosage par le
  LLM, marquées en ligne `[ESTIMATED: ...]`. L'IDENTITÉ d'un médicament
  n'est jamais estimée, seuls les chiffres de dosage le sont (un mauvais
  médicament est catégoriquement plus grave qu'un mauvais chiffre — c'est
  imposé dans le code, pas seulement dans le prompt).

Le WER n'est pas un signal fiable à cette étape — une transcription qui
signale honnêtement "incertain" obtient un moins bon score qu'une
transcription qui devine, même quand deviner est pire à tous points de vue
cliniquement pertinents. Suivre le nombre d'erreurs critiques en parallèle
du WER ici.

## Pièges connus

- **Incompatibilité driver NVML** (`Driver/library version mismatch`) →
  `sudo reboot`.
- **Disque Docker plein** → vérifier `df -h`, probablement
  `/var/lib/docker` / `/var/lib/containerd` sur un petit disque racine ;
  déplacer `data-root`/`root` vers le montage `/scratch` plus grand, puis
  rebuild.
- **`seg_0010` est exclu des métriques agrégées** — sa vérité terrain est
  un résumé narratif alors que l'audio est un dialogue verbatim ; pas une
  comparaison juste pour aucun modèle.
- **`seg_0013` est absent** de tous les runs jusqu'à présent — jamais
  résolu, cause inconnue.
- La vérité terrain du dataset contenait des **boucles d'hallucination non
  détectées** (trouvées dans `seg_0003`, `seg_0010`, et plusieurs autres à
  l'origine). Corrigé dans la version actuelle de `test_set_frozen.json` —
  si le dataset est un jour régénéré depuis `segments_raw.json` /
  `correction_worksheet.tsv` / `.progress.json`, **ces fichiers sources ont
  aussi été corrigés** (2ᵉ passe) pour éviter de réintroduire le bug.
- `consultation_type` (`consultation` vs `dictation`) est étiqueté par
  segment dans le dataset — chaque run de notebook rapporte les deux WER
  séparément.

## État actuel

Meilleure configuration actuelle : WhisperX large-v3, découpage par
silence, prompt `generic_v3`, `condition_on_previous_text=False`. L'étape
de correction Qwen est disponible mais pas encore validée comme une
amélioration nette du WER — utile pour la visibilité des erreurs critiques
quoi qu'il en soit.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Raul59209

24 commits

Raul59209/ParCanVox

0

stars

24

commits

Python

primary language

Aug 16, 2026

updated

README

# STT Benchmark — Guide de build et d'utilisation de WhisperX

Benchmark comparant plusieurs modèles STT (WhisperX, faster-whisper, Voxtral,
Canary, Scaleway Whisper API, Kyutai/Moshi) sur des audios médicaux en
français (majoritairement ORL — consultations et lettres dictées).

## Arborescence du dépôt

```
containers/
  base/                 # image de base partagée — normalizer.py, metrics.py, données médicaments
    Dockerfile
    src/
      normalizer.py      # normalisation du texte (le scoring WER/CER en dépend)
      metrics.py          # WER/CER, extraction d'entités, détection des erreurs critiques
      correct_transcriptions.py  # correction post-traitement via Qwen (API Scaleway)
      tag_consultation_type.py
      refresh_ground_truth_normalized.py
  whisperx/
    Dockerfile
    notebook_whisperx_chunked.py   # script principal (découpage par silence)
    notebook_whisperx.py           # variante audio entier (sans découpage)
dataset/
  test_set_frozen.json   # jeu d'évaluation figé — 18 segments, vérité terrain + métadonnées
audio/                    # fichiers source .m4a
results/                  # CSV de résultats, un par run
```

## Build

**Toujours reconstruire l'image après avoir modifié un fichier `.py`** —
`docker compose run` utilise ce qui est intégré dans l'image, pas le
système de fichiers de l'hôte. C'est de loin la cause la plus fréquente de
"pourquoi mon changement n'a rien fait" dans ce projet.

```bash
docker build -t stt-benchmark-base -f containers/base/Dockerfile containers/base/
docker compose build whisperx
```

`dataset/`, `audio/` et `results/` sont montés en volume, pas intégrés à
l'image — modifier ces fichiers sur l'hôte prend effet immédiatement, sans
rebuild.

## Lancer WhisperX

```bash
docker compose run --rm whisperx python3 notebook_whisperx_chunked.py [options]
```

| Option | Défaut | Notes |
|---|---|---|
| `--prompt-variant` | `generic` | Voir tableau ci-dessous |
| `--condition-on-previous-text` | désactivé | Conditionnement inter-fenêtres de Whisper. Désactivé = évite les boucles de répétition hallucinées, mais limite la portée d'`initial_prompt` à ~30s par chunk. **Actuellement forcé à désactivé dans le code, quelle que soit cette option** — voir le commentaire près de `load_whisperx_model()` si besoin de le réactiver. |
| `--chunk-max-s` | 180 | Longueur des chunks (découpage par silence) |

### Variantes de prompt (`--prompt-variant`)

| Variante | Description |
|---|---|
| `none` | Vraie base de référence, aucun `initial_prompt` |
| `generic` | Prompt générique multi-spécialités d'origine (prose naturelle) |
| `generic_v2` | + `antécédents`, explication des marqueurs de ponctuation dictés |
| `generic_v3` | **Actuellement le meilleur.** + termes ORL à forte valeur, fusionnés en un seul prompt |
| `orl_dictee` / `orl_assistant` / `orl_cro` | Listes de termes ORL fournies par le boss (séparées par virgules) — **moins performantes que `generic`**, à ne pas utiliser |
| `orl_dictee_v2` / `orl_assistant_v2` / `orl_cro_v2` | Mêmes termes, reformulés en prose — mieux, mais toujours en dessous de `generic_v3` |
| `auto` | Deux passes : transcription brouillon → Qwen classe le type de contenu → nouvelle transcription avec le prompt spécialisé correspondant. **Moins performant que `generic_v3`** (coût x2, le classificateur se trompe sur ~40% des segments de dictée) — non recommandé actuellement |

**À retenir : utiliser `generic_v3`.** Les approches par routage/prompt
spécialisé ont systématiquement perdu face à un seul prompt général bien
écrit, sur tous les tests effectués jusqu'ici.

Chaque variante écrit dans son propre fichier
`results_whisperx_chunked_prompt_<variante>.csv` — rien n'est écrasé.

## Étape de correction (`correct_transcriptions.py`)

Post-traite un CSV de résultats avec Qwen (Scaleway Generative APIs) pour
corriger noms de médicaments/dosages. Nécessite la variable d'environnement
`SCW_API_KEY`.

```bash
python3 correct_transcriptions.py results/results_whisperx_chunked_prompt_generic_v3.csv [--use-orl-lexicon]
```

Produit **deux variantes séparées pour la sécurité**, par segment :
- `hypothesis_llm_corrected` — fiable, uniquement basé sur l'audio. Un nom
  de médicament non reconnu est marqué `[DRUG UNCLEAR: ...]`, jamais deviné.
- `hypothesis_llm_corrected_with_est` — idem + estimations de dosage par le
  LLM, marquées en ligne `[ESTIMATED: ...]`. L'IDENTITÉ d'un médicament
  n'est jamais estimée, seuls les chiffres de dosage le sont (un mauvais
  médicament est catégoriquement plus grave qu'un mauvais chiffre — c'est
  imposé dans le code, pas seulement dans le prompt).

Le WER n'est pas un signal fiable à cette étape — une transcription qui
signale honnêtement "incertain" obtient un moins bon score qu'une
transcription qui devine, même quand deviner est pire à tous points de vue
cliniquement pertinents. Suivre le nombre d'erreurs critiques en parallèle
du WER ici.

## Pièges connus

- **Incompatibilité driver NVML** (`Driver/library version mismatch`) →
  `sudo reboot`.
- **Disque Docker plein** → vérifier `df -h`, probablement
  `/var/lib/docker` / `/var/lib/containerd` sur un petit disque racine ;
  déplacer `data-root`/`root` vers le montage `/scratch` plus grand, puis
  rebuild.
- **`seg_0010` est exclu des métriques agrégées** — sa vérité terrain est
  un résumé narratif alors que l'audio est un dialogue verbatim ; pas une
  comparaison juste pour aucun modèle.
- **`seg_0013` est absent** de tous les runs jusqu'à présent — jamais
  résolu, cause inconnue.
- La vérité terrain du dataset contenait des **boucles d'hallucination non
  détectées** (trouvées dans `seg_0003`, `seg_0010`, et plusieurs autres à
  l'origine). Corrigé dans la version actuelle de `test_set_frozen.json` —
  si le dataset est un jour régénéré depuis `segments_raw.json` /
  `correction_worksheet.tsv` / `.progress.json`, **ces fichiers sources ont
  aussi été corrigés** (2ᵉ passe) pour éviter de réintroduire le bug.
- `consultation_type` (`consultation` vs `dictation`) est étiqueté par
  segment dans le dataset — chaque run de notebook rapporte les deux WER
  séparément.

## État actuel

Meilleure configuration actuelle : WhisperX large-v3, découpage par
silence, prompt `generic_v3`, `condition_on_previous_text=False`. L'étape
de correction Qwen est disponible mais pas encore validée comme une
amélioration nette du WER — utile pour la visibilité des erreurs critiques
quoi qu'il en soit.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Raul59209

24 commits

Languages

Python

97.7%

Shell

2.3%