MFCC·HMM과 Wav2Vec으로 설계하는 음성 인식 시스템

MFCC·HMM 파이프라인과 Wav2Vec 2.0 기반 End-to-End 음성 인식을 비교하고, 디코딩·운영·도메인 적응 방식을 정리한다.

2026-08-14 · 최초 발행 2025-10-14

음성 인식 파이프라인을 고르는 기준

음성 인식은 사용자 인터페이스, 콜센터 자동화, 회의록 생성에서 텍스트 변환을 담당한다. 전통적인 MFCC+HMM 조합은 특징 추출과 시퀀스 모델링을 분리하고, Wav2Vec 2.0 기반 방식은 표현 학습과 음향 모델을 하나의 네트워크에 통합한다.

두 방식의 차이는 모델 정확도만으로 정리되지 않는다. 사용할 수 있는 데이터와 연산 자원, 지연 목표, 도메인 어휘의 변화 주기, 운영 인프라까지 선택 기준에 포함된다.

MFCC(Mel-Frequency Cepstral Coefficients)는 사람의 청각 특성에 맞춘 멜 스케일 스펙트럼 특징 추출 방식이다. 단음절과 연속음을 다룰 수 있는 저차원 특징 벡터를 만든다. HMM(Hidden Markov Model)은 은닉 상태 전이와 관측 확률로 시퀀스를 표현하는 통계 모델이며, Viterbi는 추론, Baum-Welch는 학습에 사용된다.

Wav2Vec 2.0은 대규모 무라벨 음성으로 자기지도학습을 수행한 뒤 CTC 또는 seq2seq 방식으로 미세조정하는 End-to-End 음성 인식 방식이다. CNN 기반 특징 인코더와 Transformer 컨텍스트 네트워크가 마스크된 구간을 예측하면서 음운과 발화 표현을 학습한다.

디코딩은 음향 모델(AM), 언어 모델(LM), 탐색(Beam Search)의 조합으로 이뤄진다. 고전 파이프라인에서는 MFCC→HMM(+n-gram LM)을, End-to-End 방식에서는 컨텍스트 인코더→CTC/seq2seq(+옵션 LM)를 사용한다.

입력 음성을 특징과 상태열로 바꾸는 과정

MFCC 경로는 Pre-emphasis, 프레이밍과 윈도잉, STFT와 멜필터, 로그, DCT 순서로 진행해 MFCC 벡터를 생성한다. 실시간 스트리밍에서는 20–25 ms 프레임과 10 ms 홉을 권장한다.

입력 품질도 별도 처리 대상이다. VAD(Voice Activity Detection)로 무음을 제거하고 SNR 기반 노이즈 억제를 적용한다. 리샘플링(16 kHz)과 채널 정규화는 필수다.

HMM은 상태 전이에 언어·발음 제약을 반영하고, 가우시안 혼합 등의 관측 확률로 프레임 시퀀스를 매핑한다. Viterbi는 이 조건에서 가장 적합한 상태열을 찾는다. 음소 단위 HMM, 발음사전, 언어 모델을 결합하는 구조는 작은 데이터셋과 낮은 연산 자원 환경에서 안정적으로 동작한다.

반대로 Wav2Vec 2.0은 사전학습 표현을 CTC 기반 문자 또는 음소 라벨에 맞춰 미세조정한다. 대용량 사전학습의 효과로 도메인 라벨이 적은 경우에도 성능 향상을 기대할 수 있다.

디코딩 정책은 정확도와 지연을 함께 바꾼다

그리디 디코딩은 지연을 최소화하는 데 유리하고, 빔 서치는 정확도를 높이는 선택지다. 빔 폭과 LM 가중치를 조정하면 지연과 정확도 사이의 균형을 바꿀 수 있다.

언어 모델은 n-gram(KenLM)과 신경 LM(RNN/Transformer)로 나눠 볼 수 있다. n-gram은 간결하고 빠르며, 신경 LM은 정확도 측면에서 우수하다. 어느 쪽을 택할지는 이용 가능한 자원과 지연 목표에 달려 있다.

서비스 형태에 따라 달라지는 적용 방식

