블로그로 돌아가기

Whisper 환각: 같은 줄을 반복하고 없는 말을 지어내는 이유

Whisper 환각을 정리했어요: 무음 구간에 “Thank you for watching”을 쓰거나 한 줄을 계속 반복하는 이유, 해결책마다 드는 비용, 완성된 SRT에서 환각을 찾아내는 방법까지 다뤄요.

2026년 10월 3일
Sablate Team

Whisper 환각(hallucination)은 아무도 말하지 않았는데 모델이 써 넣는 텍스트예요. 무음 구간에 나오는 “Thank you for watching”, 자막 제작자 크레딧, 1분 동안 반복되는 한 줄이 그 예예요. 설정 두 가지로 대부분 막을 수 있어요. 음성이 아닌 오디오를 걸러내고, 모델이 자기 출력을 문맥으로 쓰지 못하게 하는 거예요. 그런데 표준 Whisper 도구는 이 둘을 모두 꺼 둔 채로 나와요.

이 글에서는 두 가지 유형을 나란히 비교하고, 각각이 생기는 이유, 우리 앱에서 그중 하나가 나타났을 때 직접 측정한 결과, 해결책마다 드는 비용, openai-whisper와 faster-whisper와 whisper.cpp의 설정, 그리고 완성된 자막 파일에서 살아남은 환각을 찾는 방법을 차례로 다뤄요.

Whisper 환각, 한눈에 비교하기

유령 문구반복 루프누락된 발화
눈에 보이는 증상“Thank you for watching”, “Thanks for watching”, 자막 제작자 크레딧한 줄이 계속 반복됨, 대개 문장 부호 없이전사본에서 문장이 빠짐
나타나는 곳무음, 음악, 박수, 실내 소음어디서든 나타나고, 한번 시작되면 이어짐조용한 도입부, 짧은 감탄사
원인음성이 아닌 오디오가 디코더에 도달함구간마다 이전 출력을 프롬프트로 삼아 디코딩함특정 모델이 그 오디오를 처리하는 방식
첫 번째 해결책음성 구간 검출 필터(VAD)이전 텍스트를 문맥으로 쓰지 않기같은 클립에 다른 모델 써 보기
자막에서 보이는 모습쉬는 구간에 끼어든 큐 하나한 줄에 갇힌 큐, 또는 문장 구분이 없는 큐줄이 있어야 할 자리에 생긴 공백

세 번째 유형인 누락된 발화는 반대쪽 실패예요. 없는 텍스트를 지어낸 게 아니라, 있던 발화를 건너뛴 경우예요. 원인과 해결책이 달라서 Whisper 모델 비교에서 따로 다뤄요. 이 글의 나머지는 모두 앞의 두 가지에 관한 이야기예요.

Whisper는 왜 아무도 말하지 않은 텍스트를 쓸까요?

Whisper는 오디오를 30초 단위 구간으로 나눠 처리하고, 디코더가 구간마다 텍스트를 써요. 음성이 없는지 확인하는 단계가 있긴 하지만, 이건 확률에 적용하는 임계값일 뿐이라 음악, 박수, 실내 소음이 담긴 구간이 이 검사를 자주 통과해서 단어로 바뀌어 나와요. 어떤 단어가 나오는지를 보면 원인이 드러나요. Whisper는 웹에서 모은 오디오 68만 시간을 전사본과 짝지어 학습했어요. 전사할 게 없을 때 나오는 문구는 대개 그 전사본에 흔했던 마무리 인사와 자막 제작자 크레딧이 그대로 튀어나온 것으로 설명해요.

수치가 결코 작지 않아요. 2025년 연구에서 AGH University of Kraków 연구진이 소음, 음악, 환경음 같은 음성이 아닌 오디오에 large-v3를 301,317번 돌렸어요. 그중 40.3%에서 텍스트가 나왔고, 그 환각 중 9.1%는 반복 루프였어요. 가장 자주 나온 출력은 환각의 24.76%를 차지한 “thank you”와 10.32%를 차지한 “thanks for watching”이었어요.

실제 음성에서는 발생률이 훨씬 낮지만 피해는 더 커요. 2024년에 발표된 Careless Whisper 연구에서는 전사의 약 1%에 오디오에 없던 구절이나 문장 전체가 들어 있었고, 그중 38%에는 폭력 조장, 없는 연관성 지어내기, 거짓 권위 암시 같은 명백한 해악이 담겨 있었어요. 이런 일은 말 사이에 침묵이 긴 화자에게 불균형하게 많이 일어났어요.

자막에서 치르는 대가는 구체적이에요. 환각으로 생긴 문구는 쉬는 구간 위에 형식을 완벽하게 갖춘 큐로 나타나고, 시청자는 그걸 대사로 읽어요.

