Whisper가 힌디어를 숫자가 아닌 단어로 전사하는 문제: 이를 다시 변환하는 라이브러리 제작
요약
Whisper가 힌디어 숫자를 단어로 전사하는 문제를 해결하기 위해, 다양한 변형을 숫자로 변환하는 라이브러리를 소개합니다. 유니코드 정규화와 힌디어 언어적 특성을 고려하여 의료 기록 등에서 수치가 왜곡되지 않도록 설계되었습니다.
핵심 포인트
- 힌디어 숫자 단어의 다양한 철자 변형을 숫자로 매핑
- 인접한 숫자를 합산하지 않고 범위를 유지하여 데이터 왜곡 방지
- 데바나가리 문자의 유니코드 NFC 분해 특성을 활용한 정규화
- 정규 표현식의 한계를 극복하기 위한 언어적 특성 반영
한 지역 사회 보건 요원이 혈압 측정치를 소리 내어 읽습니다. Whisper는 이를 एक सौ दस बटा सत्तर — 즉,
음성-텍스트 변환 (Speech-to-text) 기술은 말로 표현된 숫자를 단어 (words) 형태로 전달하며, 사전적인 방식이 아닌 들리는 소리 그대로 철자를 표기합니다. 따라서 매핑 (mapping) 과정은 유연해야 합니다. पाँच, पांच, पाच 모두 5로 도달해야 하며, सत्तर와 सतर 모두 70으로 도달해야 합니다. v0.1.0 버전은 약 160개의 이러한 변형을 포함하며, 이를 조합하여 최대 999까지 구성합니다:
from hindi_normalize import parse_hindi_number
parse_hindi_number("नौ सौ निन्यानवे") # → 999
제가 가장 강력하게 옹호하는 단 하나의 설계 결정은 다음과 같습니다: 인접한 숫자들은 절대 합산되지 않습니다.
convert_numbers("दो तीन दिन") # → '2 3 दिन', 절대 '5'가 아님
구어체 힌디어에서 दो तीन दिन은 "2~3일"을 의미합니다. 이는 영어에서 "a couple days"라고 말하는 것과 같은 근사치 표현입니다. 단순한 단어-숫자 파서 (word-number parser)는 인접한 토큰을 더해 이를 5로 바꿔버립니다. 의료 기록 (medical transcript)에서 수치를 임의로 만들어내는 것은 이 라이브러리가 할 수 있는 최악의 행동이므로, 범위를 범위 그대로 유지합니다.
단순한 정규 표현식 (regex) 그 이상으로 만드는 세 가지 데바나가리 (Devanagari) 특성
1. NFC는 누크타 (nukta) 문자를 조용히 분해 (decomposes) 합니다
"동일한 텍스트를 동일하게 비교하도록 만들기" 위한 명백한 첫 번째 단계는 유니코드 NFC입니다. 대부분의 스크립트 (scripts)에서 NFC는 합성 (composes)을 수행합니다. 즉, 기본 문자 (base character)와 결합 문자 (combining mark)를 하나의 사전 결합된 코드 포인트 (precomposed codepoint)로 합칩니다.
하지만 데바나가리 누크타 자음의 경우, 그 반대로 동작합니다.
import unicodedata
[hex(ord(c)) for c in "क़"] # ['0x958'] — 하나의 코드 포인트
[hex(ord(c)) for c in unicodedata.normalize("NFC", "क़")] # ['0x915', '0x93c'] — क + ़
사전 결합된 형태인 क़ ख़ ग़ … (U+0958–U+095F)는 유니코드의 합성 제외 (composition-exclusion) 테이블에 포함되어 있으므로, NFC는 이를 기본 문자 + U+093C의 분해된 상태로 남겨둡니다. 이는 NFC가 항상 합성할 것이라고 예상하는 사람들을 놀라게 합니다. 저는 이를 거부하는 대신 오히려 활용합니다. 즉, 분해된 형태로 정규화 (canonicalize)하는 것입니다. 이는 Indic NLP Library의 정규화기 (normalizer)가 수행하는 방식과 정확히 일치합니다. 문자가 어떤 방식으로 입력되었든, 결과적으로 동일한 바이트 (bytes)를 얻게 됩니다.
2. \b는 대부분의 힌디어 단어에서 조용히 실패합니다
ASR 말더듬 (है है है है → है)을 해결하기 위해, 저는 처음에 교과서적인 반복 단어 정규 표현식(regex)인 \b\S+\b를 사용했습니다. 하지만 거의 모든 경우를 놓쳤습니다.
그 이유는 미묘합니다. 대부분의 힌디어 단어는 matra — 즉, हिंदी의 ी나 है의 ै와 같은 결합 모음 기호(combining vowel sign)로 끝납니다. 결합된 matra는 \w 문자가 아니며, \b는 \w와 non-\w 사이의 경계로 정의됩니다. 따라서 matra로 끝나는 단어 뒤에는 \b가 찾을 수 있는 단어 경계(word boundary)가 없으며, 결과적으로 힌디어에서 가장 자주 사용되는 단어들에서 매칭이 조용히 실패하게 됩니다.
해결책은 \b를 완전히 버리고 대신 공백(whitespace)에 앵커(anchor)를 거는 것입니다:
(\S+)(?:\s+\1){n,}
라이브러리를 추출하는 과정에서 이 문제가 실패하는 유닛 테스트(unit test)로 나타났습니다. 이는 오히려 다행스러운 결과입니다. 이런 종류의 버그는 그대로 배포될 경우 모델이 불안정(flaky)한 것처럼 보이기 때문입니다.
3. 언어를 삭제해 버리는 한 줄의 코드
Unicode 텍스트를 "정리"하는 유혹적인 방법이 있습니다. 바로 결합 기호(combining marks)를 제거하는 것 — 즉, Unicode Mark (M) 카테고리에 속하는 모든 것을 필터링하는 것입니다. 이를 데바나가리(Devanagari) 문자에 실행하면 모든 matra와 virama가 삭제됩니다:
import unicodedata
"".join(c for c in "हिंदी" if unicodedata.category(c)[0] != "M")
# → 'हद'
출력 결과는 여전히 데바나가리처럼 보입니다. 다만 더 이상 단어가 아닐 뿐입니다 — 혹은 그 어떤 단어도 아니게 됩니다. 이는 단순한 정규화 도구(naive normalizers)에서 발생하는 실제 버그이며, 머신러닝 (ML) 분야에서 고약한 2차 효과를 일으킵니다. 모델이 평가받는 바로 그 문자들을 버려버리기 때문에, Whisper의 측정된 단어 오류율 (Word Error Rate, WER)을 조용히 축소(deflate) 시켜버립니다. hindi-normalize는 기호들을 정형화(canonicalize)할 뿐, 카테고리 자체를 제거하지는 않습니다. 이 라이브러리가 제공하는 손실이 있는 변환들 — nukta 제거, 비음 접기 (nasal folding), chandrabindu 및 visarga 접기 — 은 모두 선택 사항(opt-in)이며 기본적으로는 꺼져 있습니다. 요청하지 않는 한 정보를 잃지 않습니다.
도구의 형태
hindi-normalize는 더 큰 도구들 사이의 작은 틈새에 존재합니다:
- 전체적인 인도 언어 NLP (Indic NLP) — 수십 개의 언어에 걸친 토큰화 (tokenization), 음차 (transliteration), 스크립트 변환 (script conversion) — 를 위해서는 Indic NLP Library를 사용하세요. 이 라이브러리는 문자 수준의 작업을 위해 해당 라이브러리의
DevanagariNormalizer를 따릅니다. - 영어 숫자-단어 파싱 (number-word parsing)을 위해서는
text2num또는word2number를 사용하세요.
이 도구는 그 사이를 이어주는 단계입니다: 구어체 힌디어 숫자를 숫자로 변환하는 것과 더불어, ASR (자동 음성 인식) 또는 OCR (광학 문자 인식) 전사본을 파싱하기 전에 실제로 필요한 데바나가리 (Devanagari) 표준화 (canonicalization)를 수행합니다. 이는 더 큰 도구들과 경쟁하는 것이 아니라, 그 도구들과 결합하여 작동합니다.
형제 프로젝트인 llmclean과 공유하는 원칙은 다음과 같습니다:
모든 함수는 전역적(total)입니다. 문자열이 아닌 값을 전달하면 입력값을 수정 없이 그대로 반환합니다. 본문은 예외를 발생시키지 않도록 래핑(wrapped)되어 있습니다. 이는 예외 경로(exception path)를 허용할 여유가 없는 파이프라인에 그대로 투입될 수 있음을 의미합니다.
기본적으로 무손실(Lossless)입니다. 안전한 변환은 자동으로 실행됩니다. 정보를 폐기하는 모든 작업은 사용자가 직접 선택해야 하는 키워드 플래그(keyword flag)를 통해서만 이루어집니다.
런타임 의존성 제로(Zero runtime dependencies). 순수 표준 라이브러리만 사용합니다. 다운스트림 사용자가 버전 충돌을 해결해야 할 일이 전혀 없습니다.
직접 사용해보고, 문제가 발생하는 부분을 알려주세요
pip install hindi-normalize
진정으로 도움이 될 만한 사항들:
만약 귀하의 ASR이 제가 매핑하지 않은 숫자-단어 철자를 생성한다면, 원본 전사본과 함께 이슈(issue)를 제기해 주세요. 그렇게 해서 변형 목록(variant list)이 늘어납니다.
만약 모자 보건(maternal and child health) 이외의 분야에서 작업하신다면, 기본 용어 사전은 ASHA(인도 보건 요원)에 맞춰져 있어 적합하지 않을 것입니다. 하지만 교체가 가능하므로, 귀하의 어휘(vocabulary)가 어떤 모습인지 알고 싶습니다.
그리고 만약 귀하가 Indic NLP Library나 귀하만의 단계로 이 문제를 해결했으며, 제가 그냥 그것을 사용했어야 한다고 생각하신다면 — 그 또한 공개적으로 논의할 만한 유용한 논거가 될 것입니다.
GitHub: Tushar-9802/hindi-normalize
PyPI: hindi-normalize on PyPI
다음 로드맵(roadmap) 과제 또한 동일한 방식으로 진행됩니다. 즉, 실제 전사(transcript) 데이터에서 먼저 발견된 후 포팅(porting)될 예정입니다: 힌디어 서수(पहला, दूसरा), 분수 관련 단어(आधा, सवा, डेढ़, पौन — 용량 및 수량 표현에 도처에 존재), 그리고 통화(currency), 날짜(date), 시간(time) 정규화(normalization)가 포함됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기