본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 01. 22:53

내부 eval은 98%인데 실기에서 실패했다 — LiveKit wakeword로 내 목소리를 학습시킨 이야기

요약

내부 평가 지표(Recall)는 높았으나 실제 환경에서 성능이 급락한 호출어(Wake word) 모델의 실패 사례를 분석합니다. 영어식 발음 데이터에 과적합된 '발음 편향' 문제를 지적하며, LiveKit wakeword를 활용한 커스텀 모델 학습 파이프라인을 소개합니다.

핵심 포인트

  • 높은 Recall 지표가 실제 환경의 성능을 보장하지 않음
  • 데이터 생성 시 언어별 발음 특성을 고려하지 않으면 편향 발생
  • Verification(검증)과 Validation(실용성)의 차이 이해 필요
  • LiveKit wakeword를 이용한 5단계 학습 파이프라인 활용

전작에서는 영어 호출어(wake word)인 "hey assistant"로 wake word 모델을 40분 만에 만들었다 (전작 기사). 내부 eval(평가)은 recall 98.95%였다. 로컬에서 사용할 수 있는 TTS (Piper)가 영어를 전제로 하기 때문에, 호출어도 자연스럽게 영어가 되었다.

하지만 정말 원했던 것은 내 목소리로 일본어 "헤이, 마루타(ヘイ、マルタ)"에 반응하는 모델이다. 호출어를 일본어로 새로 만든 이전 버전 (v3)은 내부 평가에서 recall 98.45%를 기록했다. 숫자만 보면 전작과 마찬가지로 우수하다. 그런데 실제 마이크를 향해 "헤이, 마루타"라고 말하면 절반밖에 작동하지 않는다. 지표를 믿고 출시했다가 어설픈 도구를 손에 쥐여준 꼴이 되었다.

그 패배로부터 다시 만든 v4에서 무엇이 바뀌었는지, 왜 이전 버전은 내부 eval을 만점에 가깝게 기록하면서도 실기에서 실패했는지. Verification (제대로 만들어졌는가)과 Validation (실용 환경에서 작동하는가)의 차이를 실제 데이터를 바탕으로 기술한다.

v3에서 일어난 일 — 내부 eval은 98%, 실기 마이크는 50%

사건의 발단은 자체 제작한 음성 어시스턴트용 wakeword 검출 모델을 v3까지 완성한 단계에서의 실기 마이크 테스트였다.

버전내부 eval (recall)실기 마이크 (본인의 자연 발화 「헤이, 마루타」)
v398.45% (threshold 0.5)약 50% (10회 중 5회)

숫자만 보면 우수한 모델이다. 자신의 녹음본을 일부 섞은 상태에서 TTS 합성 데이터를 대량으로 학습시켰고, 내부의 validation 세트에서는 나무랄 데 없는 recall을 뽑아내고 있었다.

하지만 실제로 마이크를 향해 일본어로 "헤이, 마루타"라고 말해보니 10회 중 5회밖에 반응하지 않는다. 대신 "헤이 모르타"나 "헤이 모타"처럼 영어식으로 발음하면 안정적으로 작동한다.

숫자에 속았다. 그렇게 느낀 순간이었다.

왜 실패했는가 — 발음 편향(Pronunciation Bias)이라는 이름의 함정

조사해 보니 원인은 학습 데이터의 생성 방식 자체에 있었다.

영어의 "malta"는 mɔːltə이다. '오'에 가까운 모음으로 시작하여 음절이 흘러간다. 반면 일본어의 「마루타(マルタ)」는 ma·ru·ta로, 3박으로 나뉘어 각 음절이 독립한다. 같은 단어라고 생각하고 합성하더라도, 영어 TTS가 생성하는 소리와 인간이 일본어로서 발음하는 소리는 파형(waveform) 레벨에서 완전히 다른 것이다.

v3가 학습에 사용한 TTS 합성 음성은 대부분 전자(영어식)에 가깝게 rendering 되어 있었다. 결과적으로 모델은 "영어 느낌의 마루타 (≒ hey morto)"라는 음향 특징에 과적합(overfitting)되었다. 정작 목표로 했던 본래의 타겟인 자신의 자연스러운 일본어 발화를 멋지게 놓쳐버리는 모델이 완성된 것이다.