한 줄의 오류가 마흔 줄로 번지는 이유

반복 루프는 원인이 달라요. 기본 설정에서 Whisper는 각 구간을 디코딩할 때 자기 자신의 이전 출력을 프롬프트로 써요. 그래서 문맥이 구간을 넘어 전달되고, 이름의 철자가 일정하게 유지되고 문장이 이어지는 것도 그 덕분이에요. 그런데 같은 기능 때문에 잘못된 구간 하나가 다음 구간을 오염시켜요. 디코더가 한 줄을 두 번 쓰면 다음 구간은 그 줄을 미리 입력받은 상태로 시작해서 그 줄을 또 써요. faster-whisper 문서의 condition_on_previous_text 설명은 이 점을 분명하게 말해요. 이 옵션을 끄면 모델이 “반복 루프나 타임스탬프가 어긋나는 현상 같은 실패 루프에 갇힐 가능성이 줄어든다”고요.

출시 전에 우리 앱에서 이 문제를 겪어서 직접 측정해 봤어요. 모델 비교에서 쓴 것과 같은 101초짜리 터키어 클립을 float16을 쓰는 RTX 3060 Ti에서 돌리면서 그 옵션 하나만 바꿨어요.

large-v3, 이전 텍스트 문맥 사용large-v3, 이전 텍스트 문맥 미사용medium, 이전 텍스트 문맥 사용medium, 이전 텍스트 문맥 미사용
전사본의 문장 종결 부호 수0304730
반복된 문구(6-gram)9000

기본 설정은 large-v3를 망가뜨렸고 medium에는 아무 영향이 없었어요. 이전 텍스트를 문맥으로 쓰는 상태에서 더 큰 모델은 문장 종결 부호를 하나도 쓰지 않았고 반복 문구를 아홉 개 만들었어요. 같은 오디오를 같은 설정으로 돌린 더 작은 모델은 멀쩡했어요. 한 번은 큐 하나에 이런 내용이 들어갔는데, 오디오로는 0.12초 길이였어요:

[58.26 -> 58.38]  bir level oldu bir level oldu bir level oldu bir level oldu
                  bir level oldu bir level oldu bir level oldu bir level oldu

빠진 문장 부호도 반복만큼 중요해요. 자막 도구는 문장 경계에서 전사본을 큐로 나누는데, 마침표가 없는 전사본은 나눌 기준이 없어요. 그래서 거대한 큐가 나오거나, 줄 분할기가 허술하면 한 단어짜리 큐가 나와요. 이전 텍스트를 문맥으로 쓰는 걸 끄니 모델은 그대로인데도 문장 부호가 돌아왔어요.

클립 하나는 벤치마크가 아니에요. 그래도 두 가지는 분명히 보여 줘요. 모델 크기는 보호막이 되지 못한다는 것, 그리고 가장 싼 해결책은 설정 하나라는 것이에요.

이전 텍스트 문맥을 끄면 대가가 있을까요?

네, 있어요. 환각을 막는 해결책에는 모두 대가가 따르고, 적용하기 전에 알아 둘 만해요:

  1. 구간 간 일관성. 이전 텍스트를 문맥으로 쓰지 않으면 철자와 문장 부호 스타일이 구간마다 달라질 수 있어요. 문서도 이 옵션을 끄면 “구간마다 텍스트가 일관되지 않게 될 수 있다”고 경고해요. 실제로는 긴 파일에서 이름이 두 가지로 표기되는 정도예요.
  2. initial prompt(초기 프롬프트)는 첫 구간에서 끝나요. 도구들은 이름, 브랜드, 전문 용어 같은 어휘 목록을 Whisper에 initial prompt로 넘기는데, 이전 텍스트 문맥을 끄면 그 목록은 첫 구간에만 영향을 줘요. openai-whisper의 --carry_initial_prompt True와 whisper.cpp의 --carry-initial-prompt는 이 프롬프트를 모든 구간에 다시 보내고, faster-whisper에서는 hotwords 옵션이 그 역할을 해요.
  3. 반복 페널티는 진짜 반복까지 지워요. repetition_penalty와 no_repeat_ngram_size는 루프를 억누를 수 있지만, 루프와 일부러 같은 말을 되풀이하는 화자를 구별하지 못해요. 우리 테스트 클립에도 딱 그런 부분이 있어요. 같은 문장을 일부러 연달아 두 번 말한 거예요. 그걸 지워 버리는 페널티는 대사를 삭제하는 거예요.
  4. 음성 구간 검출 필터는 작은 목소리를 잘라 낼 수 있어요. VAD는 Whisper가 듣기 전에 무엇을 음성으로 볼지 정하는데, 조용하게 시작하는 부분이나 전화 통화처럼 대역이 제한된 오디오에서 단어를 놓쳐요. AutoSubs는 바로 그런 경우를 위해 2026년 9월에 무음 건너뛰기를 끄는 스위치를 추가했어요.
  5. hallucination_silence_threshold는 단어 타임스탬프가 필요하고, 만능 해결책도 아니에요. 환각이 의심되는 부분 주변의 긴 무음을 건너뛰는 옵션이에요. 우리 클립에서는 측정 가능한 차이가 없었어요. 지어낸 줄이라고 의심했던 줄이 알고 보니 실제 음성이었거든요.

