Metadata-Version: 2.5
Name: kocer
Version: 0.1.3
Summary: 한국어 STT를 공정하게 채점하는 CER/WER — 띄어쓰기·숫자·문장부호 차이를 오류로 세지 않음
Project-URL: Repository, https://github.com/Choi-kwan-u/kocer
Author: Choi Kwan-u
License-Expression: MIT
License-File: LICENSE
Keywords: asr,cer,evaluation,korean,stt,wer
Classifier: Natural Language :: Korean
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: rapidfuzz; extra == 'dev'
Provides-Extra: fast
Requires-Dist: rapidfuzz; extra == 'fast'
Description-Content-Type: text/markdown

# kocer

한국어 STT를 공정하게 채점하는 CER — 띄어쓰기·숫자·문장부호·영문 약어 표기 차이를 오류로 세지 않습니다.

*Fair CER/WER for Korean speech recognition. Spacing, punctuation, number notation (3개 ↔ 세 개) and Latin acronyms (AI ↔ 에이아이) are not counted as errors. Unlike jiwer and nlptutti, kocer scores numbers against every valid Korean reading and keeps the original CER alongside. Pure Python, zero dependencies, `jiwer`-compatible API.*

```
ref: 사과 세 개 주세요
hyp: 사과 3개 주세요
jiwer CER 0.200   nlptutti CER 0.143   kocer CER 0.000
```

## 공개 모델 재채점: 같은 출력, 네 가지 채점

공개 한국어 ASR 6개 설정을 Zeroth 테스트(457발화)와 FLEURS ko 테스트(382발화)에서 같은 조건(greedy, 언어 고정, 프롬프트 없음)으로 돌리고, **모델 출력은 그대로 둔 채 채점만** 네 단계로 바꿨습니다. 각 단계는 정답과 출력에 똑같이 적용됩니다. 단위 %, 두 데이터셋 합산(총 오류 / 총 정답 글자).

| 모델 | raw | 공백 제거 | 공백·부호 제거 | kocer | 순위 raw → 공백·부호 → kocer |
|---|---|---|---|---|---|
| TeamUNIVA/qwen3_asr_1.7b_ko_beta | 7.79 | 7.51 | 4.94 | **3.35** | 4 → 3 → **1** |
| Qwen/Qwen3-ASR-1.7B | 6.86 | 6.44 | 4.13 | 3.58 | 1 → 1 → 2 |
| openai/whisper-large-v3 | 8.77 | 6.59 | 4.62 | 3.62 | 6 → 2 → 3 |
| BuzzASR/korean | 7.98 | 7.68 | 5.34 | 3.91 | 5 → 6 → 4 |
| FunAudioLLM/SenseVoiceSmall (itn 끔) | 7.07 | 7.59 | 5.28 | 4.57 | 2 → 4 → 5 |
| FunAudioLLM/SenseVoiceSmall (itn 켬) | 7.23 | 7.76 | 5.30 | 4.57 | 3 → 5 → 6 |

열끼리 비율로 비교하면 안 됩니다. 공백을 지우면 분모가 25% 줄어서, 띄어쓰기를 이미 잘 맞춘 모델은 CER이 오히려 오릅니다(SenseVoice). 그래서 오류 **개수**로 다시 봅니다.

| 모델 | raw 오류 수 | 띄어쓰기 | 문장부호 | 숫자·약어 (kocer만 처리) | 진짜 오류 | 표기 차이 비율 |
|---|---|---|---|---|---|---|
| TeamUNIVA ko | 3,846 | 26% | 26% | 15% | 32% | 68% |
| Qwen3-ASR-1.7B | 3,388 | 28% | 27% | 6% | 39% | 61% |
| whisper-large-v3 | 4,327 | 42% | 18% | 9% | 31% | 69% |
| BuzzASR/korean | 3,938 | 26% | 24% | 13% | 37% | 63% |
| SenseVoice (itn 끔) | 3,488 | 18% | 26% | 8% | 48% | 52% |
| SenseVoice (itn 켬) | 3,567 | 18% | 28% | 7% | 47% | 53% |

