STT(Speech-to-Text) 시스템의 한국어 출력에 대해 문자 오류율(CER), 단어 오류율(WER), 문자 정답률(CRR)을 계산하는 Python 패키지입니다. 예를 들어 Microsoft의 Azure Speech, Amazon Transcribe, Google Cloud Speech-to-Text 같은 클라우드 서비스와 OpenAI Whisper 같은 음성 인식 모델이 생성한 결과를 같은 기준으로 평가할 수 있습니다. 정답 문장(reference)과 인식 문장(hypothesis) 사이의 Levenshtein 최소 편집거리에서 치환, 삭제, 삽입 횟수를 계산합니다.
문자 오류율(CER/WER)은 자동 음성 인식 시스템의 성능에 대한 일반적인 메트릭입니다. CER은 WER(단어 오류율)과 유사하지만 단어 대신 문자에 대해 작동합니다. 자세한 내용은 WER 문서를 참조하십시오.[1] 문자 오류율은 다음과 같이 계산할 수 있습니다.
표준 CER/WER:
(S + D + I) / (S + D + C)
Nlptutti 기본 정규화 오류율:
(S + D + I) / (S + D + I + C)
- S: 치환(substitution)
- D: 삭제(deletion)
- I: 삽입(insertion)
- C: 올바르게 인식한 문자 또는 단어(hit)
호환성 정책:
rate_mode를 생략하면 기존 버전과 동일한rate_mode="normalized"가 적용됩니다. 표준 CER/WER가 필요한 경우에만rate_mode="standard"를 명시하십시오. 기본값은 기존 사용자 결과 보호를 위해 변경하지 않습니다.
클라우드와 오픈소스 위스퍼를 이용한 한국어 음성 텍스트 변환
- http://www.itdaily.kr/news/articleView.html?idxno=213297
- http://www.comworld.co.kr/news/articleView.html?idxno=50818
이 패키지를 만들게 된 배경과 한국어 ASR/STT 평가 실험을 정리한 프롤로그 글입니다.
- Korean: https://hyeonsangjeon.github.io/job-transcribe/
- English: https://hyeonsangjeon.github.io/job-transcribe/en/
설치부터 CER/WER/CRR, 코퍼스 평가, Entity CER·개체명 F1, 키워드 보존 평가, 오류 상세 분석까지 함수별 예제를 제공합니다.
- Korean manual: https://hyeonsangjeon.github.io/job-transcribe/nlptutti/
가장 간단한 사용 사례는 두 문자열 간의 편집 거리를 계산하는 것입니다.
pip install nlptutti- 기존 호출의 기본 계산식은 계속
rate_mode="normalized"입니다. 표준식은rate_mode="standard"를 직접 지정할 때만 사용합니다. - 유니코드 정규화는 기존 결과 보호를 위해 기본적으로 적용하지 않습니다. 필요한 경우
unicode_normalization="NFC"를 지정합니다. get_cer("", ""),get_wer("", "")처럼 참조문장과 가설문장이 모두 비어 있으면 완전 일치로 보고 오류율0.0을 반환합니다.- 참조문장은 비어 있고 가설문장만 있는 경우에는 전체를 삽입 오류로 계산합니다.
- CER/CRR 계산에서는
rm_punctuation값과 관계없이 공백을 제거합니다.rm_punctuation은 문장부호 제거 여부만 제어합니다. - 키워드 패턴은 정규식 특수문자를 포함한 키워드도 안전하게 처리하며, 긴 단어 내부의 부분문자열 오탐을 줄이기 위해 키워드 앞뒤 경계를 검사합니다.
evaluate_entities의 별칭은aliases에 직접 지정한 경우에만 정답으로 인정합니다. 발음 유사도나 퍼지 매칭은 자동으로 적용하지 않습니다.calculate_keyword_error_rate_with_pattern은 참조문장 리스트와 STT 결과 리스트의 길이가 다르면ValueError를 발생시킵니다.
import nlptutti as metrics
refs = "아키택트"
preds = "아키택쳐"
result = metrics.get_cer(refs, preds)
# result -> {"cer": 0.25, "substitutions": 1, "deletions": 0, "insertions": 0}import nlptutti as metrics
refs = "제이 차 세계 대전은 인류 역사상 가장 많은 인명 피해와 재산 피해를 남긴 전쟁이었다."
preds = "제이차 세계대전은 인류 역사상 가장많은 인명피해와 재산피해를 남긴 전쟁이었다."
result = metrics.get_cer(refs, preds)
cer = result['cer']
substitutions = result['substitutions']
deletions = result['deletions']
insertions = result['insertions']
# prints: [cer, substitutions, deletions, insertions] -> [CER = 0 / 34, S = 0, D = 0, I = 0]import nlptutti as metrics
refs = "대한민국은 주권 국가 입니다."
preds = "대한민국은 주권국가 입니다."
result = metrics.get_wer(refs, preds)
wer = result['wer']
substitutions = result['substitutions']
deletions = result['deletions']
insertions = result['insertions']
# prints: [wer, substitutions, deletions, insertions] -> [WER = 2 / 4, S = 1, D = 1, I = 0]import nlptutti as metrics
refs = "제이 차 세계 대전은 인류 역사상 가장 많은 인명 피해와 재산 피해를 남긴 전쟁이었다."
preds = "제이차 세계대전은 인류 역사상 가장많은 인명피해와 재산피해를 남긴 전쟁이었다."
result = metrics.get_crr(refs, preds)
crr = result['crr']
substitutions = result['substitutions']
deletions = result['deletions']
insertions = result['insertions']
# prints: [crr, substitutions, deletions, insertions] -> [CRR = 1 - (0 / 34), S = 0, D = 0, I = 0]기존 코드와 같은 값이 필요하면 옵션을 생략합니다. 표준 CER/WER를 보고할 때만 rate_mode="standard"를 지정합니다.
import nlptutti as metrics
default_result = metrics.get_cer("STEAM", "STREAM")
standard_result = metrics.get_cer("STEAM", "STREAM", rate_mode="standard")
print(default_result["cer"]) # 0.16666666666666666
print(standard_result["cer"]) # 0.2여러 문장을 한 번에 평가하고 micro/macro CER·WER와 문장 오류율을 확인합니다.
import nlptutti as metrics
report = metrics.evaluate_corpus(
["가나", "다라"],
["가마", "다라바"],
)
print(report["cer"]["micro"]) # 0.4
print(report["cer"]["macro"]) # 0.41666666666666663전체 문장 CER와 별도로 회사명, 사람 이름, 상품명처럼 중요한 개체명 구간의 문자 오류율과 언급 보존 성능을 함께 평가합니다. 논문의 Named Entity WER(NE-WER)처럼 참조 개체명 span에 정렬된 오류만 Entity CER로 집계하되, 한국어에서는 단어 경계의 영향을 줄이기 위해 문자 단위로 계산합니다.[3] 개체명의 추가 인식과 누락은 precision, recall, F1으로 별도 집계합니다.
import nlptutti as metrics
report = metrics.evaluate_entities(
["삼성전자의 갤럭시 S26 발표"],
["삼성전다의 갤럭시 에스 이십육 발표와 애플"],
{
"ORG": ["삼성전자", "애플"],
"PRODUCT": ["갤럭시 S26"],
},
aliases={
"갤럭시 S26": ["갤럭시 에스 이십육"],
},
)
print(report["entity_cer"]["micro"]) # 0.1
print(report["summary"]["f1"]) # 0.5
print(report["labels"]["PRODUCT"]["f1"]) # 1.0
print([(e["type"], e["entity"]) for e in report["errors"]])
# [("misrecognition", "삼성전자"), ("addition", "애플")]- Entity CER: 참조 개체명 span 내부의 치환·삭제·삽입을 문자 단위로 계산합니다.
micro는 전체 편집 횟수 기반,macro는 개체명 언급별 평균입니다. - 계산식 선택: 기존 사용자 호환성을 위해 기본값은
rate_mode="normalized"입니다. 논문의 NE-WER처럼 참조 개체명 문자 수를 분모로 보고하려면rate_mode="standard"를 지정합니다. Entity CER는 NE-WER의 문자 단위 응용이며 논문 구현을 그대로 재현한 지표는 아닙니다. - 개체명 F1: 정확한 개체명 언급과 명시적으로 허용한 별칭을 기준으로 추가·누락을 계산합니다.
- 한국어 처리: 개체명 내부 띄어쓰기와 기본 조사·어미 결합을 허용합니다. 조사는 Entity CER span에서 제외됩니다.
- 별칭 정책:
aliases는 숫자 읽기나 영문 표기의 허용 가능한 전사형처럼 실제로 같은 개체인 표현만 등록합니다. 등록하지 않은 유사 표현은 오류입니다. - 오탐 해석: 참조에 없는 개체명의 추가 인식은 Entity CER가 아니라 precision/F1과
errors의addition으로 확인합니다. - 모델 범위: 이 함수는 NER 모델을 실행하지 않습니다. 사용자가 제공한 개체명 사전을 평가합니다.
NE-WER는 일반 WER를 참조 개체명 단어에 제한한 지표로 설명되며, 최근 Spoken NER 연구의 NEER도 전체 WER를 대체하기보다 도메인 핵심어 분석을 보완하는 지표로 다룹니다.[3][4] ASR 오류와 개체명 인식 오류의 관계를 분석한 연구도 있어 Entity CER와 F1을 함께 확인하는 편이 안전합니다.[5]
논문 설명뿐 아니라 실제 계산 코드를 확인하려면 아래 공개 구현을 함께 참고할 수 있습니다. 이름이 비슷한 지표라도 입력 형식, 매칭 정책, 계산 단위가 다르므로 점수를 그대로 서로 비교해서는 안 됩니다.
| 공개 구현 | 실제 제공 기능 | Nlptutti와의 차이 |
|---|---|---|
| ContextASR-Bench 평가 코드 / NVIDIA NeMo-Skills 구현 | 개체명 목록을 이용해 WER, 퍼지 매칭 기반 NE-WER, 정확 일치 기반 NE-FNR 계산 | ContextASR는 개체명 단어열을 퍼지 추출한 뒤 WER를 계산합니다. Nlptutti는 퍼지 매칭을 기본으로 사용하지 않고 한국어 문자 span과 명시적 aliases를 평가합니다. |
Teklia ie-eval (ECER/EWER 문서) |
BIO 정답·예측 파일로 ECER/EWER와 유형별·순서 독립 점수 계산 | Teklia는 이미 태깅된 NER 출력을 입력받습니다. Nlptutti는 원문 reference/hypothesis와 사용자가 제공한 개체명 사전을 입력받으며 NER 모델을 실행하지 않습니다. |
| PIER | 전체 문장을 정렬한 뒤 참조의 관심 단어 위치에 해당하는 편집만 계산 | PIER는 코드 스위칭 관심 단어를 단어 단위로 평가합니다. Nlptutti Entity CER는 같은 관심 구간 평가 원칙을 한국어 문자 단위로 적용합니다. |
evaluate_entities는 위 저장소의 코드를 포팅한 함수가 아니라, NE-WER·ECER·PIER 계열의 공통 평가 원칙을 한국어 원문과 개체명 사전 입력에 맞춰 독립적으로 구현한 API입니다. 공통 계약과 의도적인 차이는 교차 검증 테스트와 고정 upstream 결과에 기록했습니다. 패키지 CI는 외부 저장소를 내려받지 않으며, 검증한 upstream 커밋과 결과를 fixture로 고정해 재현성을 유지합니다.
문장별 실제 언급 횟수를 기준으로 누락과 추가 인식을 함께 집계합니다. 라벨 딕셔너리를 넘기면 ORG, PRODUCT 같은 유형별 결과도 제공합니다. 문자 단위 Entity CER와 별칭, 오류 목록까지 필요하면 evaluate_entities를 사용하십시오. 두 함수 모두 키워드·개체명 목록을 평가하며 NER 모델을 실행하지는 않습니다.
import nlptutti as metrics
report = metrics.evaluate_keywords(
["삼성전자와 삼성전자가 협력했다.", "애플이 발표했다."],
["삼성전자가 협력했다.", "애플과 삼성전자가 발표했다."],
{"ORG": ["삼성전자", "애플"]},
)
print(report["keywords"]["삼성전자"]["false_negatives"]) # 1
print(report["keywords"]["삼성전자"]["false_positives"]) # 1
print(report["summary"]["f1"]) # 0.6666666666666666점수만으로 원인을 찾기 어려울 때 문자 또는 단어 단위 정렬과 오류 빈도를 확인합니다.
import nlptutti as metrics
detail = metrics.explain_errors("아키택트", "아키택쳐")
print(detail["counts"])
# {"hits": 3, "substitutions": 1, "deletions": 0, "insertions": 0}
print(detail["error_frequencies"]["substitutions"])
# [{"reference": "트", "hypothesis": "쳐", "count": 1}]가설 또는 정답 텍스트에 일부 전처리 단계를 적용해야 할 수 있습니다. 한국어 문장 구성은 단어간 띄어쓰기의 모호성으로 CER계산에서 공백을 계산하지 않았습니다. 근대 이전까지 동양의 언어에는 ‘띄어쓰기’ 개념이 존재하지 않았고, 한국어는 맞춤법 상 띄어쓰기 규칙이 정해져 있기는 하나, 띄어쓰기를 지키지 않아도 문장의 맥락을 이해하는데 큰 무리가 없는 언어입니다. 따라서 CER 계산에서 입력 변수의 whitespace는 제거합니다. 공백 문자는 \t, \n, \r, \x0b 및 \x0c와 whitespace입니다.
ref = '또 다른 방법으로 데이터를 읽는 작업과 쓰는 작업을 분리합니다'
refs -> 또다른방법으로데이터를읽는작업과쓰는작업을분리합니다
STT 인식기에 따라 구두점을 처리하지 않는 경우가 많습니다. 입력 변수의 구두점 필터링은 flag처리로 사용할 수 있습니다. 필터링 기본값은 True입니다. 구두점 문자는:
구두점 filter-> '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
import nlptutti as metrics
refs = "또 다른 방법으로, 데이터를 읽는 작업과 쓰는 작업을 분리합니다!"
preds = "또! 다른 방법으로 데이터를 읽는 작업과 쓰는 작업을 분리합니다."
result = metrics.get_wer(refs, preds, rm_punctuation=True)
# prints: wer -> 0.0make_keyword_pattern 함수는 한국어 자연어 처리(NLP)에서 매우 중요한 형태소의 변이와 띄어쓰기 오류를 robust하게 다루기 위해 설계된 함수입니다.
이 함수는 입력한 키워드가 실제 문장 내에서 조사(예: "의", "에서", "까지" 등), **어미(예: "다", "했다" 등)**와 결합하거나, 키워드 내부에 불규칙한 띄어쓰기가 포함되어 나타나는 다양한 형태 모두를 정규표현식 패턴으로 포괄적으로 인식할 수 있게 해줍니다.
특히 한국어 음성인식(STT) 결과에서는 띄어쓰기 오류나 조사·어미 결합이 빈번하게 발생해 키워드 매칭이 어렵기 때문에,
이 함수를 활용하면 키워드 기반 오류 분석이나 고유명사 인식 평가 등에서 훨씬 더 정확한 평가가 가능합니다.
import nlptutti as nt
from nlptutti.asr_metrics import make_keyword_pattern, COMPLEX_JOSA
# 조사 리스트 (기본 제공되는 COMPLEX_JOSA 사용 가능)
josa_list = ["의", "에서", "까지", "도", "만", "를", "을", "은", "는", "이", "가", "와", "과"]
# 어미 리스트 (선택사항)
eomi_list = ["다", "합니다", "했다", "한다", "한다면", "하고"]
# "삼성전자" 키워드에 대한 패턴 생성
pattern = make_keyword_pattern("삼성전자", josa_list, eomi_list)
# 테스트 문장들
test_sentences = [
"삼성전자", # True
"삼성전자의", # True
"삼 성 전 자의", # True (띄어쓰기 있어도 인식)
"삼성전자에서부터", # True (조사 확장 포함)
"삼성전자합니다", # True (어미 결합)
"삼성전자 했다", # True (어미 결합, 띄어쓰기)
"애플은" # False
]
for sentence in test_sentences:
is_matched = bool(pattern.search(sentence))
print(f"'{sentence}' → {is_matched}")NLP적 의의
- 키워드와 조사, 어미, 띄어쓰기 등 다양한 실제 사용 맥락을 포괄적으로 처리
- 형태소 분석 없이도 정규표현식만으로 한국어의 대표적 변이현상(조사·어미 결합, 띄어쓰기 오류 등)에 강건
- 음성인식(STT) 결과물, 문서 검색, 정보추출 등에서 키워드 기반 평가 및 분석의 정밀도를 크게 향상
calculate_keyword_error_rate_with_pattern 함수는 여러 키워드에 대해 STT 인식 결과와 참조문장을 비교하여,
각 키워드별 인식 정확도 및 전체 요약 통계를 제공합니다.
조사·어미·띄어쓰기 등 한국어 변형을 유연하게 처리하므로, 고유명사/중요 단어의 STT 인식 품질 평가에 적합합니다.
이 함수는 하위 호환을 위해 유지되는 문장 존재 여부 기반 API입니다. 반복 언급과 false positive까지 평가하려면 evaluate_keywords를 사용하십시오.
from nlptutti.asr_metrics import calculate_keyword_error_rate_with_pattern, COMPLEX_JOSA
# 조사·어미 리스트 정의 (필요시 확장 가능)
josa = COMPLEX_JOSA + ["라는", "이라는", "에서의", "으로서의"]
eomi = ["다", "합니다", "했다", "한다면", "하고", "하는데", "했었다"]
# 참조(정답) 문장과 STT(가설) 문장
refs = [
"오늘은 메리츠화재의 주식이 올랐습니다.",
"애플은 새로운 아이폰을 발표했습니다.",
"구글에서 검색해보세요.",
"메리츠화재까지도 주가가 상승했다."
]
hyps = [
"오늘은 매리츠화제의 주식이 올랐습니다.", # 메리츠화재 → 매리츠화제 (오류)
"애플은 새로운 아이푼을 발표했습니다.", # 아이폰 → 아이푼 (오류)
"구글에서 검색해보세요.", # 정확 인식
"메리츠 화재까지도 주가가 상승했다." # 띄어쓰기 포함, 정확 인식
]
keywords = ["메리츠화재", "애플", "구글", "아이폰"]
# 오류율 계산 및 결과 출력
result = calculate_keyword_error_rate_with_pattern(refs, hyps, keywords, josa, eomi)
print("=== 개별 키워드 결과 ===")
for k, stats in result["keywords"].items():
print(f"'{k}': 총 {stats['total']}회, 정확 {stats['correct']}회, 오류 {stats['errors']}회")
print(f" 정확도: {stats['accuracy']:.1%}, 에러율: {stats['error_rate']:.1%}")
print("\n=== 전체 키워드 요약 ===")
s = result["summary"]
print(f"전체 키워드 등장 횟수: {s['total_keywords']}")
print(f"정확히 인식된 키워드: {s['correct_keywords']}")
print(f"오류가 발생한 키워드: {s['incorrect_keywords']}")
print(f"전체 키워드 에러율: {s['keyword_error_rate']:.1%}")출력 예시
=== 개별 키워드 결과 ===
'메리츠화재': 총 2회, 정확 1회, 오류 1회
정확도: 50.0%, 에러율: 50.0%
'애플': 총 1회, 정확 1회, 오류 0회
정확도: 100.0%, 에러율: 0.0%
'구글': 총 1회, 정확 1회, 오류 0회
정확도: 100.0%, 에러율: 0.0%
'아이폰': 총 1회, 정확 0회, 오류 1회
정확도: 0.0%, 에러율: 100.0%
=== 전체 키워드 요약 ===
전체 키워드 등장 횟수: 5
정확히 인식된 키워드: 3
오류가 발생한 키워드: 2
전체 키워드 에러율: 40.0%
반환값 구조
{
"keywords": {
"키워드명": {
"total": 등장횟수, # 참조 문장에서의 총 등장 횟수
"correct": 정확개수, # 정확히 인식된 횟수
"errors": 오류횟수, # 인식 실패 횟수
"accuracy": 정확도율, # correct/total
"error_rate": 에러율 # errors/total
}
},
"summary": {
"total_keywords": 전체등장횟수, # 모든 키워드의 총 등장 횟수
"correct_keywords": 전체정확횟수, # 전체 정확히 인식된 횟수
"incorrect_keywords": 전체오류횟수, # 전체 인식 실패 횟수
"keyword_error_rate": 전체에러율 # 전체 키워드 에러율
}
}NLP적 의미
- 형태소 변이, 띄어쓰기 오류, 조사·어미 결합 등 한국어 STT 결과의 자연스러운 변형을 고려하여 키워드 인식 성능을 신뢰성 있게 평가합니다.
- 특히 고유명사, 신조어, 전문용어 등 특정 단어의 인식률 분석 시 유용합니다.
[1]. Word Error Rate[2]. Computing error rates, Text Digitisation[3]. Galibert et al., Generating Task-Pertinent sorted Error Lists for Speech Recognition, LREC 2016. NE-WER를 참조 개체명 span에 제한된 WER로 설명합니다.[4]. Le-Duc et al., Medical Spoken Named Entity Recognition, NAACL 2025. WER·KER를 보완하는 Named-Entity-Error-Rate를 논의합니다.[5]. Szymański et al., Why Aren't We NER Yet? Artifacts of ASR Errors in Named Entity Recognition in Spontaneous Speech Transcripts, ACL 2023. ASR 오류와 개체명 인식 오류의 관계 및 오류 유형을 분석합니다.[6]. Gong et al., BR-ASR: Efficient and Scalable Bias Retrieval Framework for Contextual Biasing ASR in Speech LLM, Interspeech 2025. 일반 WER와 별도로 bias word 성능을 보고합니다.[7]. K et al., Advocating Character Error Rate for Multilingual ASR Evaluation, NAACL 2025 Findings. 단어 경계와 형태가 다양한 다국어 ASR에서 CER 병행의 근거를 제시합니다.

