Nlptutti Manual
Nlptutti 0.0.0.20+

Nlptutti 한국어 STT 평가 매뉴얼

한 문장의 CER 계산부터 두 STT 시스템 비교, JSON·SRT·TSV 결과의 재현 가능한 평가, 여러 문장의 micro/macro, 개체명 span의 Entity CER와 실제 치환·삭제·삽입 위치까지 한 흐름으로 정리했다.

기존 사용자 호환성: rate_mode를 생략하면 과거 버전과 동일한 "normalized" 계산식이 적용된다. 표준 CER/WER는 rate_mode="standard"를 직접 적은 경우에만 사용된다. 유니코드 정규화도 기본적으로 켜지지 않는다.
동일 기존 기본 호출의 계산 결과
4 formats text / JSON / SRT / TSV
3.8+ 지원 Python 버전
Metric guide

CER·WER·CRR 먼저 고르기

세 함수는 정답 문장과 STT 결과를 같은 순서로 받지만, 평가 단위와 점수 방향이 다르다.

API 입력과 반환값 선택할 때 사용 경계
get_cer 문자열 2개 → cer와 치환·삭제·삽입 횟수 띄어쓰기 차이를 제외하고 문자 전사 품질을 비교할 때 공백을 항상 제거하므로 띄어쓰기 품질은 평가하지 못한다. 낮을수록 좋다.
get_wer 문자열 2개 → wer와 치환·삭제·삽입 횟수 공백으로 나뉜 단어와 띄어쓰기 차이까지 포함할 때 서로 다른 토큰화 정책의 결과를 그대로 비교하면 안 된다. 낮을수록 좋다.
get_crr 문자열 2개 → crr와 치환·삭제·삽입 횟수 문자 품질을 높을수록 좋은 보조 지표로 표시할 때 round(1 - CER, 2)이며 별도 정렬 지표가 아니다.
정규화 옵션 구분: rate_mode="normalized"는 삽입 오류를 분모에도 넣는 기존 계산식의 이름이며 입력 문자열을 바꾸지 않는다. 실제 Unicode 문자열 변환은 기본값이 Noneunicode_normalization"NFC" 등을 직접 지정할 때만 적용된다.

세 함수는 기본적으로 문장부호를 제거한다. 문장부호도 오류로 평가하려면 rm_punctuation=False를 지정한다. 여러 문장은 evaluate_corpusmicromacro를 목적에 맞게 사용한다.

Quick start

설치하고 첫 점수 확인하기

패키지명은 nlptutti다. 정답 문장을 첫 번째 인자, STT 결과를 두 번째 인자로 전달한다.

pip install -U nlptutti
import nlptutti as nt

reference = "오늘 날씨가 맑습니다"
hypothesis = "오늘 날씨는 맑습니다"

cer = nt.get_cer(reference, hypothesis, rate_mode="standard")
wer = nt.get_wer(reference, hypothesis, rate_mode="standard")
crr = nt.get_crr(reference, hypothesis, rate_mode="standard")

print(round(cer["cer"], 4), cer["substitutions"])  # 0.1111 1
print(round(wer["wer"], 4), wer["substitutions"])  # 0.3333 1
print(crr["crr"], crr["substitutions"])            # 0.89 1

CER·WER는 낮을수록, CRR는 높을수록 좋다. 완전 일치는 각각 0.0, 0.0, 1.0이며 상세 편집 횟수는 같은 결과 딕셔너리에서 확인한다.

System comparison

두 STT 결과를 같은 평가 문장으로 비교하기

compare_systems는 정답 문장과 둘 이상의 STT 출력을 문장별로 맞춰 micro/macro CER·WER·CRR과 pairwise delta를 계산한다.

import nlptutti as nt

report = nt.compare_systems(
    ["오늘 날씨가 맑습니다", "서울은 따뜻합니다"],
    {
        "baseline": ["오늘 날씨는 맑습니다", "서울은 춥습니다"],
        "candidate": ["오늘 날씨가 맑습니다", "서울은 따뜻합니다"],
    },
    rate_mode="standard",
)