- raw CER이 오류로 센 것의 **52~69%는 표기 차이**였습니다. 대부분은 띄어쓰기와 문장부호이고, 이 둘만 지워도(nlptutti 등이 이미 하는 일) whisper-large-v3가 꼴찌에서 2위로 올라옵니다.
- 숫자·영문 약어는 kocer만 처리하는 몫입니다. 전체 raw 오류의 6~15%지만 상위권 순위를 다시 바꿉니다. 한국어 파인튜닝 모델 TeamUNIVA는 raw에서는 기본 모델보다 나빠 보이다가 kocer에서 1위가 되고, 그 차이(0.24%p)는 문장 단위 paired bootstrap 2,000회에서 95% 구간 [0.08, 0.39]로 0을 벗어납니다. Zeroth 정답이 숫자를 전부 한글로 적는데 이 모델은 아라비아 숫자를 자주 뱉어서, Zeroth raw 오류의 24%가 숫자 표기였습니다.
- Qwen3-ASR과 whisper-large-v3의 kocer 차이(0.04%p)는 유의하지 않습니다. 순위 구간은 TeamUNIVA [1,1], Qwen [2,3], whisper [2,3], BuzzASR [4,4], SenseVoice [5,6]입니다.

**정규화가 진짜 오류를 숨기지 않나.** raw > 0이고 kocer = 0인 문장을 전체에서 모아(중복 제거 1,325건) 무작위 100건을 정답과 나란히 읽은 1차 검수에서, 100건 모두 표기 차이였고 실제 오류가 숨겨진 경우는 0건이었습니다. 1차 판정은 AI 어시스턴트가 했고 저자 재확인은 아직입니다. 목록은 [benchmarks/results/fake_review.tsv](benchmarks/results/fake_review.tsv)에 있습니다. 띄어쓰기 차이가 동형이의어를 만드는 경우(`세 배`/`세배`)는 공백을 지우는 모든 CER이 0으로 세며, kocer도 같습니다.

**안 한 것.** Common Voice ko는 접근 제한 때문에 빠졌습니다. BuzzASR가 whisper를 가장 크게 이긴다고 보고한 데이터셋이라, BuzzASR 대 whisper 비교는 여기서 결론 내리지 않습니다. 디코딩은 전 모델 greedy로 각 모델의 최고점 설정이 아닙니다. 데이터셋은 각 1시간 안팎이라 0.3%p 이하 차이는 위 신뢰구간과 함께 읽어야 합니다.

모델 출력 전체(jsonl), 추론·채점 스크립트, 부트스트랩 코드, 전체 표는 [benchmarks/](benchmarks/)에 있습니다. kocer 0.1.3으로 다시 채점하면 표의 kocer 열이 소수점 둘째 자리까지 재현됩니다.

## 기존 도구와 무엇이 다른가