콜센터 STT와 QA 자동화에서는 노이즈와 중첩 발화가 있는 환경에서 가용성을 확보해야 한다. Wav2Vec+CTC+KenLM 조합으로 실시간 자막과 의도 분류 파이프라인을 구성할 수 있으며, 고유명사와 약어는 사전·스펠링 단서 또는 사용자 사전 핫스왑으로 적응한다.

음성 비서와 키오스크는 스트리밍 디코딩과 200–300 ms의 낮은 지연 목표를 둔다. Quantization과 INT8 기반 온디바이스 가속은 비용과 프라이버시를 함께 고려하는 방법이며, 핫워드 검출을 전단에 배치하면 불필요한 추론을 막을 수 있다.

회의록과 미디어 자막에서는 장시간 음성을 세그먼트로 나누고, 화자 전환을 기준으로 타임스탬프를 정렬한다. 문장 부호 복원과 대문자화는 최종 텍스트의 가독성을 높이는 후처리다.

모델 선택에 남는 운영상의 차이

항목 MFCC+HMM End-to-End (Wav2Vec)
성능(정확도) 중간, 도메인 최적화 필요 높음, 자기지도 사전학습 효과
확장성 언어·도메인 확장 시 수작업 많음 사전학습 가중치 재사용으로 확장 용이
일관성 규칙 기반 구성으로 예측 가능성 높음 데이터 편향·도메인 드리프트 영향 큼
안정성 저자원·에지 환경에 견고 모델/프레임워크 의존, 리소스 장애 영향
운영 편의 단순 파이프라인, 디버깅 용이 파이프라인 단순화, 학습·서빙 인프라 복잡

베이스라인인 MFCC+HMM과 비교하면 End-to-End(Wav2Vec)는 WER를 30–60% 상대 개선할 수 있으며, 이는 코퍼스와 사전학습 규모에 따라 ±가 있다. 한국어 공개 모델에서는 CER 6–12% 수준이 보고된 사례가 있다(최신 정보 확인 필요).

자원 측면에서는 CPU 전용 HMM 파이프라인이 저비용 운영에 적합하다. GPU/NPUs를 활용하는 E2E 방식은 단위 비용이 높아질 수 있지만 정확도와 유연성이 높다. 양자화와 프루닝으로 지연을 30–70% 단축할 수 있다. End-to-End 방식은 도메인 적응, 노이즈 강건성, 후처리 간소화에 유리하며 사전학습 전이를 통해 저라벨 환경에서도 빠르게 고도화할 수 있다.

처리 경로와 조건 분기

리샘플링/정규화VAD 무음 제거MFCC 경로E2E 경로GreedyBeam+LM조건: SNR<임계입력 오디오 스트림 8/16 kHz전처리프레이밍/윈도잉분기MFCC 추출(로그멜→DCT)HMM 디코더(Viterbi)텍스트, 신뢰도, 타임스탬프Wav2Vec 특징인코더+Transformer디코딩텍스트, 신뢰도텍스트, 신뢰도, 토큰 스코어노이즈 억제

입력에서 텍스트와 신뢰도, 타임스탬프를 얻기까지의 흐름을 분리해 두면 경로별 병목을 확인하기 쉽다. SNR이 임계 미만이면 노이즈 억제를 거치며, VAD가 음성을 검출하지 못한 경우에는 버퍼를 누적한 뒤 배치 디코딩을 재시도하는 방식을 권장한다.

데이터부터 배포까지 이어지는 관리 항목

음성 수집 단계에서는 샘플 레이트를 통일하고 채널 일관성을 확보하며 라벨 검수 체계를 마련한다. 도메인 사전에는 고유명사와 약어를 표준화해 반영한다. 고전 방식은 발음사전을, E2E 방식은 스펠링 힌트를 관리한다.

저자원, 저지연, 단순 도메인이라면 MFCC+HMM이 적합하다. 범용성, 높은 정확도, 적응성이 더 중요하면 Wav2Vec 2.0 + CTC + LM 조합을 선택하고 빔 폭과 LM 가중치를 최적화한다.

운영 파이프라인은 수집→정제→증강→학습→배포→모니터링→어댑테이션의 폐루프로 관리한다. WER/CER, 지연, 인퍼런스 실패율을 추적하고, 지연 p95, 오디오 드롭률, OOV 비율을 포함한 SLO를 정의한다. 배포는 A/B 또는 샌드박스 기반 페이즈드 롤아웃으로 진행하며, 텔레메트리에 따라 사전을 핫스왑한다.