데이터 생성 단계의 편향(bias)이 그대로 모델의 편향이 된다. 이것이 발음 편향(pronunciation bias)의 정체였다.

머리로 이해하는 것과, 실기에서 절반밖에 작동하지 않는 자신의 모델을 직접 보는 것은 교훈의 무게가 전혀 다르다.

LiveKit wakeword란 — 5단계 파이프라인으로 나만의 호출어를 만들기

여기서 사용하고 있는 것은 livekit.wakeword라는 Python 패키지다. 실시간 음성 AI 문맥에서 최근 성장하고 있는 LiveKit 에코시스템의 일부로, 커스텀 호출어 검출 모델을 학습 및 에스포트(export)하는 툴체인을 제공한다.

최종 결과물은 ONNX 모델이며, 추론 측은 openwakeword 런타임에서 불러온다. 학습 파이프라인은 5단계로 나뉘어 있다.

generate → augment → train → export → eval
(합성) (증강) (학습) (ONNX화) (평가)

각 단계에서 하는 일은 다음과 같다.

  • generate: TTS (Piper 등)를 사용하여 positive (호출어)와 negative (호출되기를 원치 않는 소리) 클립을 합성 생성한다. 영리한 점은, positive_train/...

폴더에 기존 클립이 있으면 개수를 세고, 부족한 분량만큼만 추가 합성하는 resume-safe 설계로 되어 있다는 점이다. 나중에 이것이 결정적인 역할을 한다. -
augment: 각 클립에 잔향 (RIR)・배경 노이즈・EQ・거리 상당의 변환을 가하여 데이터를 증강(augmentation)한다. 원본 하나로부터 여러 개의 변형(variant)이 생성된다. -
train: 증강(augmented)된 데이터로 검출 모델을 학습한다. -
export: 학습된 모델을 ONNX로 변환한다. -
eval: holdout / validation을 통해 재현율 (recall)・FPPH (시간당 오검출 수)・정확도 (accuracy)를 산출한다.

이 정도의 패키지를 무료로 사용할 수 있는 시대가 되었다. 편리한 반면, 무엇이 일어나고 있는지 모른 채 돌리다가는 v3에서 빠졌던 함정에 그대로 빠지게 된다.

LiveKit wakeword를 자택 GPU에서 개인이 돌려 자신만의 전용 호출어를 만든다는 일본어 사례는 거의 찾아볼 수 없다. 프레임워크 자체의 인지도가 이제부터 시작이라는 의미에서 '새로운 기술'이다.

도구를 의심하라 — voxcpm 클론 불가・config에 주입구 없음

v4의 방침을 세우는 단계에서 가장 먼저 떠올린 아이디어는 "voice cloning으로 자신의 목소리에서 positive를 양산한다"는 안이었다. 구체적으로는 voxcpm이라는 TTS backend를 사용하면, 자신의 reference 음성으로부터 닮은 목소리로 "헤이, 마르타"를 무한히 생성할 수 있을 것이라 생각했다.

하지만 livekit.wakeword의 config schema와 소스 코드를 정독했더니, 당초 구상을 뒤엎는 세 가지 사실이 드러났다.

  • config에 "실제 녹음된 positive를 직접 주입하는 field"가 존재하지 않는다. positive_audio_dir와 같은 인수는 없다. -
  • voxcpm backend는 voice-DESIGN이지 voice-CLONING이 아니다. reference 음성 wav를 전달하는 인수 자체가 없으며, 준비된 페르소나 프롬프트는 모두 영어·미영 악센트의 텍스트 기술이었다. "자신의 녹음을 voxcpm으로 클론하여 positive를 양산한다"는 당초 안은 이 버전에서는 구현 불가능했다. voxcpm의 페르소나가 전부 영어 악센트라는 것은, 배제하고 싶은 편향 (bias)의 근원 그 자체를 학습 데이터에 섞게 된다는 뜻이다. -
  • 실제 녹음을 활용할 수 있는 유일한 정규 루트는 generate의 resume-safe 기구였다. positive_train/에 기존의 clip_######.wav를 두면, generate는 그것을 보존하고 부족한 분량만을 TTS로 합성한다. 이것을 사용하면 실제 녹음을 미리 학습 데이터에 올려둘 수 있다.