for system in report["systems"]:
    print(
        system["id"],
        round(system["metrics"]["cer"]["micro"], 4),
        round(system["metrics"]["wer"]["micro"], 4),
    )

# baseline 0.2353 0.4
# candidate 0.0 0.0

이 예제는 패키지 저장소의 고정 예제 데이터와 같다. 문서와 CI에서도 같은 기대값을 검사한다.

JSON과 Markdown 결과 저장

python -c "from urllib.request import urlretrieve; urlretrieve('https://raw.githubusercontent.com/hyeonsangjeon/computing-Korean-STT-error-rates/main/examples/comparison_input.json', 'comparison_input.json')"
nlptutti compare comparison_input.json \
  --rate-mode standard \
  --output-dir comparison-report
comparison-report/report.json
comparison-report/report.md
  • micro: corpus 전체의 편집 횟수를 합산한 점수다.
  • macro: 문장별 점수의 단순 평균이다.
  • delta: candidate 점수에서 baseline 점수를 뺀 값이다. CER·WER는 음수, CRR은 양수일수록 candidate가 좋다.
  • 기본값: 기존 결과와 호환되도록 rate_mode="normalized"를 유지한다. 공식 비교에서는 "standard"를 직접 적는다.
  • ID mapping: ID 문자열을 정렬한 순서로 계산하므로 mapping에 넣은 순서가 fingerprint나 bootstrap 결과를 바꾸지 않는다.
  • 선택 평가 설정: 키워드·개체명·별칭을 사용하면 설정 전체의 SHA-256을 결과에 남긴다.
  • 원문: 기본 보고서에는 넣지 않는다. 필요할 때만 include_transcripts=True를 켠다.

입력 ID 규칙, paired bootstrap, 한국어 진단과 결과 schema는 패키지 비교 매뉴얼에 정리되어 있다.

Structured transcript

구조화 입력과 평가 여권

parse_transcript로 평가 문장을 읽고 evaluate_transcript로 사용한 옵션과 패키지 버전까지 함께 남긴다.

범위: 이 기능은 음성을 전사하거나 STT 모델을 내려받지 않는다. 일반 문자열이나 지원 형식에 맞는 JSON·SRT·TSV 결과를 기존 CER·WER·CRR 함수로 연결한다.

1. 일반 텍스트

import nlptutti as nt

transcript = nt.parse_transcript(
    "오늘 날씨는 맑습니다",
    "text",
)

입력 문자열을 바꾸지 않고 가설문장으로 사용한다. txt도 같은 형식 이름으로 사용할 수 있다.

2. JSON

transcript = nt.parse_transcript(
    {
        "text": "오늘 날씨는 맑습니다",
        "segments": [
            {"start": 0, "end": 1200, "text": "오늘 날씨는"},
            {"start": 1200, "end": 2500, "text": "맑습니다"},
        ],
        "model": "example-model",
        "language": "ko",
    },
    "json",
)

기본값은 최상위 text만 평가 문장으로 사용한다. file, model, language, duration_s가 실제로 있으면 provenance에 보존한다. JSON 타임스탬프의 단위는 추정하지 않는다.

# 최상위 text가 없을 때만 직접 선택한다.
transcript = nt.parse_transcript(
    {
        "segments": [
            {"text": "오늘 날씨는"},
            {"text": "맑습니다"},
        ]
    },
    "json",
    json_text_policy="segments_fallback",
)

3. SRT

srt = """1
00:00:00,000 --> 00:00:01,200
오늘 날씨는

2
00:00:01,200 --> 00:00:02,500
맑습니다
"""

transcript = nt.parse_transcript(srt, "srt")

cue 텍스트를 순서대로 ASCII 공백 하나로 연결한다. 타임코드는 점수에서 빼고 provenance에만 남긴다.

4. TSV

tsv = """start\tend\ttext
0.0\t1.2\t오늘 날씨는
1.2\t2.5\t맑습니다
"""

transcript = nt.parse_transcript(tsv, "tsv")

