음성 인식(Speech Recognition; ASR) — CTC, RNN-T, Attention

음성 인식(Speech Recognition)은 매 타임스텝(timestep)마다 오디오 분류(audio classification)를 수행하고, 영어와 침묵(silence)을 이해하는 시퀀스 모델(sequence model)로 그 결과를 이어 붙이는 일입니다. CTC, RNN-T, Attention은 이를 푸는 세 가지 방식입니다. 하나를 고르고 왜 그 방식을 쓰는지 이해해야 합니다.

유형: Build 언어: Python 선수 강의: Phase 6 · 02 (Spectrograms & Mel), Phase 5 · 08 (CNNs & RNNs for Text), Phase 5 · 10 (Attention) 예상 시간: 약 45분

학습 목표

  • 오디오 프레임(audio frame)과 텍스트 토큰(text token)이 1:1로 정렬(align)되지 않는 이유를 설명합니다.
  • CTC(Connectionist Temporal Classification), RNN-T(Recurrent Neural Network Transducer), Attention encoder-decoder의 차이를 구분합니다.
  • WER(Word Error Rate)을 계산하고 해석합니다.
  • 오프라인(offline), 스트리밍(streaming), 엣지(edge) 배포에 맞는 ASR 모델 계열을 고릅니다.

문제

10초짜리 16 kHz 클립이 있습니다. 원하는 출력은 "turn on the kitchen lights" 같은 문자열입니다. 이 과제는 구조적인 어려움이 있습니다. 오디오 프레임(audio frame)은 문자(character)와 1:1로 정렬되지 않습니다. "okay"라는 단어는 200 ms일 수도 있고 1200 ms일 수도 있습니다. 침묵은 발화를 끊어 놓습니다. 어떤 음소(phoneme)는 다른 음소보다 깁니다. 출력 토큰(output token)의 개수는 미리 알 수 없습니다.

이를 푸는 정식화(formulation)는 세 가지입니다.

  1. CTC(Connectionist Temporal Classification). 특수한 공백(blank)을 포함해 프레임별 토큰 확률(per-frame token probabilities)을 출력합니다. 디코딩(decoding)할 때 반복과 공백을 접습니다(collapse). 비자기회귀(non-autoregressive)라서 빠릅니다. wav2vec 2.0, MMS에서 사용됩니다.
  2. RNN-T(Recurrent Neural Network Transducer). 인코더 프레임(encoder frame)과 이전 토큰(previous tokens)을 함께 보고 조인트 네트워크(joint network)가 다음 토큰을 예측합니다. 스트리밍이 가능합니다. Google의 온디바이스(on-device) ASR과 NVIDIA Parakeet에서 사용됩니다.
  3. Attention 인코더-디코더(encoder-decoder). 인코더가 오디오를 은닉 상태(hidden states)로 압축하고, 디코더(decoder)가 교차 어텐션(cross-attention)을 사용해 토큰을 자기회귀적으로 생성합니다. Whisper, SeamlessM4T에서 사용됩니다.

2026년 기준 LibriSpeech test-clean의 SOTA WER은 Parakeet-TDT-1.1B(NVIDIA) 1.4%, Whisper-Large-v3-turbo 1.58% 수준입니다. 숫자 차이는 작지만, 배포 방식의 차이는 큽니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.음성 인식(ASR)에서 오디오 프레임(audio frame)과 출력 텍스트 문자를 1:1로 정렬(align)할 수 없는 이유는 무엇인가요?

2.단어 오류율(WER; Word Error Rate)은 무엇을 측정하며, 문자 수준 정확도가 아니라 WER이 ASR의 표준 지표인 이유는 무엇인가요?

0/2 답변 완료

개념