문서만 읽고 구현에 들어갔다면 확실히 시간을 허비했을 것이다. 소스 코드까지 내려가 schema를 읽고 나서야 비로소 길이 보였다. 도구를 사용하기 전에 소스를 읽는다는 당연한 일의 위력을 절감했다.

참고로 이 "resume-safe 기구를 역이용하는" 경로는, v3에 포함된 미완료 배치(batch)의 코멘트에 남아있던 "re-process all positive_train including new 84 personal wavs"라는 의도와 일치했다. 설계자가 본래 의도한 예상 경로는 외부 문서에는 적혀 있지 않고, 코드 파편으로만 남아 있었던 셈이다.

v4 해결책 — 자신의 녹음 84건을 미리 배치하기

전략은 심플하다. 자신의 실제 녹음을 positive_train/의 일련번호 파일로 미리 배치해 두고, generate가 그것을 보존하게 한 상태에서 부족한 분량만 TTS로 합성하고, augment로 증강하여 train으로 흘려보낸다. 이것뿐이다.

녹음 자체는 구현 측에서 용도별로 설계한 5가지 카테고리에 따라 처음부터 나누어 녹음해 두었다. 고민해야 할 지점은 "84건을 전부 positive로 할 것인가"가 아니라, 이 5가지 카테고리를 학습 시 각각 positive / negative 중 어디로 할당할 것인가였다.

prefix건수내용v4 라벨
jp_*19일본어 자연스러운 「헤이, 마루타」POSITIVE (주요 타겟)
distance_*10거리 변화에 따른 발화POSITIVE
rec_*25일반 녹음POSITIVE
noise_*20YouTube 재생 중 등 잡음·배경음 환경 녹음NEGATIVE (배경음을 명시적으로 reject)
english_*10영어식 발음NEGATIVE (문제의 근원을 명시적으로 reject)

포인트는, positive를 본인의 일본어 자연 발화 (jp / distance / rec의 54건)로 좁히고, 거부하고 싶은 소리 2종류를 모두 명시적으로 NEGATIVE에 배치했다는 점이다.

첫 번째 NEGATIVE는 영어식 발음 (english_*, 10건)이다. v3가 과적합 (overfitting) 되었던 「영어식 발음의 마루타」를 학습 단계에서 명시적으로 거부하여, 「영어 발음에는 반응하지 않는다」라는 역억제 (counter-inhibition)를 포함시켰다.

두 번째 NEGATIVE는 잡음·배경음 (noise_*, 20건)이다. YouTube 재생 중과 같은 배경음 그 자체를 negative에 넣어, 「기동 워드(wake word) 이외의 환경음에는 반응하지 않는다」를 학습시켰다.

정답 (본인의 일본어 발화)을 가르칠 뿐만 아니라, 반응해서는 안 되는 소리를 「영어식 발음」과 「배경음」이라는 두 방향에서 명시적으로 거부한다. 발음 편향 (pronunciation bias) 제거와 환경음에서의 오검출 (false detection) 억제를 별도의 방식으로 심어 넣는 접근법이었다.

config 측에서도, target_phrases를 ["hey maruta"]라는 영어 철자로 설정하여 ma-ru-ta로 분해시키려는 의도를 담았다 (일본어 철자 「マルタ」는 piper의 영어 CMUdict에서 발음이 불가능하다). tts_backend는 piper_vits를 그대로 유지했고, voxcpm은 채택하지 않았다 (앞서 언급했듯이 영어 페르소나 문제와 HF 다운로드 리스크를 모두 회피하기 위함이다).

실행은 자택의 RTX 5060 Ti 머신 (Windows) 상에서 진행했다. generate (6 splits + holdout)부터 augment, train (3-phase / loss 0.67 → 0.16), export (ONNX), eval까지 한 차례 모두 통과했다.