start, end, text 헤더가 필요하다. 시간 값은 초 단위의 유한한 숫자로 검증하고 text 열만 평가 문장으로 사용한다.

형식 평가 문자열 시간 정보
text / txt 입력 문자열 그대로 없음
JSON 최상위 text 기본, segments fallback 선택 원래 값을 단위 추정 없이 보존
SRT cue 텍스트를 공백 하나로 연결 SRT timecode로 보존
TSV text 열을 공백 하나로 연결 초 단위로 검증해 보존

평가 여권 만들기

report = nt.evaluate_transcript(
    "오늘 날씨가 맑습니다",
    transcript,
    rate_mode="standard",
)

print(round(report["metrics"]["cer"]["value"], 4))  # 0.1111
print(round(report["metrics"]["wer"]["value"], 4))  # 0.3333
print(report["schema_version"])                      # 1.0
print(report["options"]["rate_mode"])               # standard
  • evaluator: 실제 평가에 사용한 nlptutti 버전을 기록한다.
  • options: rate_mode, 문장부호 제거, 유니코드 정규화 설정을 기록한다.
  • metrics: CER·WER·CRR 값과 각 치환·삭제·삽입 횟수를 기록한다.
  • provenance: 입력 형식, 선택한 JSON 정책, 구간 수, 제공된 메타데이터와 시간 정보를 기록한다.
  • 원문 보호: reference와 hypothesis 원문 대신 SHA-256, 문자 수, UTF-8 byte 수를 기록한다. 해시는 익명화 수단이 아니므로 민감한 짧은 문장의 접근 권한은 별도로 관리한다.

JSON 문법 오류, 필수 필드 누락, 잘못된 SRT 타임코드, TSV 헤더 누락은 TranscriptFormatError로 알린다. 잘못된 입력을 빈 가설문장으로 조용히 바꾸지 않는다.

지원 형식은 FunASR 고정 커밋 d1007c3CLI 출력 문서를 참고하되 특정 공급자에 종속되지 않도록 직접 구현했다. FunASR 모델이나 런타임은 패키지 의존성에 포함하지 않는다.

Compatibility

오류율 분모 방식 선택

두 방식은 삽입 오류를 분모에 포함하는지가 다르다. 보고서나 논문에서 표준 CER/WER가 필요할 때만 standard를 명시한다.

normalized

(S + D + I) / (S + D + I + C)

Nlptutti의 역사적 기본값이다. 삽입을 분모에도 포함하므로 0과 1 사이에서 비교하기 쉽다.

standard

(S + D + I) / (S + D + C)

참조 길이를 분모로 사용한다. 삽입이 많으면 1보다 커질 수 있다.

같은 입력 비교

import nlptutti as nt

default_result = nt.get_cer("STEAM", "STREAM")
standard_result = nt.get_cer(
    "STEAM",
    "STREAM",
    rate_mode="standard",
)

print(default_result["cer"])   # 0.16666666666666666
print(standard_result["cer"])  # 0.2

기존 코드는 수정할 필요가 없다. 옵션을 생략한 호출은 계속 normalized로 계산된다. 한 프로젝트 안에서는 두 방식을 섞지 말고 결과와 함께 rate_mode를 기록한다.

Character

get_cer

문자 단위 오류율을 계산한다. 한국어 띄어쓰기 차이에 덜 민감하도록 공백은 항상 제거한다.

import nlptutti as nt

result = nt.get_cer(
    "제이 차 세계 대전",
    "제이차 세계대전",
)

print(result)
# {"cer": 0.0, "substitutions": 0, "deletions": 0, "insertions": 0}

띄어쓰기 자체도 오류로 평가해야 한다면 CER보다 별도 전처리 정책이나 WER를 함께 검토한다.

Word

get_wer

공백으로 구분한 단어 단위 오류율을 계산한다. 띄어쓰기 차이가 단어 경계를 바꾸면 점수에도 반영된다.

import nlptutti as nt

result = nt.get_wer(
    "오늘 날씨가 좋습니다",
    "오늘 정말 날씨가 좋습니다",
)