음성 PII는 암호화해 저장하고 전송에는 TLS를 적용한다. 온디바이스 또는 프라이빗 추론도 검토 대상이며, 바이어스와 도메인 드리프트를 탐지할 체계가 필요하다.

실행 예제

전제조건

  • Python 3.10, PyTorch 2.2+, torchaudio 2.2+, librosa 0.10+, transformers 4.42+ 설치
  • CUDA 사용 시 torch 설치 시 버전 일치 필요
  • 공개 한국어 모델 가용성은 변경 가능(최신 정보 확인 필요)

설치

pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu121  # CUDA 생략 가능
pip install librosa soundfile transformers datasets accelerate
pip install hmmlearn scikit-learn

MFCC 특징을 추출하고 단어 수준 HMM을 학습해 추론하는 예제다.

# 파일: mfcc_hmm_demo.py
# 데이터: data/wavs 폴더에 label__uttid.wav 형태(예: hello__001.wav)
import os, glob, numpy as np, librosa, soundfile as sf
from hmmlearn.hmm import GaussianHMM
from sklearn.preprocessing import StandardScaler
from collections import defaultdict

sr = 16000
def extract_mfcc(path):
    y, _ = librosa.load(path, sr=sr, mono=True)
    y = librosa.effects.preemphasis(y)
    mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13, n_fft=400, hop_length=160)
    d = librosa.feature.delta(mfcc)
    dd = librosa.feature.delta(mfcc, order=2)
    feat = np.vstack([mfcc, d, dd]).T  # (T, 39)
    return feat

# 학습 데이터 적재
train_files = glob.glob("data/wavs/*.wav")
by_label = defaultdict(list)
for f in train_files:
    label = os.path.basename(f).split("__")[0]
    by_label[label].append(f)

# 특징/스케일링
scaler = StandardScaler()
all_feats = []
for files in by_label.values():
    for f in files:
        all_feats.append(extract_mfcc(f))
scaler.fit(np.vstack(all_feats))

# 라벨별 HMM 학습(단어 수준 모델 가정)
models = {}
for label, files in by_label.items():
    X = np.vstack([scaler.transform(extract_mfcc(f)) for f in files])
    lengths = [extract_mfcc(f).shape[0] for f in files]
    model = GaussianHMM(n_components=5, covariance_type="diag", n_iter=50, verbose=False)
    model.fit(X, lengths)
    models[label] = model

# 추론
def predict(wav_path):
    feat = scaler.transform(extract_mfcc(wav_path))
    scores = {lbl: m.score(feat) for lbl, m in models.items()}
    return max(scores, key=scores.get), scores

test_wav = "data/test.wav"
pred, scores = predict(test_wav)
print("Prediction:", pred)
print(scores)

아래 예제는 CTC 기반 Wav2Vec2 한국어 추론을 수행한다.

# 파일: wav2vec2_ko_infer.py
import torch, librosa
from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC

# 공개 한국어 모델 예: jonatasgrosman/wav2vec2-large-xlsr-53-korean (가용성 변동 가능)
model_id = "jonatasgrosman/wav2vec2-large-xlsr-53-korean"
processor = Wav2Vec2Processor.from_pretrained(model_id)
model = Wav2Vec2ForCTC.from_pretrained(model_id).eval()

def transcribe(path, target_sr=16000):
    y, _ = librosa.load(path, sr=target_sr, mono=True)
    with torch.no_grad():
        inputs = processor(y, sampling_rate=target_sr, return_tensors="pt", padding=True)
        logits = model(inputs.input_values).logits
        ids = torch.argmax(logits, dim=-1)
        text = processor.batch_decode(ids)[0]
    return text

print(transcribe("data/test_ko.wav"))

지연 최적화는 torch.compile, half-precision(fp16), onnxruntime, quantization(dynamic/static) 순서로 적용할 수 있다. pyctcdecode+KenLM으로 빔 서치를 결합하면 고유명사 정확도 향상을 기대할 수 있다.

음성 인식MFCCHMMWav2VecCTC언어 모델