A-5 스파이크 — 어눌한 한국어 STT 정확도를 잰다

무엇을

구현이 아니라 “잴 수 있는가”를 먼저 확인하는 스파이크다. 외국인이 어눌한 한국어로 말할 때 상용 STT 가 얼마나 알아듣는지를 숙련도 등급별로 잰다.

⚠ 2026-08-28 ID 정정: A-3 → A-5. 기획서의 A-3 은 이미 「브라우저 실시간 전달(WebSocket)」이고 A-4 는 발화 구간 검출이다. decisions/201 초안이 같은 번호를 통번역에 붙여 한 ID 가 두 뜻을 갖게 됐다 — 추적성 규칙(rfp-harness §1.3)이 깨진다. 코드 주석에는 # Requirement: A-5 를 쓴다. 근거: _project/plan.md rev.5 요약.

범위 — ⓑ 어눌한 한국어만 (2026-08-28 확정)

_project/decisions/201 부록 A. 두 시나리오 중 ⓑ 만 1차 범위다.

  ⓐ 고객이 모국어로 말함 ⓑ 고객이 어눌한 한국어로 말함
상용 API 대체로 됨 가장 못하는 지점
평가 데이터 ❌ 못 찾았다 ✅ AI Hub 505 · 71479

ⓐ 와 상담원→고객 TTS 는 폐기가 아니라 여유 시 확장이다.

데이터 — 확보 경로가 있다

데이터셋 규모 비고
AI Hub dataSetSn=505 4,302시간 베트남·영어·일본·중국·태국어 모어 화자
AI Hub dataSetSn=71479 1,500시간 숙련도 4등급 라벨 + 발음오류 태깅

전량이 필요하지 않다. 학습이 아니라 측정이 목적이므로 정답 전사가 붙은 발화 수십~수백 건이면 WER/CER 이 나온다. 등급별로 고르게 뽑는 것이 규모보다 중요하다.

⚠ 반드시 함께 적을 한계

완료 조건

참고 — API 를 써도 데이터는 필요하다

Chirp 3 가 85개 이상 언어를 지원하므로 학습·파인튜닝은 불필요하다. 그러나 측정에는 필요하다 — 정답 전사가 붙은 외국인 화자 음성이 없으면 A-5 가 되는지 안 되는지 말할 수 없다(절대 원칙 10). API 는 “만들 데이터”를 없애지 “잴 데이터”를 없애지 않는다.


2026-09-09 — 절반은 막혔고 절반은 쟀다

막힌 것 — 외국인 화자 데이터가 없다

AI Hub 505·71479 를 아직 신청하지 않았다. 이 티켓의 본체(숙련도 등급별 WER/CER)는 그 데이터 없이는 한 걸음도 못 나간다. 지금 data/raw/ 에 있는 다섯 데이터셋은 전부 내국인 발화다(ktelspeech · krespspeech · lowquality-phone · minwon-qa · seoul-minwon-audio). 미결 항목의 “외국인 화자 한국어 음성 데이터 0건” 이 그대로 유효하다.

승인에 시간이 걸리므로 신청이 먼저다. 이 티켓을 in-progress 로 두는 이유가 그것이다.

잰 것 — 8kHz 협대역 페널티 (완료 조건 4)

외국인 데이터가 도착해 WER 이 나쁘게 나왔을 때 그것이 「어눌한 한국어라서」인지 「8kHz 로 낮춰서」인지 구분하려면 기준선이 필요하다. 그 교란 요인은 지금 가진 데이터로 먼저 분리할 수 있다.

측정일 2026-09-09 · 커밋 c9da0a3-dirty
.venv/bin/python scripts/measure_bandwidth_penalty.py --limit 20
데이터 aihub-krespspeech/validation (16kHz, 내국인) · 시드 20260909 · 표본 20건 · 오디오 260초

  original(16kHz)   WER 0.354 · CER 0.150 · n 20
  8000Hz            WER 0.358 · CER 0.129 · n 20
  차이              WER +0.004 · CER −0.021

⚠ 「8kHz 는 안전하다」가 아니라 「이 표본으로는 차이가 보이지 않는다」다. WER 은 8kHz 가 조금 나쁘고 CER 은 조금 좋다 — 방향조차 일정하지 않으므로 n=20 에서 두 조건의 차이를 검출하지 못한 것이다. 표본을 늘리면 달라질 수 있다.