print(result["wer"])         # 0.25
print(result["insertions"])  # 1

표준 WER가 필요하면 같은 호출에 rate_mode="standard"를 추가한다.

Recognition

get_crr

문자 오류율의 보수인 1 - CER을 반환한다. 기존 호환성을 위해 소수점 둘째 자리로 반올림한다.

import nlptutti as nt

result = nt.get_crr("아키택트", "아키택쳐")

print(result["crr"])           # 0.75
print(result["substitutions"]) # 1

standard 모드에서는 CER가 1을 넘을 수 있으므로 CRR가 0보다 작아질 수도 있다. 0과 1 사이의 정답률이 필요하면 기본 normalized 모드를 사용한다.

Korean keyword

make_keyword_pattern

조사 결합과 키워드 내부의 불규칙한 띄어쓰기를 허용하는 정규식 패턴을 만든다.

import nlptutti as nt

pattern = nt.make_keyword_pattern("메리츠 화재")

print(bool(pattern.search("메리츠화재의 주가")))  # True
print(bool(pattern.search("메리츠 화재는 상승"))) # True
print(bool(pattern.search("비메리츠화재상품")))    # False

조사 목록을 생략하면 COMPLEX_JOSA가 사용된다. 직접 제어하려면 두 번째 인자에 조사 목록을, 세 번째 인자에 어미 목록을 전달한다.

Corpus

evaluate_corpus

여러 문장을 한 번에 평가해 전체 편집 횟수 기반 micro와 문장 평균 macro를 함께 반환한다.

import nlptutti as nt

report = nt.evaluate_corpus(
    ["가나", "다라"],
    ["가마", "다라바"],
)

print(report["rate_mode"])          # normalized
print(report["cer"]["micro"])       # 0.4
print(report["cer"]["macro"])       # 0.41666666666666663
print(report["perfect_sentences"])  # 0
print(report["sentence_error_rate"]) # 1.0
  • micro: 모든 문장의 편집 횟수를 합친 뒤 한 번 계산한다.
  • macro: 문장별 오류율을 동일한 가중치로 평균한다.
  • sentence_error_rate: CER 전처리 후 하나라도 문자 오류가 있는 문장의 비율이다.
Entity-aware ASR

evaluate_entities

전체 문장 점수와 별도로 회사명, 사람 이름, 상품명처럼 중요한 개체명의 문자 오류와 언급 보존 성능을 함께 평가한다.

이 함수는 NER 모델을 실행하지 않는다. 사용자가 제공한 개체명 사전을 문장에서 찾아 참조 개체명 span의 Entity CER와 정확한 언급 기준 precision, recall, F1을 계산한다.
import nlptutti as nt

report = nt.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.micro: 모든 참조 개체명 span의 편집 횟수를 합쳐 계산한다.
  • entity_cer.macro: 참조 개체명 언급별 CER를 동일한 가중치로 평균한다.
  • 계산식 선택: 기본 normalized는 기존 패키지 호환용이다. 논문의 NE-WER처럼 참조 개체명 문자 수를 분모로 보고할 때는 rate_mode="standard"를 지정한다.
  • summary: 개체명 언급의 precision, recall, F1과 false positive, false negative를 담는다.
  • labels: ORG, PERSON, PRODUCT처럼 사용자가 지정한 유형별로 결과를 나눈다.
  • errors: omission, addition, misrecognition과 해당 문장 인덱스 및 편집 횟수를 보여준다.

한국어와 별칭 정책

  • 개체명 내부 띄어쓰기와 기본 조사·어미 결합을 허용하며, 조사는 개체명 span에 포함하지 않는다.
  • aliases를 생략하면 정확한 개체명 표기만 인정한다. 발음 유사도나 퍼지 매칭은 적용하지 않는다.
  • 숫자 읽기나 영문 표기의 허용 가능한 전사형처럼 실제로 같은 개체인 경우에만 별칭을 등록한다.
  • 별칭은 문자 정렬 전에 대표 개체명으로 바뀌므로 등록된 별칭의 Entity CER는 0.0이다.
  • 참조에 없는 개체명 추가는 Entity CER가 아니라 precision/F1과 addition 오류로 확인한다.