three ways to map T audio frames to U tokens CTC — frame-level + blank log-mel → encoder per-frame distribution argmax per frame: a a _ _ a b b _ c collapse + unblank → "aabc" non-AR, streamable no internal LM wav2vec 2.0, MMS LibriSpeech-clean: ~1.9 WER RNN-T — encoder + predictor audio encoder (frames) predictor: prev tokens → state joiner → P(next | frame, history) emit token or skip frame streamable, internal LM complex 3D loss lattice Parakeet, Riva, Google on-device LibriSpeech-clean: 1.40 WER (SOTA 2026) attention — seq2seq audio encoder (6-32L) decoder cross-attends autoregressive tokens: BOS → hello → world → EOS timestamps from attention highest offline quality non-streamable by default Whisper v3, SeamlessM4T LibriSpeech-clean: 1.58 WER

CTC 직관. 인코더가 T개의 프레임 수준 분포(frame-level distribution)를 출력한다고 합시다. 분포의 클래스는 V+1개입니다. V개의 문자 토큰에 공백(blank) 하나가 추가됩니다. 길이가 U < T인 목표 문자열 y에 대해, 접었을 때 y가 되는 모든 프레임 정렬(frame alignment)이 정답으로 인정됩니다. CTC 손실(loss)은 그런 모든 정렬의 확률을 합산합니다. 추론(inference)은 프레임별 argmax를 구하고, 반복을 접고, 공백을 제거합니다.

장점은 비자기회귀이고, 스트리밍이 가능하며, 미리 보기(lookahead)가 없다는 점입니다. 단점은 조건부 독립 가정(conditional independence assumption) 입니다. 각 프레임 예측이 다른 프레임 예측과 독립이라고 보므로 내부 언어 모델(internal language model)이 없습니다. 빔 탐색(beam search)이나 얕은 융합(shallow fusion)으로 외부 언어 모델(external LM)을 붙여 보완합니다.

RNN-T 직관. RNN-T는 토큰 이력(token history)을 임베딩하는 예측기(predictor) 네트워크와, 예측기 상태와 인코더 프레임을 결합해 V+1개 토큰의 조인트 분포(joint distribution)를 만드는 결합기(joiner) 를 추가합니다. 여기서 +1은 null 또는 no-emit을 의미합니다. CTC가 무시한 조건부 의존성(conditional dependence)을 명시적으로 모델링합니다. 각 단계가 과거 프레임과 과거 토큰에만 조건을 두므로 스트리밍이 가능합니다.

장점은 스트리밍 가능성과 내부 언어 모델입니다. 단점은 학습이 더 복잡하고 메모리를 많이 쓴다는 점입니다. RNN-T는 3D 손실 격자(loss lattice)를 다루며, RNN-T 손실 커널(loss kernel)만으로도 별도 라이브러리 범주가 있을 정도입니다.

Attention 인코더-디코더. 인코더는 로그 Mel(log-mel) 프레임을 6-32개의 Transformer 레이어로 처리합니다. 디코더는 6-32개의 Transformer 레이어로 구성되며, 인코더 출력에 교차 어텐션(cross-attention)을 걸어 토큰을 자기회귀적으로 생성합니다. 정렬 제약(alignment constraint)이 없습니다. 어텐션은 오디오의 어디든 볼 수 있습니다. 어텐션 범위를 제한하지 않으면 스트리밍이 어렵습니다. 예를 들어 2024년의 chunked Whisper-Streaming은 어텐션을 청크(chunk) 단위로 제한합니다.

장점은 오프라인 ASR에서 가장 높은 품질을 내고, 표준 seq2seq 도구로 학습하기 쉽다는 점입니다. 단점은 자기회귀 지연시간(autoregressive latency)이 출력 길이에 비례하며, 별도 엔지니어링 없이는 스트리밍할 수 없다는 점입니다.

WER: 보고하는 하나의 숫자

단어 오류율(Word Error Rate, WER) = (S + D + I) / N입니다. 여기서 S는 대체(substitution), D는 삭제(deletion), I는 삽입(insertion), N은 기준 문장의 단어 수(reference word count)입니다. 단어 수준 레벤슈타인 편집 거리(Levenshtein edit distance)와 같습니다. 낮을수록 좋습니다. WER이 20%를 넘으면 일반적으로 사용하기 어렵고, 읽은 음성(read speech)에서 5% 아래면 인간 수준(human-parity)에 가깝습니다.

표준 벤치마크의 2026년 수치는 다음과 같습니다.

