whisper-transcription-fiable
Pièges connus et corrections MESURÉES pour toute application utilisant Whisper (openai-whisper, mlx-whisper, faster-whisper, whisper.cpp) — hallucinations sur les silences, paramètres de décodage sûrs, filtre anti-hallucination à liste fermée, méthode de validation A/B avec invariant anti-perte. Utiliser ce skill dès qu'un projet conçoit, code, débogue, audite ou porte de la transcription automatique avec Whisper, ET dès qu'un transcript présente des symptômes — texte répété en boucle (« Non. Non. Non. »), boilerplate de sous-titres (« Sous-titrage Société Radio-Canada », « Thanks for watching », « Amara.org »), caractères exotiques (々, 如果你), texte inventé sur des silences — même si l'utilisateur ne prononce jamais les mots « Whisper » ou « hallucination ». S'applique aussi au choix des paramètres AVANT d'écrire la première ligne de code d'une app de transcription/dictée/notes de réunion.
~/.claude/skills/whisper-transcription-fiableSkill local
Skill purement local : aucune source amont n'est déclarée dans le frontmatter. Les modifications vivent uniquement sur ton disque.
Whisper — transcription fiable
Savoir issu d'un chantier réel (app macOS de comptes-rendus de réunion, mlx-whisper large-v3-turbo) : diagnostic sur réunions réelles de 2 h 30, corrections validées par A/B mesuré, release gatée. Tous les chiffres sont datés du 2026-08-21 et vérifiés sur mlx-whisper 0.4.3 / whisper large-v3-turbo — re-vérifier sur les versions ultérieures avant de les citer. Détail des mesures : references/mesures-2026-08-21.md (à lire avant de chiffrer une recommandation).
1. Reconnaître les symptômes
| Symptôme dans le transcript | Cause | À savoir |
|---|---|---|
| « Sous-titrage Société Radio-Canada », « Sous-titrage ST' 501 », « Merci d'avoir regardé », « Thanks for watching », « Amara.org » | Hallucination sur silence : le décodeur a appris ces crédits de sous-titres sur des millions de fins de vidéos | Marqueurs canoniques par langue ; apparaissent sur toute fenêtre de 30 s sans parole |
| Boucles : « Non. Non. Non. », « D'accord. » ×10, un n-gram répété 30 fois | Décodage glouton qui s'enferme, PROPAGÉ de fenêtre en fenêtre par condition_on_previous_text=True | Peut MANGER de la vraie parole : la fenêtre entière est remplie par la boucle |
| Caractères CJK isolés ou en rafale (々々々, 如果你如果你) sur des plages quasi muettes | Re-décodages de secours à température élevée sur du silence | Densité typique < 0,3 caractère/s (parole réelle : ~7-8 c/s) |
| Un mot unique étalé sur 15-60 s (timestamps « smear ») | Frontières de segments collées aux fenêtres en zone silencieuse | Dégrade l'attribution des locuteurs si diarisation séparée |
Propriété importante : le contenu des hallucinations n'est PAS déterministe d'un run à l'autre (々 un jour, 如果你 le lendemain, Radio-Canada le surlendemain aux mêmes endroits) — les zones (silences) sont stables. Conséquence : filtrer par CLASSES fermées, jamais par motif de contenu précis.
2. Les quatre causes racines
- Whisper reçoit les silences (pas de VAD par défaut) : face à 30 s sans parole, le décodeur invente plutôt que de se taire.
condition_on_previous_text=True(défaut) : le texte de la fenêtre précédente amorce la suivante — une boucle ou un boilerplate né dans une fenêtre contamine les suivantes.- Le garde-fou interne est neutralisé : une fenêtre « silencieuse » n'est écartée que si
no_speech_prob > 0.6ETavg_logprob < -1.0. Or les hallucinations sont du texte fluide et « confiant » (logprob élevé) → conservées. Mesuré : des « Sous-titrage… » avecno_speech_prob ≈ 1e-10. - Selon l'implémentation, décodage glouton sans beam search (cas mlx-whisper — vérifié dans la source :
NotImplementedError), plus sujet aux boucles que le beam 5 d'openai-whisper.
3. Réglages sûrs dès la conception
Pour tout enregistrement long (réunion, dictée, interview) comportant des silences :
condition_on_previous_text=False. Coupe la propagation des boucles. Coût : NUL — mesuré 27 à 48 % PLUS RAPIDE (prompts plus courts, moins de re-décodages), et récupère de la vraie parole que les boucles écrasaient. Contrepartie : légère variabilité de style/ponctuation entre fenêtres de 30 s — sans effet si le transcript alimente un LLM ensuite. C'est le réglage n° 1, à poser par défaut.- Filtre anti-hallucination à liste fermée, sur les segments BRUTS (avant toute fusion/alignement) : (a) boilerplate de sous-titres — liste fermée de sous-chaînes par langue cible (
"sous-titrage","sous-titres réalisés","amara.org","merci d'avoir regardé","abonnez-vous","thanks for watching","subtitles by"), texte normalisé (minuscules, espaces réduits, apostrophes typographiques → ASCII, sinon « d'avoir » ne matche jamais) ; (b) segments non latins quasi purs pour une app fr/en — ≥ 50 % de caractères CJK parmi les caractères signifiants OU rafale de ≥ 3 CJK collés, minimum 2 (un idéogramme isolé au milieu de vraie parole est conservé). Faux positifs ≈ 0 par construction. - Conserver et logguer les métriques par segment (
no_speech_prob,avg_logprob,compression_ratio,temperature) au moins pour les segments rejetés/suspects, avec une ligne par rejet + un résumé. Sans elles, tout futur incident redevient une session d'analyse au lieu de 2 minutes de lecture de log. Uncompression_ratio> 20 signe une boucle ;temperatureélevée signe un re-décodage de secours. - Verrouiller par des tests : garde anti-régression sur les kwargs de l'appel (le défaut de la lib peut changer), fixtures tirées de VRAIS transcripts pollués (filtrés ET conservés — les cas « conservés » protègent la vraie parole), test que le schéma de sortie est inchangé.
4. Les pièges — ne pas faire, avec le pourquoi
- JAMAIS de filtre textuel générique de répétitions. La parole spontanée répète réellement : « très probablement très probablement », « je me suis dit je me suis dit », « D'accord. D'accord. » en backchannel — faux positifs PROUVÉS sur données réelles. Le remède aux boucles est au décodage (§3.1), pas dans un regex destructeur.
word_timestamps=Trueseul n'active AUCUN anti-hallucination. C'est le prérequis dehallucination_silence_threshold(défautNone) ; sans ce second paramètre on paie le surcoût pour rien.hallucination_silence_thresholdcoûte très cher : ×2 à ×2,8 mesuré en zone parlée, ×8 à ×16 en zone silencieuse (le re-seek rampe dans les silences). Quasi parfait sur les suspects, mais à réserver aux cas où ce budget est acceptable — mesurer d'abord (§5), et question ouverte : possible sur-saut de parole faible en zone silencieuse (−29 % de texte propre observé vs décodage simple).- VAD ou
clip_timestampspilotés par une diarisation : risque de perte SILENCIEUSE. Toute parole que le détecteur rate n'est jamais transcrite (indétectable après coup), et les bruits de bouche étiquetés « parole » restent transcrits — la correction ne corrige pas là où on l'attend. faster-whisper a unvad_filterintégré (Silero) : option légitime, mais à valider avec l'invariant anti-perte (§5), jamais à l'aveugle. - Plafonner la température (ex. cascade limitée à 0,4) supprime des tokens exotiques mais retire la porte de sortie prévue des boucles (le fallback haute température se déclenche sur échec de
compression_ratio). Trade-off réel : uniquement sur mesure A/B. - Ne jamais comparer des durées entre deux runs à conditions différentes. Variance thermique documentée jusqu'à ×2 sur Apple Silicon ; mesuré : +17 % sur une étape TÉMOIN au code inchangé entre deux runs du même fichier. Toute conclusion de performance exige soit un A/B à conditions égales, soit une décomposition par étape témoin.
- Ne pas incrémenter à l'aveugle un chiffre hérité (calibrations de progression, tailles, ratios) après modification des paramètres : re-mesurer ou retirer.
5. Méthode de validation — obligatoire avant d'adopter un réglage
Ne jamais adopter un paramètre de transcription sur la foi de la littérature : mesurer sur SES données. Protocole éprouvé (script prêt : scripts/ab_hallucinations.py, à adapter à l'implémentation) :
- Choisir 2 fenêtres d'un enregistrement RÉEL : la plus polluée (boucles/CJK) et une zone silencieuse (début de réunion typiquement).
- Transcrire chaque fenêtre avec chaque config candidate (baseline = paramètres actuels ; une config par levier isolé ; la combinaison).
- Comparer trois choses : suspects (classes fermées du §3.2), durée de transcription, et l'invariant anti-perte : le volume de texte HORS segments suspects doit rester stable (±2-3 %) entre configs — c'est la preuve qu'on supprime du déchet, pas de la parole. Attention : la « couverture temporelle » n'est PAS un bon proxy (les timestamps se resserrent avec certains réglages) ; comparer les caractères, et échantillonner les zones divergentes pour lecture humaine.
- Décider par matrice : si le levier gratuit suffit (souvent le cas), ne pas payer le levier cher ; consigner le levier écarté AVEC ses chiffres (il redevient un plan B documenté, pas une idée perdue).
6. Spécificités par implémentation
- mlx-whisper (Apple Silicon) : greedy uniquement (beam non implémenté — vérifié 0.4.3) ;
hallucination_silence_thresholddisponible mais gated surword_timestamps=True; les segments retournés portent bien les métriques (§3.3). - openai-whisper : mêmes paramètres ; le CLI utilise beam 5 par défaut à T=0 (boucles un peu plus rares, hallucinations sur silence identiques).
- faster-whisper : ajoute
vad_filter=True(Silero intégré) — alternative structurelle au filtre aval, à valider avec l'invariant anti-perte ; paramètres de décodage homologues. - whisper.cpp : options équivalentes (
--no-context≈ condition=False) ; vérifier la version, l'écosystème bouge vite.
Avant de citer un comportement précis d'une lib : vérifier dans la source de la version épinglée du projet (une signature de fonction se lit en 30 secondes et évite de propager un fait périmé).