왜 두 점수를 함께 보나

LREC 2016의 NE-WER 연구는 전체 WER 대신 참조 개체명 span에 속한 오류를 따로 계산한다. Nlptutti는 한국어의 불안정한 단어 경계를 줄이기 위해 이 개념을 문자 단위 Entity CER로 독립 구현했다. 이는 특정 논문이나 공개 구현을 그대로 포팅한 지표가 아니며, 다른 구현과 점수가 같다는 의미도 아니다. 다만 참조 span 밖에 새로 생긴 개체명은 이 CER에 포함되지 않으므로 언급 precision/F1을 함께 반환한다.

NAACL 2025의 Spoken NER 연구도 개체명 오류율을 전체 WER의 대체제가 아닌 보완 지표로 설명한다. 따라서 보고서에는 전체 CER, Entity CER, 개체명 F1과 평가 정책을 함께 기록하는 것이 좋다.

Implementations

관련 공개 구현과 차이

논문 설명이 아니라 실제 실행 가능한 평가 코드를 기준으로 입력 형식과 계산 정책을 비교한다.

  • ContextASR-Bench 평가 코드: 개체명 목록을 이용해 WER, 퍼지 매칭 기반 NE-WER, 정확 일치 기반 NE-FNR을 계산한다. NVIDIA NeMo-Skills에도 같은 벤치마크 평가 구현이 있다. Nlptutti는 퍼지 매칭을 기본으로 사용하지 않고 한국어 문자 span과 사용자가 등록한 aliases를 평가한다.
  • Teklia ie-eval: BIO 형식의 정답·예측 파일을 입력받아 ECER/EWER와 유형별·순서 독립 점수를 계산한다. Nlptutti는 이미 태깅된 NER 출력 대신 원문 reference/hypothesis와 개체명 사전을 입력받으며 NER 모델을 실행하지 않는다.
  • PIER: 전체 문장을 먼저 정렬하고 참조에서 지정한 관심 단어 위치의 편집만 단어 단위로 센다. Nlptutti Entity CER는 같은 관심 구간 평가 원칙을 한국어 문자 단위로 적용한다.
해석 원칙: Nlptutti의 evaluate_entities는 위 코드를 복제한 함수가 아니다. NE-WER·ECER·PIER 계열의 공통 원칙을 한국어 원문과 개체명 사전 입력에 맞춰 독립 구현했다. 따라서 이름이 비슷한 점수를 직접 비교하기 전에 단어/문자 단위, 퍼지 매칭, NER 태그 입력 여부를 먼저 맞춰야 한다.

공통으로 지키는 규칙과 의도한 차이는 교차 검증 테스트고정 upstream 결과에 기록한다. CI에서는 외부 저장소를 내려받지 않고 검증한 커밋과 결과를 fixture로 고정한다.

Entity preservation

evaluate_keywords

지정한 고유명사나 핵심어가 STT 결과에 몇 번 보존됐고, 몇 번 빠지거나 추가됐는지 문장별로 집계한다.

import nlptutti as nt

report = nt.evaluate_keywords(
    [
        "삼성전자와 삼성전자가 협력했다.",
        "애플이 발표했다.",
    ],
    [
        "삼성전자가 협력했다.",
        "애플과 삼성전자가 발표했다.",
    ],
    {"ORG": ["삼성전자", "애플"]},
)

samsung = report["keywords"]["삼성전자"]
print(samsung["true_positives"])   # 1
print(samsung["false_negatives"])  # 1
print(samsung["false_positives"])  # 1
print(samsung["f1"])               # 0.5
print(report["labels"]["ORG"]["f1"])  # 0.6666666666666666
이 함수는 NER 모델이 아니다. 사용자가 지정한 키워드 목록의 언급 횟수를 평가한다. 같은 문장 안에서는 reference와 hypothesis의 등장 횟수 중 작은 값을 true positive로 본다. 문자 단위 Entity CER, 직접 지정한 별칭, 누락·추가·오인식 목록이 필요하면 evaluate_entities를 사용한다. 실제 NER 모델의 span 예측 평가는 별도의 평가기가 필요하다. 조사와 어미 목록을 생략하면 각각 COMPLEX_JOSACOMPLEX_EOMI가 사용되며, 빈 목록을 전달하면 해당 접미사 매칭을 끌 수 있다.
Diagnostics