모델LibriSpeech test-cleanLibriSpeech test-other크기
Parakeet-TDT-1.1B1.40%2.78%1.1B params
Whisper-Large-v3-turbo1.58%3.03%809M
Canary-1B Flash1.48%2.87%1B
Seamless M4T v21.7%3.5%2.3B

이 모델들은 모두 encoder-decoder 또는 RNN-T 기반입니다. 순수 CTC 시스템(wav2vec 2.0)은 test-clean에서 대략 1.8-2.1% 수준입니다.

직접 만들기

Step 1: 탐욕(greedy) CTC 디코딩

def ctc_greedy(frame_logits, blank=0, vocab=None):
    # frame_logits: list of per-frame probability vectors
    preds = [max(range(len(p)), key=lambda i: p[i]) for p in frame_logits]
    out = []
    prev = -1
    for p in preds:
        if p != prev and p != blank:
            out.append(p)
        prev = p
    return "".join(vocab[i] for i in out) if vocab else out

규칙은 두 가지입니다. 연속 반복을 접고, 공백을 제거합니다. 예를 들어 a a _ _ a b b _ ca a b c가 됩니다.

Step 2: 빔 탐색(beam-search) CTC

def ctc_beam(frame_logits, beam=8, blank=0):
    import math
    beams = [([], 0.0)]  # (tokens, log_prob)
    for p in frame_logits:
        log_p = [math.log(max(pi, 1e-10)) for pi in p]
        candidates = []
        for seq, lp in beams:
            for t, lpt in enumerate(log_p):
                new = seq[:] if t == blank else (seq + [t] if not seq or seq[-1] != t else seq)
                candidates.append((new, lp + lpt))
        candidates.sort(key=lambda x: -x[1])
        beams = candidates[:beam]
    return beams[0][0]

프로덕션(production) 환경에서는 언어 모델 융합(LM fusion)을 적용한 접두사 트리 빔 탐색(prefix tree beam search)을 사용합니다. 이 코드는 개념을 보여주는 뼈대(skeleton)입니다.

Step 3: WER

def wer(ref, hyp):
    r, h = ref.split(), hyp.split()
    dp = [[0] * (len(h) + 1) for _ in range(len(r) + 1)]
    for i in range(len(r) + 1):
        dp[i][0] = i
    for j in range(len(h) + 1):
        dp[0][j] = j
    for i in range(1, len(r) + 1):
        for j in range(1, len(h) + 1):
            cost = 0 if r[i - 1] == h[j - 1] else 1
            dp[i][j] = min(
                dp[i - 1][j] + 1,
                dp[i][j - 1] + 1,
                dp[i - 1][j - 1] + cost,
            )
    return dp[len(r)][len(h)] / max(1, len(r))

Step 4: Whisper로 추론하기

import whisper
model = whisper.load_model("large-v3-turbo")
result = model.transcribe("clip.wav")
print(result["text"])

2026년 기준 가장 강한 범용 ASR(general ASR) 중 하나를 한 줄로 실행할 수 있습니다. 24 GB GPU에서 실시간 대비 약 20배(20x realtime) 속도로 동작합니다.

Step 5: Parakeet 또는 wav2vec 2.0으로 스트리밍하기

from transformers import pipeline
asr = pipeline("automatic-speech-recognition", model="nvidia/parakeet-tdt-1.1b")
for chunk in streaming_audio():
    print(asr(chunk, return_timestamps=True))

스트리밍 ASR에는 청크 단위 인코더 어텐션(chunked encoder attention)과 상태 이월(carryover state)이 필요합니다. 이런 기능을 지원하는 라이브러리를 사용해야 합니다(Parakeet은 NeMo, transformers pipeline은 chunk_length_s).

사용하기

2026년 기준 스택은 다음처럼 고릅니다.

상황선택
영어, 오프라인, 최고 품질Whisper-large-v3-turbo
다국어, 견고함SeamlessM4T v2
스트리밍, 낮은 지연시간Parakeet-TDT-1.1B 또는 Riva
엣지/모바일, 500 ms 미만 지연시간양자화(quantized)한 Whisper-Tiny 또는 Moonshine(2024)
긴 음성(long-form)VAD 기반 청킹(chunking)을 사용하는 WhisperX
도메인 특화(의료, 법률)wav2vec 2.0 미세조정(fine-tune) + 도메인 언어 모델 융합(domain LM fusion)