⚠ 다운샘플링만 분리한 값이다. 실제 전화망에는 코덱 압축·잡음·라인 손실이 더해지므로 여기서 나온 값보다 나쁘다. 그대로 「8kHz 페널티」로 옮겨 적지 않는다(절대 원칙 10).

⚠ 44.1kHz 가 아니라 16kHz 를 원본으로 썼다. 티켓 원문은 44.1kHz 를 적었는데, 그건 AI Hub 505·71479 의 사양이고 지금 가진 krespspeech 는 16kHz 다. 데이터가 오면 그때 44.1 → 8 로 다시 잰다.

이번에 한 번 틀렸다 — 도구 버그가 결과처럼 보였다

첫 실행에서 WER 1.115 가 나왔다. 「8kHz 가 이만큼 나쁘다」로 읽힐 수치였지만 측정 도구의 버그였다 — AI Hub 파일이 전부 0001.wav 라(세션 폴더로 구분된다) 다운샘플 사본이 한 파일에 덮여 쓰였고, 캐시가 첫 번째 것을 20번 돌려줬다. 전사 원문을 열어 보고 20건이 전부 같은 문장인 것을 발견해 잡았다.

숫자만 보고 넘어갔으면 기획서에 틀린 수치가 들어갈 뻔했다. narrow_path() 독스트링에 경위를 남겼다(절대 원칙 8).

만든 것

ai/apps/evaluation/metrics/asr.py         WER/CER — 규칙 기반, 그룹별 집계 강제
ai/apps/evaluation/tests/test_asr_metrics.py
scripts/measure_bandwidth_penalty.py      협대역 실험 (COST-1 가드 재사용)

aggregate_by_group() 이 그룹별로 나눠 내도록 강제한다 — 이 티켓의 완료 조건이 “전체 평균 하나로 뭉개지 않는다” 이기 때문이다. 데이터가 도착하면 숙련도 등급을 그룹으로 넣기만 하면 된다.

AI Hub 라벨 규약((A-4)/(에이 다시 사) 이중 전사 · n/ 잡음 표시)을 정규화하지 않으면 STT 가 무엇을 뱉든 전부 오류가 된다. 적용한 규칙 5개를 NORMALIZATION_RULES 로 드러내 두었고 리포트에 함께 찍는다 — 정규화는 점수를 올리는 조치라 감추면 안 된다.

비용

STT 사용 520초(오늘). .env 의 STT_MAX_SECONDS_PER_DAY=600 안. 첫 실행이 버그로 260초를 헛되이 썼다 — 재실행분 260초와 합쳐 520초다. data/processed/stt-usage.json 에 누적된다.

다음

  1. AI Hub 505 또는 71479 신청 ← 여기서 막혀 있다. 승인에 시간이 걸린다
  2. 수령 후 숙련도 등급별로 표본 추출(등급당 20건 이상) → aggregate_by_group 에 넣는다
  3. 왕복 지연(STT→번역→화면) 실측 — 아직 안 했다
  4. 결과가 나쁘면 A-5 를 코어에서 뺄지 팀에 알린다

2026-09-15 — 상태 그대로다

AI Hub 505·71479 신청이 여전히 안 됐다. 신청은 AI Hub 계정으로 사람이 해야 하는 일이라 이번 세션에서 할 수 없었다. 외국인 화자 음성이 0건인 상태에서 이 티켓으로 잴 수 있는 것은 없다(09-09 에 8kHz 페널티 분리만 끝냈다). 데이터가 오기 전에는 「측정 불가 — 표본 없음」이다.

2026-09-21 — AI Hub 71479 파일 목록 확인 (신청 전)

정성윤 님 지시로 다운로드를 맡았다. 로그인 상태에서 「파일 목록 (API 다운로드)」 탭을 읽었다 — 299.78 GB 전체가 아니라 Validation 만 받는다(data/README.md 규칙). 승인은 소개 페이지의 「다운로드」 버튼 → 이용 신청으로 받는다(내국인만 신청 가능 — 본인 계정).

파일 용량 filekey 받나
VL_Speech_lab.zip (Validation 라벨 · 발화) 15.30 MB 475451 먼저 — 숙련도 등급 필드 확인
VL_pronunciation_lab.zip (라벨 · 발음) 54.33 MB 475452 먼저
VL_pronunciation_tag.zip (발음 오류 태깅) 34.61 MB 475453 먼저
VS_Speech_sound.zip (원천 · 발화) 19.91 GB 475449 라벨 확인 뒤 — WER/CER 은 이쪽
VS_pronunciation_sound.zip (원천 · 발음) 13.17 GB 475450 보류 — 발음 평가용이라 1차 범위 밖
Training 원천 5개 · 라벨 3개 266 GB 475441~475448 안 받는다

