| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- TTS
- RDBMS
- CLIP
- 힙정렬
- 소프트웨어 개발
- 에이전트
- LangGraph
- dementional reduction
- RNN
- 데이터 시각화
- Transformer
- ASR
- 캐글
- Python
- 기초
- 객체지향
- 데이터엔지니어
- UMAP
- SQL
- 정보처리기사
- 랭그래프
- python기초
- 생성형 인공지능
- python 기초
- 트랜스포머
- 머신러닝
- CNN
- 자연어처리
- 딥러닝
- 알고리즘
Archives
- Today
- Total
수달이네 기술 블로그
6. Whisper - ASR모델의 구조 본문
기초 구조
오디오 청크
↓
EnergyVAD (1차 필터)
↓ 발화 완성
float32 배열 → Whisper
├── 내장 VAD (2차 필터, 환각 방지)
├── Beam Search (beam_size=5, 최적 텍스트 탐색)
└── avg_logprob → exp() → confidence [0,1]
↓
TranscriptionResult { text, confidence, language }
↓
on_transcription 콜백
- whisper모델은 위와 같이 구성된다.
Log Probability(avg_logprob)
확률
- Whisper는 음성을 텍스트로 바꿀 때 내부적으로 음성이 단어에 매칭될 확률을 계산한다.
- 위의 단어를 예측할 때 토큰 하나씩 예측한다
결합 확률(Joint Probability)
Whisper는 음성을 듣고 토큰(단어/음절)을 하나씩 순서대로 예측한다
- “안” → “녕” → “하” → “세” → “요”
- 각 토큰을 예측할 때마다 “이 토큰이 맞을까?”를 계산함.
이 개념을 바탕으로 결합 확률을 계산하는데
$$ P("안녕하세요" 전체가 맞을 확률) = P("안") × P("녕"|"안") × P("하"|"안녕") × P("세"|"안녕하") × P("요"|"안녕하세") $$
- 위 수식에서 $P("녕"|"안")$일 경우, “안”이후, “녕”이 올 조건부 확률을 구한다.
이렇게 나올 확률을 직관적으로 살피면
"안" 맞을 확률 0.95 "녕" 맞을 확률 0.92 "하" 맞을 확률 0.90 "세" 맞을 확률 0.88 "요" 맞을 확률 0.91
- 위와 같이 하나하나 보면 높은 확률으로 보이지만.
전체 문장이 맞을 확률 = 0.95 × 0.92 × 0.90 × 0.88 × 0.91 = 0.620
- 결과 값은 꽤나 낮게 나타남을 확인할 수 있다.
만약, 세그멘테이션이 길어지거나, 확률이 높지 않을 경우 0으로 점차 수렴하여 언더플로우가 발생 0.0으로 처리한다.
로그 확률
위와 같은 문제를 해결하기 위해 곱셈 대신 로그 합을 이용한다.
$log(0.92 × 0.88 × ...) = log(0.92) + log(0.88) + ... = -0.083 + -0.128 + ...$
- 위와 같이 표현된다.(0~1사이값은 음수로 표현하므로)
- 따라서 아주 작은 수여도 언더플로우가 발생하지 않는다.
위처럼 변환해서 계산 처리하고, exp()함수를 통해 사람이 읽을 수 있도록 보여준다.
confidence = sum(
math.exp(seg.avg_logprob) * (seg.end - seg.start)
for seg in segments
) / total_duration
confidence = max(0.0, min(1.0, confidence)) # 0~1값 사이를 벗어나지 않도록 clamp
avg_logprob = -0.1 → exp(-0.1) = 0.905 → 신뢰도 90%
avg_logprob = -0.5 → exp(-0.5) = 0.607 → 신뢰도 61%
avg_logprob = -1.0 → exp(-1.0) = 0.368 → 신뢰도 37%
avg_logprob = -2.0 → exp(-2.0) = 0.135 → 신뢰도 14%
Beam Search vs Greedy
Greedy 디코딩 방식
매 순간 가장 높은 확률의 단어 하나만 선택하는 방식
입력: "안녕..."
→ 1번째 토큰: "안녕" (확률 0.9) ✓ 선택
→ 2번째 토큰: "하세요" (확률 0.8) ✓ 선택
→ 결과: "안녕하세요"
- 매순간 바로 다음 단어가 나올 확률이 가장 높은 단어 하나만 선택하는 방식
- 전체 문장 기준으로는 최손이 아닐 가능성이 높음
Beam Search(beam_size = n)
beam_size만큼 후보경로를 동시에 유지하며 디코딩하는 방식
시작
├── 경로 1: "안녕" (누적 점수: -0.1)
├── 경로 2: "아" (누적 점수: -0.3)
├── 경로 3: "네" (누적 점수: -0.5)
├── 경로 4: "저" (누적 점수: -0.6)
└── 경로 5: "그" (누적 점수: -0.7)
다음 스텝 (각 경로에서 확장 후 상위 5개만 유지)
├── 경로 1: "안녕하세요" (누적: -0.2)
├── 경로 2: "아 안녕" (누적: -0.4) ← 나중에 더 좋아질 수도
...
- 이후 최종적으로 누적 점수가 가장 높은 경로를 선택한다.
- beam_size가 크면 정확도는 높아지지만 속도는 줄어든다.
- 이런 방식으로 음성을 예측해 나간다.
VAD(faster-whisper)
처음에 VAD를 통해 한번 걸러준다.
- CPU/GPU를 절약하기 위한 방식으로 묵음 구간을 whisper에 보내지 않기 위함.
# 1) ASRCore에서 먼저 거름 (EnergyVAD)
class ASRCore:
def push(self, chunk):
is_speech = self.vad.is_speech(chunk) # ← 1차 VAD
audio = self.buffer.push(chunk, is_speech)
if audio is not None:
t = threading.Thread(target=self._transcribe, ...)
이후 whisper내부 구조에서 한번 더 걸러준다.
- 이건 whisper에 넘어온 오디오 안에서 잘라내어 환각을 방지하기 위함.
# 2) Whisper 내부에서도 한 번 더 거름
def transcribe(self, pcm_float32):
segments_gen, info = self.model.transcribe(
pcm_float32,
vad_filter=True, # ← 2차 VAD (faster-whisper 내장)
vad_parameters=dict(min_silence_duration_ms=500),
)
이렇게 2중 VAD를 통해 침묵을 명시적으로 걸러낼 수 있다.
언어 감지
segments_gen, info = self.model.transcribe(
pcm_float32,
language=self.config.language, # "ko" 로 고정
...
)
# info.language → Whisper가 감지한 언어 코드
return TranscriptionResult(
text=text,
confidence=confidence,
language=info.language or self.config.language, # 감지 실패 시 설정값 사용
)
- 설정 상에서 우린 언어를 ko로 고정하지만 whisper자체에서도 언어를 감지할 수 있다.
- 고정해줄 경우 오감지 문제를 해결할 수 있고, 속도도 빨라진다.
'프로젝트' 카테고리의 다른 글
| 어떻게 하면 LLM이 내가 원하는 역할을 잘 수행할까? (0) | 2026.05.21 |
|---|---|
| 7. Whisper Finetuning 1 (Feature Extractor & Tokenizer) (1) | 2026.04.18 |
| 5. 스트리밍 시스템에서의 청킹 + 발화 세그멘테이션 (0) | 2026.04.16 |
| 4. VAD(Voice Activity Detection) (0) | 2026.04.15 |
| 3. ASR파이프라인 구현(파이프라인 설계 + 오디오 개념) (0) | 2026.04.14 |