explain_errors

점수의 원인이 된 문자 또는 단어를 정렬하고 반복되는 치환·삭제·삽입을 빈도순으로 확인한다.

import nlptutti as nt

detail = nt.explain_errors(
    "아키택트",
    "아키택쳐",
)

print(detail["rate"])   # 0.25
print(detail["counts"])
# {"hits": 3, "substitutions": 1, "deletions": 0, "insertions": 0}

print(detail["error_frequencies"]["substitutions"])
# [{"reference": "트", "hypothesis": "쳐", "count": 1}]

단어 단위 분석은 unit="word"를 지정한다. 반환되는 alignment에는 equal, substitute, delete, insert가 순서대로 담긴다.

Compatibility API

calculate_keyword_error_rate_with_pattern

기존 사용자를 위해 유지되는 문장 존재 여부 기반 키워드 평가 함수다.

import nlptutti as nt

result = nt.calculate_keyword_error_rate_with_pattern(
    ["삼성전자가 발표했다."],
    ["삼성전자가 발표했다."],
    ["삼성전자"],
    nt.COMPLEX_JOSA,
)

print(result["keywords"]["삼성전자"]["accuracy"]) # 1.0

이 함수는 한 문장에 같은 키워드가 여러 번 나와도 1회로 집계하며, hypothesis에만 등장한 키워드는 false positive로 세지 않는다. 새 코드에서는 evaluate_keywords를 권장한다.

Preprocessing

전처리 옵션을 결과에 남기기

점수 비교에서는 모델 이름만큼 공백, 문장부호, 유니코드 정규화 정책을 함께 남기는 것이 중요하다.

문장부호

result = nt.get_wer(
    "안녕하세요!",
    "안녕하세요",
    rm_punctuation=True,  # 기본값
)

유니코드 NFC

result = nt.get_cer(
    reference,
    hypothesis,
    unicode_normalization="NFC",
)
  • unicode_normalization=None이 기본값이며 과거 결과를 그대로 보존한다.
  • NFC/NFD/NFKC/NFKD를 선택할 수 있다. 일반적인 한글 조합형 차이에는 NFC가 적합하다.
  • CER와 CRR은 공백을 항상 제거한다. WER는 공백을 단어 경계로 사용한다.
  • 코퍼스, 키워드, 개체명 평가의 reference/hypothesis 문장 수가 다르면 ValueError가 발생한다.
References

논문과 참고 자료

Entity CER 설계와 한국어 문자 단위 평가를 설명할 때 참고한 1차 자료다.

Choose

목적에 맞는 함수 선택

한 문장 점수, 구조화 결과 재현, 전체 데이터셋 평가, 특정 용어 보존, 오류 원인 분석을 분리하면 결과 해석이 선명해진다.

get_cer / get_wer / get_crr

한 쌍의 문장을 빠르게 점수화할 때 사용한다.

parse_transcript / evaluate_transcript

text·JSON·SRT·TSV 결과를 같은 규칙으로 평가하고 재현 가능한 JSON 기록을 남길 때 사용한다.

evaluate_corpus

데이터셋 전체의 micro/macro 성능과 완전 일치 문장 비율을 볼 때 사용한다.

evaluate_entities

개체명 span의 문자 오류, 언급 F1, 별칭과 실제 오류 목록을 함께 평가할 때 사용한다.

evaluate_keywords

회사명, 상품명, 전문용어의 누락과 추가 인식만 간단히 언급 횟수 기준으로 평가할 때 사용한다.

explain_errors

점수가 나빠진 실제 문자·단어와 반복 오류를 찾을 때 사용한다.