STT 오류 주입기 — 성공 조건의 나머지 절반
무엇을
전사 텍스트에 합성 STT 오류를 주입해 오류율 0~20% 구간에서 검색 품질 곡선을 낸다. 4.2절 STT 오류 내성 실험.
왜 — 티켓조차 없던 것을 만든다
_project/STATE.md 「지금 막혀 있는 것」이 「오류 주입기가 없다 · 미배정 ·
성공 조건의 절반인데 티켓조차 없다」로 적어 둔 항목이다. 성공 조건은 F-2 가 아니라
STT 오류 내성 실험 + 검색 품질 개선 수치인데(CLAUDE.md §1) 그 절반이 착수조차
안 돼 있었다.
텍스트 레벨이라 STT 실물 없이 만들 수 있다 — data/README.md 도 “4.2절 실험은 이
데이터를 쓰지 않는다 — 텍스트 레벨 합성 오류 주입” 이라고 적고 있다. 막을 것이 없다.
어떻게 — 오류를 지어내지 않는다
⚠ 주입할 오류의 종류를 실측에서 가져온다. 아무 글자나 바꾸면 그건 STT 오류가 아니라 오타이고, 그 위에서 낸 곡선은 아무것도 말하지 않는다(절대 원칙 10).
근거로 쓸 수 있는 것 —
w1-v3-v4-stt실측 — 숫자 출력 형태·부분 결과 지연scripts/measure_bandwidth_penalty.py결과(data/processed/bandwidth-penalty/result.json) — 2026-09-09 에 정답 전사 ↔ Chirp 출력 20쌍을 실제로 저장해 뒀다. 여기서 실제 오류 유형을 뽑아낼 수 있다("파리바게트"→"파리바게뜨"·"폰이 꺼져서"→"폰이 이뻐져서"· 조사 탈락 · 띄어쓰기 이동)ai/apps/evaluation/metrics/asr.py의 WER/CER 로 주입한 오류율을 실제로 검증한다 — 「10% 주입」이라고 말하려면 그 결과가 정말 WER 0.10 인지 재야 한다
완료 조건
- 오류 유형이 실측에서 왔다는 근거가 코드 주석에 있다 (지어낸 유형 0건) —
error_injection/profile.py - 시드를 받아 같은 시드면 같은 결과 — 재측정 비교가 성립해야 한다
- 주입 후 WER 을 재서 목표 오류율과 실제가 맞는지 검증한다
- 오류율 0 · 5 · 10 · 15 · 20% 에서 Recall@5 · MRR 곡선을 낸다 (골든셋 v1-150) — 모델 학습 후 진행 예정
- C-5 마스킹 재현율 곡선도 같은 구간에서 — 구분자 부재가 주 실패 모드다(2.4절) — 모델 학습 후 진행 예정
- 수치에 측정일·커밋·명령·표본 수 네 가지가 붙어 있다(§5) — 곡선을 낼 때
- 곡선이 나쁘면 그대로 적는다 — 「오류에 강하다」를 목표로 두지 않는다
2026-09-14 — 주입기까지 만들었다. 곡선은 아직 안 냈다
프로파일 — 손으로 고르지 않았다
분류 규칙을 코드로 고정하고(error_injection/classify.py) 실측 40쌍 전체에 돌렸다.
.venv/bin/python scripts/build_stt_error_profile.py
출처 bandwidth-penalty/result.json · 측정일 2026-09-09 · 20발화 × 2조건(16kHz·8kHz) = 40쌍 · 정답 단어 452 · 편집 구간 81
spacing_split 12 · spacing_merge 2 · word_delete 3 · number_verbalize 2 · jamo_sub 17 · ending_drop 7
ending_add 1 · word_insert 0 · unmodeled 37
흉내 내지 못하는 몫이 46.9%(38/81)다 — 환청·의미 붕괴·여러 자모가 바뀐 치환. 주입기는 넣을 말을 지어내야 하는 유형을 넣지 않으므로 같은 WER 에서 실제 STT 보다 덜 파괴적이다 → 곡선은 낙관 쪽으로 기운다. 곡선을 낼 때 이 비율을 옆에 적는다.
캘리브레이션 (주입기 검증 — 품질 수치 아님)
골든셋 v1-150 고객 발화 136건 · 단어 815 · 시드 20260914 · 커밋 9970fba-dirty
목표 0.00 → 실제 0.0000 (편집 0) 목표 0.05 → 0.0503 (34) 목표 0.10 → 0.1018 (66)
목표 0.15 → 0.1509 (103) 목표 0.20 → 0.2000 (138)
한 건씩 넣으며 재므로 넘쳐도 편집 1건 몫이다. 채점기(metrics/asr.score_pairs)로 다시 재도 같은 값이다(테스트가 고정).
이번에 틀린 것 둘
- 숫자 읽기를 과대 집계했다. 「숫자가 한쪽에만 있다」만 봤더니 숫자로 시작해 문장 전체가 무너진 환청까지
number_verbalize로 셌다(6건 → 실제 2건). 흉내 낼 수 있는 몫이 부풀어 한계가 작게 보일 뻔했다. 「숫자·수사를 뺀 나머지가 비슷하다」를 더했다 - 주입본이 정규화된 텍스트였다. 오류율 0% 에서도
010-2345-6789의 하이픈이 사라졌다 — C-5 곡선의 주 실패 모드가 「구분자 부재」라 0% 지점부터 교란이 들어간다. 편집은 원문에, 정규화는 WER 을 잴 때만 쓰게 고쳤다
남은 한계
- 자모 혼동이 문맥 없이 적용돼 「이곴」처럼 실제로 없는 음절이 나온다
- 표본이 작다(자모 혼동 11종 17건). 두 조건은 같은 발화라 독립이 아니다. 내국인 낭독·질의 발화다 — 전화망·외국인·민원 대화의 분포가 아니다
주의
목표 수치를 코드에 하드코딩하지 않는다. 6.1절이 오류 10% 구간 Recall@5 ≥0.60 을 걸어 뒀지만, 그건 판정선이지 주입기가 맞춰야 할 값이 아니다.
2026-09-15 — 곡선을 냈다. 결과는 곡선 티켓 두 장에 있다
- 검색: w5-error-tolerance-curve — 10% 에서 bm25 0.812 · dense·rerank 0.979
- C-5: w5-masking-recall-curve — 규칙+NER 누락 0 → 1(5%) → 2(10~20%)
- 명령
scripts/measure_error_tolerance.py(시드 3개 · 점마다 최저치) - 「모델 학습 후 진행 예정」 메모를 뗐다. 곡선 경로에 학습 대상 모델이 없어(주입기 · BM25 · 사전학습 KoE5 · 리랭커 · 사전학습 NER) 기다릴 이유가 없었다. 학습 모델이 올라와 경로가 바뀌면 같은 명령으로 다시 잰다
- C-5 곡선을 재다 하네스 채점의 구멍(부분 마스킹을 통과로 셈)을 찾아 고쳤다 — 곡선 티켓 참고