2026-09-21 — 71479 적재 완료 · 라벨 구조 확인

2026-09-21 — 실측 · 숙련도 등급별 WER/CER

측정일 2026-09-21 · 커밋 97f7b25-dirty(스크립트·티켓이 아직 커밋 전) · 시드 20260921
PYTHONPATH= .venv/bin/python scripts/measure_a5_proficiency.py --dry-run   # 표본·482초 확인
PYTHONPATH= .venv/bin/python scripts/measure_a5_proficiency.py
데이터 aihub-foreign-proficiency-71479/validation · 표본 80건(등급당 20 · 3~6초 · 화자당 ≤2)
STT Google Cloud Speech v1 recognize · ko-KR · 기본 모델 · 자동 구두점 (09-09 와 같은 호출)
STT 사용 482.3초(8 kHz 387.1 + 원본 95.3) · 장부 오늘 0 → 482.3/600 · 이달 513.2 → 995.6/3600

주 측정 — 8 kHz (정규화 후 / 정규화 전)

등급 n 화자 초 WER CER WER(정규화 전) CER(정규화 전)
Beginner 20 20 97.4 0.412 0.221 0.431 0.212
Intermediate 20 20 90.6 0.266 0.198 0.287 0.190
Advance 20 19 102.3 0.225 0.087 0.234 0.087
Fluent 20 16 96.8 0.198 0.067 0.216 0.078
(참고) 전체 80 75 387.1 0.273 0.139 0.289 0.138

등급이 내려갈수록 나빠지는 방향은 일관되고, Beginner 는 단어 다섯 중 둘을 틀린다(CER 도 Advance 의 2.5배). Intermediate 와 Advance 는 WER 로는 가깝고 CER 로 갈린다(0.198 vs 0.087) — 초급·중급은 음절 단위로 틀리고 고급은 띄어쓰기·조사 정도라는 뜻이다. 빈 가설(아무것도 못 알아들음) 0건.

정규화 전 CER 이 낮아 보이는 것은 분모에 문장부호 글자가 들어가서다 — 좋아진 것이 아니다. 정규화가 WER 을 올린 폭은 등급마다 0.01~0.02 — 정규화가 결과를 만든 것은 아니다.

원본(48 kHz) vs 8 kHz — 같은 20건(등급당 5), 정규화 후

등급 n WER 원본 → 8 kHz CER 원본 → 8 kHz
Beginner 5 0.400 → 0.560 (+0.160) 0.250 → 0.281 (+0.031)
Intermediate 5 0.263 → 0.263 (±0) 0.179 → 0.196 (+0.018)
Advance 5 0.160 → 0.240 (+0.080) 0.047 → 0.109 (+0.062)
Fluent 5 0.261 → 0.261 (±0) 0.033 → 0.033 (±0)
전체 20 0.272 → 0.337 (+0.065) 0.127 → 0.155 (+0.029)

전사가 두 조건에서 같은 것이 20건 중 13건, 다른 것이 7건. 방향은 8 kHz 쪽이 나쁘다(09-09 내국인 표본에서는 방향조차 안 보였다) — 하지만 등급당 5건이라 등급별 숫자는 표본 잡음 안이다. 전체 +0.065 만 인용한다.