이 중 어느 것도 반복 루프의 대가에 비하면 아무것도 아니에요. 철자가 틀린 이름은 찾아 바꾸기 한 번이면 끝나지만, 반복 루프가 생긴 대목은 다시 전사해야 해요.

Whisper 환각을 막는 방법, 도구별로 정리

openai-whisperfaster-whisperwhisper.cppSablate
음성 구간 검출 필터내장 기능 없음vad_filter=True, 배치 파이프라인에서만 기본으로 켜짐Silero 모델과 함께 쓰는 --vad항상 켜짐
이전 텍스트를 문맥으로 쓰지 않기--condition_on_previous_text Falsecondition_on_previous_text=False-mc 0항상 꺼짐
환각이 의심되는 부분 뒤의 무음 건너뛰기--hallucination_silence_thresholdhallucination_silence_threshold해당 기능 없음아니요
모든 구간에 프롬프트 다시 보내기--carry_initial_prompt Truehotwords--carry-initial-prompt아니요
이 설정을 직접 바꿀 수 있음네네네아니요

마지막 세 행은 Sablate의 솔직한 약점이에요. 두 가지 해결책이 항상 켜져 있고, 이 설정 중 사용자에게 열려 있는 건 하나도 없어요. 필터를 조정하거나 프롬프트를 구간마다 이어 보내고 싶다면 오픈소스 도구가 그걸 할 수 있게 해 줘요.

인터뷰 파일을 openai-whisper로 처리한다면 이렇게 해요:

whisper talk.mp4 --model turbo --output_format srt --condition_on_previous_text False

openai-whisper에는 자체 음성 구간 검출 필터가 없어서, 이 명령어는 반복 루프만 고치고 유령 문구는 못 고쳐요. 유령 문구에 대한 부분적인 대응책은 --hallucination_silence_threshold와 --word_timestamps True를 함께 쓰는 거예요. whisper.cpp에는 두 가지 해결책이 다 있고, 명령줄 도구는 16비트 WAV를 요구해요:

ffmpeg -i talk.mp4 -ar 16000 -ac 1 -c:a pcm_s16le talk.wav
./build/bin/whisper-cli -m models/ggml-large-v3-turbo.bin -f talk.wav -osrt -mc 0 --vad -vm models/ggml-silero-v6.2.0.bin

-mc 0은 구간 사이에 텍스트 문맥을 전혀 유지하지 않는 옵션이고, Silero 모델은 저장소의 download-vad-model.sh 스크립트로 내려받아요. Python에서는 faster-whisper가 둘 다 인자로 받아요:

from faster_whisper import WhisperModel

model = WhisperModel("large-v3-turbo")
segments, info = model.transcribe("talk.mp4", vad_filter=True, condition_on_previous_text=False)

완성된 SRT에서 환각은 어떻게 찾을까요?

어떤 설정도 환각을 전부 없애 주지는 않으니, 파일을 확인하세요. 명령어 세 개면 살아남은 환각 대부분을 잡아낼 수 있어요. macOS와 Linux에서, 또는 Windows의 Git Bash나 WSL에서 실행하세요:

grep -n -i -E "thank(s| you) for watching|amara\.org|subtitles by" talk.srt
awk '{ sub(/\r$/, "") } NF && !/-->/ && !/^[0-9]+$/ { if ($0 == prev) print NR": "$0; prev = $0 }' talk.srt
perl -ne 'print "$.: $_" if /(\S+(?: \S+){1,4}) \1 \1/' talk.srt

첫 번째 명령어는 상투적인 마무리 인사와 자막 제작자 크레딧을 찾아요. 두 번째는 바로 앞 줄과 똑같은 자막 줄을 모두 출력하는데, 반복 루프가 큐로 잘리고 나면 보통 이런 모습이에요. 세 번째는 두 단어에서 다섯 단어 사이의 문구가 한 줄 안에서 세 번 반복되는 경우를 잡아요.