2026년에도 배포되는 흔한 함정

  • VAD 없음. 침묵 구간에 Whisper를 실행하면 "Thanks for watching!" 같은 환각(hallucination)이 나올 수 있습니다. 항상 VAD(voice activity detector)로 차단(gate)해야 합니다.
  • 문자 vs 단어 vs 서브워드(subword) WER. 정규화(normalization: 소문자화, 문장부호 제거) 이후 단어 수준 WER을 보고해야 합니다.
  • 언어 식별 드리프트(language ID drift). Whisper의 자동 언어 식별(auto LID)은 잡음 많은 클립을 일본어 또는 웨일스어로 잘못 라우팅할 수 있습니다. 언어를 알고 있다면 language="en"처럼 고정합니다.
  • 청킹 없는 긴 클립. Whisper는 30초 윈도우(window)를 가집니다. 그보다 긴 클립에는 chunk_length_s=30, stride=5를 사용합니다.

산출물 만들기

outputs/skill-asr-picker.md로 저장합니다. 주어진 배포 목표(deployment target)에 맞춰 모델, 디코딩 전략(decoding strategy), 청킹, 언어 모델 융합을 선택하는 skill입니다.

연습문제

  1. 쉬움. code/main.py를 실행합니다. 직접 만든 CTC 출력(hand-crafted CTC output)을 탐욕적으로 디코딩하고 기준 문장(reference)에 대한 WER을 계산합니다.
  2. 중간. Step 2의 접두사 트리 빔 탐색(prefix-tree beam search)을 제대로 구현합니다. 공백 병합 규칙(blank merge rule)을 반영합니다. 10개 예시로 구성된 합성 데이터셋(synthetic dataset)에서 탐욕(greedy) 디코딩과 비교합니다.
  3. 어려움. whisper-large-v3-turboLibriSpeech test-clean에 실행합니다. 첫 100개 발화(utterance)의 WER을 계산하고 공개된 수치와 비교합니다.

핵심 용어

용어흔한 설명실제 의미
CTC공백 토큰 손실(blank-token loss)프레임-토큰 정렬(frame-to-token alignment) 전체를 주변화(marginalize)하는 비자기회귀(non-AR) 손실입니다.
RNN-T스트리밍 손실CTC에 다음 토큰 예측기(next-token predictor)를 더해 단어 순서(word order)를 다룹니다.
Attention enc-decWhisper 방식인코더와 교차 어텐션(cross-attending) 디코더를 사용하며, 오프라인 품질이 가장 좋습니다.
WER보고하는 숫자단어 수준의 (S+D+I)/N입니다.
Blank비어 있음CTC에서 "이 프레임에는 출력(emission)이 없음"을 나타내는 특수 토큰입니다.
LM fusion외부 언어 모델빔 탐색 중 LM 로그 확률(log-prob)을 가중합으로 더합니다.
VAD침묵 게이트(silence gate)음성 활동 감지기(voice activity detector)이며 비음성 구간을 잘라냅니다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

asr-picker

Pick ASR model, decoding strategy, chunking, and LM fusion for a given deployment target.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.CTC는 프레임 예측 간 조건부 독립 가정(conditional independence assumption)을 합니다. 이것이 실제로 어떤 문제를 일으키며, 보통 어떻게 해결하나요?

2.500 ms 미만 지연시간(latency)의 모바일 기기에 ASR 시스템을 배포해야 합니다. Whisper 같은 어텐션 인코더-디코더(attention encoder-decoder)가 부적합한 이유와, 대신 어떤 정식화(formulation)를 선택해야 하나요?

3.긴 오디오 클립에 VAD(음성 활동 감지; Voice Activity Detection) 없이 Whisper를 실행하면, 침묵 구간에서 'Thanks for watching!' 같은 환각(hallucination) 텍스트가 나타나는 이유는 무엇인가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다