⚠ 이 수치는 상한이다 — 함께 옮겨 적는다

  1. 온라인 녹음(48 kHz, 잡음 없음)을 다운샘플만 했다. 실제 전화망의 코덱·잡음·라인 손실이 없다 → 실제 통화는 이보다 나쁘다.
  2. 민원 어휘가 아니다 — 과제가 낭독(LAR 55건)·질문답변(ATQ 25건)이다. 낭독은 같은 문장을 여러 화자가 읽어 정답 문장이 80건 중 70종이다.
  3. 3~6초 짧은 발화만 골랐다(데이터 중앙값 7.5초) — STT 일 캡 600초 때문이다. 긴 발화는 다를 수 있다.
  4. 모어 편중 — 인도네시아 29 · 베트남 21 · 몽골 11 · 그 외 19. 데이터셋 자체의 분포다(중·일어 화자는 없다).
  5. 등급당 n=20 — 등급 간 차이가 표본 잡음보다 작을 수 있다. 원본 재측정은 등급당 5건뿐이다.
  6. 정답 전사가 학습자의 오류 발화를 그대로 적는다(지나 생일에·용화가 좋). STT 가 그럴듯하게 「교정」하면(지난 생일을) 오류로 잡힌다 — 우리 목적(상담원이 알아듣기)에는 그 교정이 오히려 도움일 수 있어, WER 이 곧 「못 알아들은 정도」는 아니다.
  7. 숫자 표기(라벨 십오년 vs STT 15년)·% 는 정규화하지 않았다. 71479 라벨용으로 추가한 규칙 2개(제어 문자 \x08 제거 · 굽은따옴표 → 공백)는 EXTRA_NORMALIZATION_RULES 로 드러냈고 JSON 에도 들어 있다.
  8. 모델이 Chirp 3 가 아니라 v1 기본 모델이다(위 완료 조건 3).

목표치는 정하지 않았다 — 이것은 측정값이고, 「A-5 를 코어에 두는가」는 팀이 이 표를 보고 정한다.

파일

scripts/measure_a5_proficiency.py             표본 추출(층화·시드·화자 상한·길이 창) → unzip 한 건씩 → 8 kHz → STT → aggregate_by_group
scripts/tests/test_measure_a5_proficiency.py  순수 함수 8건 (.venv/bin/python -m pytest scripts/tests -q)
data/processed/a5/2026-09-21-proficiency.json 건별 정답·가설·조건 (gitignore — AI Hub 재배포 금지)
data/processed/a5/{wav,8k,stt}/               꺼낸 원본 80개 · 8 kHz 사본 · 전사 캐시(내용 해시 — 재실행이 예산을 다시 쓰지 않는다)
jekyll/assets/a5-wer-2026-09-21.json          공개 요약 — 등급별 n·WER·CER·초·조건 3종×정규화 전/후·한계. 건별 전사 없음

다음

2026-09-21 — 닫음 (내 몫 끝)

스파이크의 목적(「잴 수 있는가」)에 답이 나왔다 — 잴 수 있고, 쟀다(위 실측 절). 남은 둘은 이 티켓 안에서 할 수 없다: ① 왕복 지연(STT→번역→화면)은 번역 단계가 아직 없어 잴 파이프라인이 없다 ② A-5 를 코어에 둘지는 팀 판단(재료: Beginner WER 0.41 — 상한). Chirp 3(v2) 재측정은 8 kHz 사본·표본이 그대로 있어 다음 날 캡으로 ~390초면 된다(--min-sec/--max-sec 그대로, 스크립트에 모델 인자만). 셋 다 미결.

2026-09-21 — Chirp 3 재측정 시도: 권한에 막혔다 (STT 0초 사용)

measure_a5_proficiency.py 에 --stt-model {v1,chirp_3,chirp_2} · --region · --probe(1건만) 를 붙이고 v2 호출(transcribe_batch.transcribe_v2)을 넣었다. 같은 표본·같은 8 kHz 사본을 모델만 바꿔 재도록 했고, 캐시 키·결과 파일에 모델 이름이 붙어 v1 결과와 섞이지 않는다(테스트 3건 추가, 11 통과). 일 캡은 사용자 지시로 이 실행에만 프로세스 환경변수 STT_MAX_SECONDS_PER_DAY=900 으로 올리고 .env 는 그대로 뒀다(리포트 cap_override 에 남는다).

--probe 1건이 403 PERMISSION_DENIED speech.recognizers.recognize 로 실패했다 — us(chirp_3) · us-central1 · asia-southeast1(chirp_2) 모두 같다. v2 API 는 v1 과 달리 서비스 계정에 「Cloud Speech 클라이언트」(roles/speech.client) 역할이 있어야 한다. 프로젝트 IAM 변경은 GCP 관리자(정성윤 님) 몫이라 하지 않았다. 역할이 붙으면 아래 한 줄이다(약 390초 — 그날 캡 안이면 캡 상향 불필요):

PYTHONPATH= .venv/bin/python scripts/measure_a5_proficiency.py --stt-model chirp_3 --region us --original-subset 0 --probe   # 1건 확인
PYTHONPATH= .venv/bin/python scripts/measure_a5_proficiency.py --stt-model chirp_3 --region us --original-subset 0