결과 — recall 83.1%, 그리고 실제 마이크 합격

v4의 eval 결과는 다음과 같다.

지표의미
recall83.1% (optimal 86.7% @ threshold 0.42)기동 검출률. 실용 영역인 80%를 넘었다
FPPH (False Positive Per Hour)0.057시간당 오검출 거의 제로
accuracy91.5%validation 전체의 판정 정확도
optimal_threshold0.42F1 최대점의 임계값

주목해야 할 점은 recall 83.1%라는 숫자 그 자체보다, v3의 98%에서 떨어졌다는 사실이다. 내부 eval 수치로만 보면 명백히 열화된 것이다.

하지만 이것은 열화가 아니라 정상화다. v3는 영어식 발음에 과적합된 결과로서 98%를 기록했었다. v4는 그 편향을 의도적으로 깎아낸 결과로서 83.1%에 안착한 것이다.

그리고 실제 마이크 테스트 결과는 다음과 같다.

✅ 「마루타」 (일본어 자연 발화)로 기동.

✅ 「모루타」 (영어식 발음)로 미작동.

→ 발음 편향 해소를 입증. v4 채택 확정 및 main 머지(merge) 완료.

숫자는 낮아졌지만, 실용에서 작동하는 모델이 되었다. 이것이 v4의 도달점이다.

배움 — Verification과 Validation은 별개의 축

지금까지의 경험을 통해 남은 교훈은 소프트웨어 공학의 고전적인 구분 그 자체였다.

관점무엇을 측정하는가이 사례의 경우
Verification (올바르게 만들었는가)사양대로 구현되었는가 · 내부 지표를 만족하는가내부 eval의 recall · FPPH · accuracy
Validation (실용에서 작동하는가)상정된 사용법으로 실제로 도움이 되는가내가 자연스러운 일본어로 「헤이, 마루타」라고 말했을 때 기동하는가

v3는 Verification (검증) 단계에서는 거의 만점이었다. 그럼에도 불구하고 Validation (타당성 검증)에서는 절반밖에 작동하지 않았다. 내부 지표는 "올바르게 학습되었는가"만을 알려줄 뿐이다. "실용 환경에서 작동하는가"는 상정한 현장에서 실제로 작동시켜 확인할 수밖에 없었다.

요약

이 사건을 계기로 출하 판정 기준이 바뀌었다. 내부 eval (평가) 수치만으로는 더 이상 출하를 결정하지 않는다.

  • 내부 eval 98%는 과적합 (Overfitting)된 방향으로 치우쳤을 뿐인 숫자였다. Verification (올바르게 만들었는가)이 만점이라도, Validation (실용에서 작동하는가)은 다른 축으로 측정하지 않으면 알 수 없다.
  • 출하의 마지막 게이트는 내부 지표가 아니라, 상정한 현장에서 실제로 작동하는가이다. eval을 믿고 그대로 출하하면 실기 (Actual device)에서 실패한다.

v4 모델은 지금도 Mac mini 위에서 돌아가고 있으며, 내가 "헤이, 마루타"라고 말을 걸면 대답을 한다. Recall (재현율) 83.1%는 v3의 98%보다 겉보기에는 좋지 않다. 그럼에도 10번을 불러도 절반밖에 기동하지 않는 도구를 안고 있기보다는, 수수하더라도 실제로 작동하는 도구를 갖고 싶다.

본 기사는 직접 제작한 음성 어시스턴트용 wake word 모델 (v3 → v4)을 자택 GPU에서 구축 및 검증한 기록을 라이터 AI가 재구성하여 작성한 1차 지견 메모이다. 1차 정보는 구현 담당 AI 에이전트가 정리한 기술 doc, build 로그, eval JSON, 실제 마이크 테스트 기록이다. 코드 파편, eval 수치, 커맨드는 모두 실제 프로젝트의 것이며, 재구성과 강조의 편향은 라이터 AI의 주관에 따른 것이다. 버전이 변경될 경우 LiveKit wakeword의 API 변경이 예상되므로, 최신 정보는 공식 문서에서 확인하기 바란다.

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0