Retour au catalogue
DéveloppementPerso

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.

WhisperTranscriptionAudio
Chemin
~/.claude/skills/whisper-transcription-fiable
Modifié
21 août 2026 à 13:00

Skill 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 transcriptCauseÀ 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éosMarqueurs 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 foisDécodage glouton qui s'enferme, PROPAGÉ de fenêtre en fenêtre par condition_on_previous_text=TruePeut 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 muettesRe-décodages de secours à température élevée sur du silenceDensité 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 silencieuseDé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

  1. 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.
  2. 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.
  3. Le garde-fou interne est neutralisé : une fenêtre « silencieuse » n'est écartée que si no_speech_prob > 0.6 ET avg_logprob < -1.0. Or les hallucinations sont du texte fluide et « confiant » (logprob élevé) → conservées. Mesuré : des « Sous-titrage… » avec no_speech_prob ≈ 1e-10.
  4. 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 :

  1. 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.
  2. 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.
  3. 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. Un compression_ratio > 20 signe une boucle ; temperature élevée signe un re-décodage de secours.
  4. 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=True seul n'active AUCUN anti-hallucination. C'est le prérequis de hallucination_silence_threshold (défaut None) ; sans ce second paramètre on paie le surcoût pour rien.
  • hallucination_silence_threshold coû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_timestamps piloté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 un vad_filter inté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) :

  1. 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).
  2. Transcrire chaque fenêtre avec chaque config candidate (baseline = paramètres actuels ; une config par levier isolé ; la combinaison).
  3. 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.
  4. 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_threshold disponible mais gated sur word_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é).