검색에 걸린 줄은 판결이 아니라 질문으로 대하세요. 사람들은 실제로 “thank you”라고 말하고, 실제로 같은 말을 되풀이해요. 큐를 오디오와 함께 재생해 보고 남길지 지울지 정하세요. 긴 침묵이나 음악 위에 나타나는 유령 문구는 어떤 패턴에도 걸리지 않아요. 그런 문구를 찾는 유일한 방법은 음성이 없는 구간 위에 놓인 큐를 직접 읽어 보는 것이에요.

상황별로 어떤 해결책을 쓸까요?

“강연, 인터뷰, 강의에 자막을 달고 있어요.” 음성 구간 검출 필터는 켜고, 이전 텍스트 문맥은 끄세요. 이 조합이 자막을 달 모든 음성에 맞는 합리적인 기본값이에요.

“녹음에 긴 무음이나 배경 음악이 깔려 있어요.” 이럴 때 필터가 가장 중요해요. 그래도 음악 위에 놓인 큐는 직접 읽어 보세요.

“전화 통화이거나 조용하고 대역이 제한된 녹음이에요.” 필터를 켜고 한 번, 끄고 한 번 돌려서 무엇이 빠졌는지 비교하세요. 조용히 말한 단어를 잘라 먹는 필터는 엉뚱하게 끼어든 “thank you”보다 나빠요.

“이름은 initial prompt에 의존해요.” 이전 텍스트 문맥은 끄고, 위에서 본 플래그로 프롬프트를 모든 구간에 다시 보내거나, 나중에 찾아 바꾸기로 이름을 고치세요.

“두 가지 해결책을 적용했는데도 줄이 반복돼요.” 반복 페널티를 쓰기 전에 같은 파일을 다른 모델로 돌려 보세요. 우리 클립은 large-v3에서는 루프가 생기고 medium에서는 생기지 않았어요. 그래도 no_repeat_ngram_size가 필요하다면, 그 옵션이 지웠을지 모르는 줄이 없는지 처음부터 끝까지 들어 보세요.

Sablate는 어떻게 처리할까요?

Sablate에서는 두 가지 해결책이 항상 켜져 있어요. 모델 앞단의 음성 구간 검출 필터가 있고, 이전 텍스트를 문맥으로 쓰지 않아요. Sablate는 내 컴퓨터에서 Whisper를 실행하고, 위의 측정 결과를 보고 이전 텍스트 문맥을 껐어요. 문장 부호가 없는 전사본을 한 단어짜리 큐로 쪼개던 줄 분할기도 고쳤어요.

알아 둘 결과가 두 가지 있어요. Defaults → Custom vocabulary 메뉴의 사용자 지정 어휘는 Whisper에 initial prompt로 전달돼요. 그래서 이전 텍스트 문맥을 끈 상태에서는 첫 구간, 대략 처음 30초 분량의 음성에만 영향을 줘요. 긴 녹음 뒤쪽에 나오는 이름은 편집기의 Find & replace 기능으로 고쳐야 하고, 이 기능은 한 번에 한 언어에서만 작동해요. 그리고 설정이 고정되어 있어서 조정할 수 있는 게 없어요. 어떤 녹음에 필터를 꺼야 한다면 위에서 소개한 오픈소스 도구 중 하나를 쓰세요.

편집기에서는 영상을 재생하면서 큐를 하나씩 넘겨 볼 수 있어요. 위 명령어에 걸린 줄을 판단하는 가장 빠른 방법이에요. 내보내기에서는 교정한 자막을 .srt, .vtt, 또는 자막을 구워 넣은 영상으로 만들어 줘요. Fast와 Balanced는 무료 요금제에서 실행되고, Accurate와 Best는 Pro에 포함돼요. Windows나 Mac용 Sablate를 다운로드해서 직접 찍은 영상으로 써 보세요.

요약

Whisper의 환각은 두 가지 방식으로 나타나요. 음성이 없는 오디오 위에 상투적인 문구를 쓰는 것, 그리고 잘못된 구간 하나의 출력이 다음 구간의 입력으로 들어가면서 루프에 빠지는 것이에요. 음성 구간 검출 필터가 첫 번째를 해결하고, condition_on_previous_text를 끄면 두 번째가 해결되는데, 표준 도구는 둘 다 꺼진 채로 나와요. 두 해결책 모두 루프로 치르는 대가에 비하면 비용이 거의 들지 않아요. 큰 모델이라고 더 안전한 모델은 아니에요. 그리고 무엇을 바꾸든, 다른 사람이 보기 전에 완성된 자막 파일을 먼저 읽어 보세요. 형식 자체를 점검하는 방법은 SRT 파일 만들기 가이드에 있어요.