한국어 STT 채점에는 보통 [jiwer](https://github.com/jitsi/jiwer)나 [nlptutti](https://github.com/hyeonsangjeon/computing-Korean-STT-error-rates)를 씁니다. 셋 다 같은 편집거리로 CER을 계산합니다. 다른 점은 **무엇을 오류로 세느냐**입니다.

- **jiwer**는 언어를 가리지 않는 범용 도구입니다. 띄어쓰기와 문장부호도 기본으로 오류로 셉니다.
- **nlptutti**는 한국어용입니다. 띄어쓰기와 문장부호는 무시하지만, 숫자와 영문 표기 차이는 오류로 셉니다.
- **kocer**는 여기에 더해 숫자 읽기(3개 = 세 개)와 영문 약어 읽기(AI = 에이아이)까지 같은 말로 봅니다.

### 같은 문장, 세 도구의 CER

2026-10-24 실측. jiwer 4.0.0, nlptutti 0.0.0.24, kocer 0.1.2, 모두 기본 설정.

| 정답 | STT 결과 | jiwer | nlptutti | kocer |
|---|---|---|---|---|
| 네, 알겠습니다. | 네 알겠습니다 | 0.222 | 0.000 | 0.000 |
| 오늘날씨가좋다 | 오늘 날씨가 좋다 | 0.286 | 0.000 | 0.000 |
| 사과 세 개 주세요 | 사과 3개 주세요 | 0.200 | 0.143 | **0.000** |
| 삼 층으로 가세요 | 3층으로 가세요 | 0.222 | 0.143 | **0.000** |
| 세 시 삼십 분 | 3시 30분 | 0.625 | 0.600 | **0.000** |
| 이천이십사 년 십이 월 | 2024년 12월 | 0.750 | 0.778 | **0.000** |
| 오천 원 | 5,000원 | 1.250 | 0.800 | **0.000** |
| 오십 퍼센트 | 50% | 1.000 | 1.000 | **0.000** |
| 에이아이 모델 | AI 모델 | 0.571 | 0.667 | **0.000** |
| 나사 우주선 | NASA 우주선 | 0.667 | 0.571 | **0.000** |
| 사과 세 개 주세요 | 사과 **네** 개 주세요 | 0.100 | 0.143 | 0.143 |
| 세 시 삼십 분 | 3시 **13**분 | 0.625 | 0.600 | 0.400 |

마지막 두 줄은 진짜 인식 오류입니다. kocer도 오류로 셉니다. "세 시 삼십 분"과 "3시 13분"에서 kocer가 낮은 이유는 "3시"는 맞게 인식한 부분이라 감점하지 않고, 틀린 "13분"만 세기 때문입니다.

### 기능 비교

| | jiwer | nlptutti | kocer |
|---|---|---|---|
| 띄어쓰기·문장부호 무시 | 직접 설정 | 기본 | 기본 |
| 숫자 읽기 차이 무시 (3개 = 세 개) | 없음 | 없음 | 기본 |
| 영문 약어 읽기 차이 무시 (AI = 에이아이) | 없음 | 없음 | 기본, 사용자 사전 지원 |
| 정규화 전·후 CER을 한 번에 반환 | 없음 | 없음 | `raw_cer`, `cer` |
| 여러 모델 비교 리포트, 품질 게이트 | 없음 | 있음 | 없음 |
| 개체명·키워드 오류율 | 없음 | 있음 | 없음 |
| 지원 지표 | WER, CER, MER, WIL, WIP | CER, WER, CRR | CER, WER |
| 필수 의존성 | rapidfuzz, click | jiwer | 없음 |

**이럴 때 kocer를 쓰세요.** 정답 전사와 STT 결과의 숫자·영문 표기 방식이 다를 때입니다. 예를 들어 정답은 한글로 읽은 대로 적었는데 모델은 아라비아 숫자로 출력하는 경우입니다. 이 차이를 오류로 세면 모델 간 비교가 표기 습관 비교가 됩니다.

**이럴 때는 nlptutti가 낫습니다.** 여러 모델의 리포트를 만들거나, 개체명·키워드 단위로 평가하거나, 기준 점수로 CI를 막고 싶을 때입니다. 두 도구를 같이 써도 됩니다.

## 설치

```bash
pip install kocer          # 순수 파이썬, 의존성 없음
pip install kocer[fast]    # rapidfuzz로 채점 수십 배 빠름. 긴 문장(자막·회의록)을 많이 재면 권장
```

## 사용

```python
import kocer

kocer.cer("사과 세 개 주세요", "사과 3개 주세요")   # 0.0
kocer.cer(refs, hyps)                                # 리스트도 됨 (jiwer와 같은 코퍼스 집계)
kocer.wer(ref, hyp)

r = kocer.measure("세 시 삼십 분", "3시 30분")
r.cer         # 0.0  정규화 후
r.raw_cer     # 0.625  정규화 전 (jiwer와 같은 값). 둘의 차이가 "가짜 오류"의 크기
r.ops         # {'S': 0, 'D': 0, 'I': 0}
r.alignment   # [('H', '세', '세'), ...]
r.chosen_ref  # 숫자 후보 중 실제로 채택된 정답
r.chosen_hyp  # '세시삼십분'

# 단어처럼 읽는 약어는 직접 넘김 (내장 사전 위에 덮어씀)
kocer.cer("엘지 트윈스", "LG 트윈스")                      # 0.0  철자 읽기
kocer.cer("케이비오 리그", "KBO 리그")                      # 0.0
kocer.cer("유니스트 입학", "UNIST 입학", aliases={"UNIST": "유니스트"})  # 0.0
```

```bash
kocer ref.txt hyp.txt          # 줄 단위 쌍, raw CER과 kocer CER을 같이 출력
kocer ref.txt hyp.txt --diff   # 틀린 글자만 색으로 표시
```

## 무엇을 무시하고, 무엇을 세는가

정규화는 순서대로 적용되고 각 단계를 끌 수 있습니다.

| 단계 | 기본 | 끄기 | 예 |
|---|---|---|---|
| 유니코드 NFC | 켬 | - | 자모 분리 입력 |
| 문장부호 제거 | 켬 | `punct=False` | `네, 알겠습니다.` = `네 알겠습니다` |
| 필러 제거 (음, 어, 그) | **끔** | `fillers=True` | 실제 단어 "그"도 지워질 수 있어 기본은 끔 |
| 영문 약어·단위 | 켬 | `letters=False` | `AI` = `에이아이`, `NASA` = `나사`, `50%` = `50퍼센트` |
| 숫자 후보 전개 | 켬 | `numbers=False` | `3개` = `세 개`, `3층` = `삼 층` |
| 공백 제거 | CER만 | - | WER은 공백 유지 |

`normalize=False`면 jiwer와 같은 순수 CER을 돌려줍니다.

### 숫자: 정답 하나가 아니라 후보 집합

숫자는 조수사에 따라 읽기가 달라서(3개=세 개, 3층=삼 층, 3시 30분=세 시 삼십 분) 규칙 하나로 정답을 만들 수 없습니다. kocer는 숫자마다 `{원문 숫자, 한자어 읽기, 고유어 읽기, 자릿수 읽기}` 후보를 만들고 CER이 최소가 되는 조합을 고릅니다.

- 조수사 사전([counters.py](src/kocer/counters.py))에 있으면 그 읽기만, **없으면 두 읽기 모두 허용**합니다. 틀리게 감점하는 것보다 관대한 쪽이 안전하다는 정책입니다.
- 반대 방향(ref가 한글 수사, hyp가 숫자)도 처리합니다.
- 전화번호·연도는 자릿수 읽기(010 = 공일공, 2024 = 이공이사)도 후보에 들어갑니다.
- 고유어 읽기는 1~99까지. `100개`는 `백 개`만 허용하고, `백스무 명`(120명)은 아직 안 됩니다.
- 고유어는 관형형(세, 스무)과 단독형(셋, 스물) 둘 다 후보에 넣습니다. 소수점은 `점`만 허용하고 구어 `쩜`은 받지 않습니다.
- 숫자 하나에 후보가 많아 조합이 64개를 넘으면 숫자 구간마다 따로 최적 후보를 고르는 근사를 씁니다.

### "너무 관대하다"는 비판에 대해

정규화는 실제 오류를 숨길 수 있습니다. 그래서 `raw_cer`를 항상 같이 돌려주고, 모든 단계를 끌 수 있게 했습니다. 두 값을 같이 보고하세요.

## 한계

- 대문자 연속은 철자로 읽습니다(`AI` = `에이아이`). 단어처럼 읽는 약어는 `aliases`로 넘기세요. `NASA`, `UNESCO`, `COVID` 등 16개만 내장되어 있습니다.
- 단위 기호는 `%`, `km`, `kg`, `cm`, `mm`, `ml`만 한글로 바꿉니다. `3m`은 `삼 미터`와 다른 것으로 봅니다.
- WER은 띄어쓰기 차이를 오류로 셉니다. 한국어 WER의 본질적 한계이고, CER을 권합니다.
- 조수사 사전은 완전하지 않습니다. 사전에 없는 조수사는 두 읽기를 다 허용합니다. 케이스를 [tests/cases.tsv](tests/cases.tsv)에 추가하는 PR을 환영합니다.
- 입력은 텍스트 쌍만 받습니다. 음성 파일·모델 실행은 다루지 않습니다.

## 라이선스

MIT
