본문 바로가기
Databricks/RAG

한국어 RAG 검색 (Kiwi) ② 사용자·동의어 사전과 복합명사 처리

by 여행을 떠나자! 2026. 6. 28.

📚 시리즈 — 한국어 RAG 검색 (Kiwi)

 

 

5. 사용자 사전

왜 필요한가 — Kiwi 의 기본 LM 분석기는 일반 한국어 코퍼스로 학습되어 도메인 특화 어휘 (IATA·항공 약어·복합명사·단위명사 등) 를 잘못 분해하거나 누락할 수 있습니다. 사용자 사전은 도메인 어휘를 런타임에 등록해 형태소 분석의 태그·우선순위·분해 방식 을 제어합니다.

언제 쓰는가 — 4가지 시나리오:

  • 신규 약어·고유명사 인식 (DGR, ULD, B777)
  • 의존명사(NNB) → 일반명사(NNG) 재태깅 — BM25 화이트리스트 통과 (§2.3)
  • 변이형·오탈자 정규화 (수화물수하물)
  • 복합명사·브랜드명 사전 분해 (스카이패스스카이+패스)

 

5.1 사용자 사전 API 5종 (개요)

API 시그니처 용도 본 가이드 절
add_user_word (form, tag, score, orig_word) 개별 단어 런타임 등록 — 가장 흔함 §5.2
add_pre_analyzed_word (form, analyzed, score) 사전 분해 결과 강제 — 불규칙 합성어 §5.3
add_rule (tag, replacer, score) callable 변형 규칙 §5.4
add_re_rule (tag, pattern, repl, score) 정규식 변형 규칙 §5.4
load_user_dictionary (path) TSV 파일 일괄 로드 — 운영 권장 §5.5

kiwipiepy ≥ 0.17 부터 공백 포함 복합명사 등록 가능.

 

5.2 add_user_word — 가장 흔한 패턴

인자 의미:

  • form — 등록할 표면형 (예: "DGR", "킬로그램")
  • tag — 강제할 품사 태그 (예: "NNP", "NNG")
  • score — 우선순위 가중치 (동일 표면형이 여러 형태소로 분석될 때)
  • orig_word (옵션) — 원형 매핑 (변이형 정규화 시)

score 의미 — Kiwi 는 입력 텍스트의 가능한 형태소 분해 후보들에 점수를 매겨 가장 높은 점수를 선택합니다. score 는 사용자 사전에 등록된 form 이 매칭됐을 때 그 후보의 LM 점수에 가산되는 가중치입니다. 양수면 LM 기본 분해보다 우선 채택, 음수면 LM 기본 후보보다 후순위로 정렬됩니다.

동작 예시 — 킬로그램 NNG 승격: 1. LM 기본 분석: 킬로그램NNB (의존명사) 2. 사용자 등록 (+3 가산): 킬로그램NNG (일반명사) 후보 점수 +3 3. 두 후보 경쟁 → 가산된 NNG 후보가 top-1 으로 선택 4. 선택된 토큰 태그가 NNGKEEP_TAGS 통과 → BM25 색인에 포함

위 코드의 의존명사 → NNG 승격 (킬로그램·시간) 이 필요한 이유는 §2.3 도식 직후 노트 참조 — KEEP_TAGS 화이트리스트에서 NNB 제외이기 때문입니다.

score 가이드: - 신규 약어·고유명사 통째 등록 — +3 ~ +5 권장 (LM 분해 후보보다 우선) - 의존명사 → NNG 승격+3 권장 (BM25 화이트리스트 통과) - 변이형·표기 통일-3 ~ -5 권장 (원형 우선)

# 신규 약어·고유명사 — NNP 고정, +5
for term in ["DGR", "AVI", "ULD", "PMC", "AKE", "RKSI", "KE", "B777", "B787"]:
    kiwi.add_user_word(term, "NNP", score=5.0)

# 의존명사 → NNG 승격 — +3
kiwi.add_user_word("킬로그램", "NNG", score=3.0)
kiwi.add_user_word("시간", "NNG", score=3.0)

# 변이형·표기 통일 — -3 (원형 "수하물" 우선)
kiwi.add_user_word("수화물", "NNG", score=-3.0, orig_word="수하물")

 

5.3 add_pre_analyzed_word — 불규칙 합성어 분해 강제

불규칙 합성어·브랜드명에 대해 사전 분해 결과를 강제합니다. score=-3 권장 (원본 LM 분석을 침범하지 않도록 후순위).

kiwi.add_pre_analyzed_word(
    "스카이패스",
    [("스카이", "NNP"), ("패스", "NNG")],
    score=-3.0,
)

 

5.4 add_rule / add_re_rule — 변형 규칙

사전 파일(YAML/TSV) 에 담기 어려운 패턴 변이 (표기 오류·구어·음절 늘림 등) 를 코드 룰로 일괄 정규화할 때 사용합니다. 입력 텍스트에서 pattern 에 매칭되는 부분을 찾으면 그 부분의 표면형을 repl 로 바꾸고 tag 태그를 부여한 토큰으로 인식 — 즉 텍스트 정규화 + 형태소 등록 이 결합된 룰입니다.

인자 의미 (add_re_rule):

  • tag — 매칭 결과에 부여할 품사 태그 (예: "NNG")
  • pattern — 입력 텍스트에서 찾을 정규식 (예: r"수화물")
  • repl — 매칭된 부분을 치환할 문자열 (예: "수하물")
  • score — 동일 표면형에 다른 분석 후보가 있을 때의 우선순위. 변이형은 -3 이하 권장 (원형 보존 후순위, §5.2 score 가이드)

add_rulepattern 자리에 정규식 대신 callable 을 받는 형태로, 정규식으로 표현하기 어려운 복잡한 변환 로직이 필요할 때만 사용합니다.

# 정규식 — 모든 "수화물" 을 "수하물" 로 정규화
kiwi.add_re_rule("NNG", r"수화물", "수하물", score=-3.0)

위 룰이 등록되면 텍스트 "수화물 분실" 의 토큰화 결과는 [(수하물/NNG), (분실/NNG)] 가 됩니다 — 표면형은 수화물수하물 로 정규화되고 태그는 NNG 부여. 인덱싱·검색 양쪽에 동일 룰이 적용되면 표기 변이에 무관하게 단일 정형(수하물) 으로 매칭 일관성을 확보합니다.

운영 권장:

  • 규모 관리 — 룰 폭증 시 유지보수 비용 증가. 50건 이내 권장
  • 대안 — 규칙성이 약한 변이는 add_pre_analyzed_word (§5.3) 로 정확한 분해 결과 등록이 더 안전

 

5.5 load_user_dictionary — TSV 파일 일괄 로드

코드 호출(add_user_word 등) 대신 TSV 파일 에 사용자 사전을 모아 일괄 로드합니다. 운영팀이 PR 로 사전을 관리하고 인덱싱·검색이 동일 파일을 읽도록 강제하는 가장 흔한 운영 패턴입니다 (§5.6 양쪽 동일 강제).

최소 형식 (Kiwi 사용자 사전 TSV 스펙):

# 컬럼: form\ttag\tscore           (3컬럼 기본)
# 컬럼: form\torig/tag\tscore      (이형태 — orig 매핑)
# 컬럼: form\tm1/t1 + m2/t2 + ...  (기분석)
# UTF-8, Tab 구분, `#` 주석, score 는 실수, tag 는 세종 표준

DGR NNP 5.0
킬로그램    NNG 3.0
수화물 수하물/NNG -3.0
스카이패스   스카이/NNP + 패스/NNG    -3.0

함정 — 4 컬럼 형식 금지: 변이형 매핑을 4번째 컬럼으로 수화물\tNNG\t-3.0\t수하물 처럼 적으면 load_user_dictionaryValueError: Wrong dictionary format 으로 실패합니다. 이형태 매핑은 2번째 컬럼 orig/tag 슬래시 표기로 표현하세요. add_user_word(form, tag, score, orig_word=...) 의 4-인자 시그니처와 TSV 컬럼 수는 다릅니다.

로드:

from glob import glob

for path in sorted(glob("/Volumes/airag_prd/dict/user_dict/current/airag_*.tsv")):
    kiwi.load_user_dictionary(path)

카테고리별 파일 정책 — A항공 운영은 사전을 카테고리별 파일로 분리(airag_iata.tsv·airag_compound.tsv 등)하고 airag_*.tsv glob 패턴으로 일괄 로드합니다. Kiwi API 는 단일 파일만 받으므로 애플리케이션 코드가 sorted(glob(...)) 으로 N 파일을 순차 로드합니다. 카테고리 개수는 고정 아님 — 디렉토리에 파일을 추가하면 자동 인식. 환경(dev/staging/prd) 은 catalog 레벨(airag_dev·airag_staging·airag_prd)에서 분리됩니다.

UC Volume 권한 분리·운영 PR·실제 샘플 행 상세는 §9 (UC Volume 사전 관리) 정본 참조.

 

5.6 적용 시점 — 양쪽 동일 강제

본 가이드 권장: 사용자 사전은 인덱싱·검색 양쪽에 동일하게 적용 합니다 (동의어 사전의 검색 시점만 (§6.4) 과 정반대).

근거 — 사용자 사전은 토큰의 form·tag·boundary 자체를 재정의하므로 (§5.2 score 의미), 한쪽만 적용하면 인덱스·검색 토큰 불일치 → hit 0 발생 (정책 D 와 동치, §7.4 참조).

운영 강제 (§10.3 bootstrap_dictionary() 가 양쪽에서 import 되도록 강제):

  • fail-fastload_user_dictionary() 가 IOError 면 silent 진행 금지 (사전 한쪽만 로드된 비대칭 차단)

 

6. 동의어 사전

본 절은 동의어 사전의 형태 선택 (라이브러리 내장 vs 애플리케이션 — §6.1~6.3), 적용 시점 (§6.4), 다중 토큰 매칭 구현 (§6.5) 을 다룹니다. 동의어를 언제 적용하느냐 (인덱싱 / 검색 / 양쪽) 의 외부 권위 자료 기반 비교·결정은 부록 A 입니다.

먼저 검색 엔진의 동의어 사전이 운영되는 2 가지 형태 부터 살펴봅니다.

 

6.1 형태 1 — 분석기·검색엔진 라이브러리 내장형

OpenSearch/Elasticsearch 의 synonym 또는 synonym_graph 토큰 필터, Apache Solr 의 SynonymFilterFactory 같이 분석기 파이프라인 안에 동의어 필터를 끼워 넣는 방식입니다. 인덱스 settings 의 분석기 정의에 동의어 사전을 등록하면, 토큰화 직후 같은 분석기 안에서 자동으로 동의어가 확장됩니다.

// OpenSearch 예시 — 분석기 내장형
"analysis": {
  "filter": {
    "ke_synonyms": {
      "type": "synonym_graph",
      "synonyms": ["대한항공, 코리안에어, KE, KAL", "제주항공, 제주에어, 7C, JJA", "아시아나항공, 아시아나, OZ, AAR", "위탁수하물, 체크인수하물"]
    }
  },
  "analyzer": {
    "ke_analyzer": {
      "tokenizer": "nori_tokenizer",
      "filter": ["nori_part_of_speech", "ke_synonyms"]
    }
  }
}
  • 장점: 토큰화·동의어가 같은 분석기 안에 묶여 일관성 보장.
  • 단점: 사전 변경 시 인덱스 close → settings 갱신 → open → 기존 문서 reindex 사이클 필요 (close-open 만으로는 settings 만 갱신되고 기존 색인된 토큰은 옛 동의어 매핑 그대로).
  • Kiwi 지원 여부: 미지원

 

6.2 형태 2 — 애플리케이션 레벨 외부 구현

토크나이저는 토큰화만 담당하고, 애플리케이션 코드가 토큰 시퀀스를 받아 외부 동의어 사전을 보고 OR 확장 하는 방식입니다. Kiwi 에는 사용자 사전과 달리 동의어 사전 등록 API (예: add_synonym()) 가 없으므로, 동의어 확장은 애플리케이션 코드로 직접 구현해야 합니다.

양방향은 로드 시점에 빌드: 운영자는 사전 파일(CSV)에 동의어 그룹 한 줄 만 작성합니다. §10.3 load_synonyms 가 로드 시점에 그룹의 모든 어휘를 매칭 구조로 펼쳐, 어느 어휘로 검색해도 그룹 전체가 매칭됩니다(양방향). 검색 시점 expand_query_for_bm25이미 빌드된 구조를 조회만 합니다. 한 방향으로만 확장해야 하는 관계는 => 단방향 모드로 작성합니다(§6.6·§9.1).

개념 예시 — 복합명사 동의어 그룹 (CSV 한 줄, 공백 포함 다중 토큰):

기내 반입, 휴대 수하물

검색 질의가 어느 표현이든, BM25 입력 질의로 그룹 전체가 OR 확장됩니다:

검색 "기내 반입 규정"   → 확장 "기내 반입 휴대 수하물 규정"
검색 "휴대수하물 무게"  → 확장 "기내 반입 휴대 수하물 무게"   ← 붙여 써도 매칭

→ "기내 반입" 으로 검색해도 휴대 수하물 이라고만 적힌 문서까지 매칭됩니다(양방향). 붙여 쓴 입력(휴대수하물)도 재토큰화로 같은 그룹에 수렴합니다. 매칭 알고리즘·정본 코드는 §6.5·§10.3 을 참조하세요.

  • 장점: 동의어를 바꿔도 문서 재색인 0 (검색 질의에만 동의어를 더함). 기내 반입 같은 여러 단어 동의어(복합명사)도 안전. 확장한 검색어를 공백으로 이어 보내면 BM25 가 OR 매칭하므로 특별한 검색 문법도 불필요.
  • 단점: Kiwi 가 동의어를 대신 처리하지 않아 확장 코드를 직접 구현 해야 함. 동의어 사전은 검색 때만 쓰지만, 확장 검색어가 색인 토큰과 맞물리려면 토큰화(사용자 사전·옵션)가 색인·검색 동일 해야 함 (§10.3). 갱신은 재배포로 반영 (hot reload 미지원, §9.4).

 

6.3 본 가이드의 형태 선택

Kiwi 가 형태 1 을 미지원할 뿐 아니라, Databricks Vector Search 가 BM25 채널에 사용자 정의 분석기를 끼울 지점을 미지원합니다. 따라서 본 가이드는 형태 2 (애플리케이션 레벨 외부 구현) 를 채택합니다. 적용 시점 결정(부록 A) 과는 독립된 형태 선택입니다.

 

6.4 적용 시점 결정 — 검색 시점 권장

본 가이드 권장: 동의어는 검색 시점 OR 확장 (Case B) 만 적용. 인덱싱 시 확장은 IDF 인플레이션·token graph 평탄화로 BM25 코퍼스 통계가 왜곡됩니다.

근거·외부 자료 인용 상세는 부록 A 참조.

 

6.5 다중 토큰 동의어

본 가이드는 동의어 어휘에 공백 포함 다중 토큰 (한 어휘 최대 5) 을 허용합니다. 위탁 수하물·기내 반입 같은 복합명사 표현이 항공 도메인에 흔하고, Kiwi 의 split_complex=True 가 붙여 쓴 복합명사를 개별 토큰으로 분해하므로 (위탁수하물['위탁','수하물']), 단일 토큰 키만으로는 이런 표현을 안정적으로 매칭할 수 없기 때문입니다. 본 절은 그 매칭 설계를 다룹니다.

6.5.1 데이터 흐름

동의어 확장은 LangGraph 앙상블의 FTS(BM25) 브랜치에만 적용됩니다. Semantic(벡터) 브랜치는 원문 임베딩을 그대로 쓰므로 동의어 영향이 없습니다.

query_type: FTS 브랜치는 self-managed 인덱스에 query_type="FULL_TEXT" (순수 BM25) 로 호출합니다. 단일 호출 Managed Hybrid (query_type="HYBRID", 벡터+키워드 내부 결합) 와 달리, 앙상블은 FTS·Semantic 을 개별 호출 후 결합 합니다.

6.5.2 매칭 알고리즘 (3단계)

expand_query_for_bm25 의 입력은 이미 Kiwi 토큰화 + 화이트리스트(SEARCH_TAGS) 필터를 거친 토큰 리스트입니다. 확장은 역할이 다른 세 단계 로 이뤄집니다 (정본 코드 §10.3):

단계 역할 한 줄 요약
Aho-Corasick 매칭 찾기 등록된 동의어 키와 일치하는 모든 구간을 한 번 스캔으로 찾음 (겹치는 것도 모두)
greedy 최장 비중첩 선택 고르기 찾은 매칭 중 각 위치에서 가장 긴 것을 서로 겹치지 않게 골라 확장
토큰 단위 중복 제거 정리 확장 결과를 개별 토큰으로 펼친 뒤 중복 토큰 제거

①은 "무엇이 매칭되는가"(엔진), ②는 "그중 무엇을 쓸까"(선택), ③은 "출력을 어떻게 정리할까"(중복 제거) — 서로 역할이 다릅니다.

① Aho-Corasick 매칭 — 찾기

여러 패턴(동의어 구)을 하나의 상태 기계로 합쳐, 질의를 한 번 통과시키면 걸리는 패턴을 모두 찾는 고전 다중패턴 검색 알고리즘이 Aho-Corasick 입니다. pyahocorasick 은 이를 C 로 구현한 파이썬 라이브러리입니다 (다중패턴 검색에 널리 쓰이는 표준). AC 는 겹치는 매칭까지 모두 돌려주며, 그중 무엇을 쓸지는 ②가 정합니다.

  • KEY_SEQUENCE 모드 — pyahocorasick 은 키를 글자 문자열(KEY_STRING) 또는 정수 시퀀스(KEY_SEQUENCE) 로 받습니다. 글자 단위면 수하물수하물함 안에서도 매칭되는 경계 오류가 생기므로, 토큰(형태소) 단위 매칭을 위해 KEY_SEQUENCE 를 씁니다.
  • 토큰 id 튜플 — 각 토큰에 정수 id 를 부여해(tok2id) 동의어 구를 정수 튜플로 등록하고, 질의도 같은 방식으로 변환합니다. 단일 토큰 동의어는 길이-1 키, 다중 토큰은 2~5 토큰 키로 함께 등록됩니다.
사전 등록 : 위탁 수하물, 체크인 수하물
tok2id   : 위탁→0, 수하물→1, 체크인→2
키(튜플)  : (0,1)="위탁 수하물",  (2,1)="체크인 수하물"
질의 "체크인 수하물 분실" → id (2,1,_UNK) → automaton.iter → (2,1) 매칭 = "체크인 수하물"

(_UNK = 사전에 없는 토큰의 sentinel id — 키에 절대 없는 값이라 매칭 안 됨)

② greedy 최장 비중첩 선택 — 고르기

①이 찾은 매칭은 서로 겹칠 수 있습니다. 그중 각 시작 위치에서 가장 긴 매칭을 택하고, 택한 토큰은 소비해 겹치지 않게, 왼→오로 진행합니다 (정식 용어로는 leftmost-longest matching). 사전에 그룹 둘이 있다고 가정:

그룹 1 (복합명사) : 위탁 수하물, 체크인 수하물
그룹 2 (단일어)   : 수하물, 짐, 가방

질의 A — "위탁 수하물 분실":

토큰 : [위탁, 수하물, 분실]
i=0  : '위탁 수하물'(2토큰) 과 '수하물'(1토큰) 둘 다 매칭 가능
       → 더 긴 '위탁 수하물' 채택(최장) → 2토큰 소비(비중첩)
i=2  : '분실' 미매칭 → 원형 유지
확장 : 위탁 수하물 체크인 분실

수하물 은 '위탁 수하물' 에 흡수되어 단독으로 짐·가방까지 확장되지 않습니다.

질의 B — "수하물 분실" (수하물 단독 등장 → 대조):

토큰 : [수하물, 분실]
i=0  : '수하물'(1토큰) 매칭 → 그룹 2 확장
확장 : 수하물 짐 가방 분실

→ 같은 수하물 이라도 앞 토큰 문맥 에 따라 결과가 달라집니다 (A 는 흡수, B 는 확장).

③ 토큰 단위 중복 제거 — 정리

②가 고른 매칭을 확장할 때, 결과를 개별 토큰 으로 펼친 뒤 중복 토큰을 제거합니다. 다중 토큰 동의어들이 공통 토큰을 공유하기 때문입니다 — 예: 위탁 수하물·체크인 수하물수하물 을 공유 → 펼치면 수하물 이 두 번 → 한 번으로 정리.

② 확장 결과 : 위탁 수하물 + 체크인 수하물  →  [위탁, 수하물, 체크인, 수하물]
③ 중복 제거 : 위탁 수하물 체크인           (수하물 1회)

왜 필수인가 — FULL_TEXT BM25 는 query_text 의 중복 term 을 점수에 선형 가산 합니다 (실측, self-managed Direct Access 인덱스):

질의 동일 문서 점수
수하물 1.0594
수하물 수하물 2.1188 (×2)
수하물 수하물 수하물 3.1782 (×3)

→ 중복을 두면 그 단어를 가진 문서만 부풀어 순위가 왜곡됩니다 (수하물 짐 가방수하물 짐 수하물 가방수하물-only 문서가 ·가방 문서를 추월). 그래서 출력은 항상 개별 토큰 + 중복 제거 형태입니다.

6.5.3 키 빌드 (사전 → 매칭 자동자 구축)

적용 시점: 키 빌드는 사전 로드 1회 (load_synonyms — 검색 워커·레플리카 부팅 시) 입니다. 질의마다가 아니며, 그 결과(Aho-Corasick 자동자)를 매 질의의 §6.5.2 ①②③ 가 조회만 합니다. (그래서 사전 갱신은 재배포로 반영 — §9.4)

한눈에 — 키 빌드는 사람이 쓴 CSV기계가 매칭에 쓸 자동자 로 변환·구축하는 단계입니다:

입력 : 동의어 사전 CSV  (사람이 쓴 그룹 — 예: "기내 반입, 휴대 수하물")
  │
  ▼  키 빌드 = load_synonyms  (부팅 시 1회)
  │   ├ 각 어휘를 검색용 Kiwi 로 재토큰화 → 정규 "키"
  │   └ 키들을 Aho-Corasick 자동자로 구축
  ▼
출력 : 매칭 구조(자동자 + tok2id)  → 매 질의의 §6.5.2 ①②③ 가 사용

같은 뜻이라도 사람마다 위탁수하물 처럼 붙여 쓰기도 하고 위탁 수하물 처럼 띄어 쓰기도 하며, Kiwi 가 단어를 쪼개는 방식도 사용자 사전 등록 여부에 따라 달라집니다. 그래서 CSV 에 적힌 글자를 그대로 키로 써 버리면, 정작 질의가 만든 토큰과 모양이 안 맞아 매칭을 놓칠 수 있습니다.

이를 피하려고 사전을 불러올 때(load_synonyms) 어휘를 검색·인덱싱에 쓰는 바로 그 경로(kiwi_search_tokens — §4.2 병합 + SEARCH_TAGS 필터) 로 한 번 돌려, 나온 토큰(소문자로 맞춰 공백으로 이은 것)을 키로 삼습니다. 질의·인덱스도 똑같은 경로를 거치니 키 = 질의 = 인덱스 가 같은 모양으로 정렬되어 매칭됩니다.

CSV "위탁수하물"     → kiwi_search_tokens → ['위탁','수하물'] → 키 "위탁 수하물"
CSV "위탁 수하물"    → kiwi_search_tokens → ['위탁','수하물'] → 키 "위탁 수하물"   ← 붙임·띄움이 같은 키로 수렴
질의 "위탁수하물 분실" → kiwi_search_tokens → ['위탁','수하물','분실']            ← 키와 동일 granularity

키 빌드는 역할이 다른 3단 규칙 으로 동작합니다 — ①이 기본이고, ②③은 ①이 안 되는 어휘만 받치는 폴백입니다:

단계 사용 함수 동작
① 기본 kiwi_search_tokens 검색·인덱싱과 동일 경로로 키 생성 → 키=질의 정렬 (대부분 어휘)
② 빈 결과 폴백 kiwi_key_tokens 단독 토큰화 시 명사 외 품사로만 잡혀 ①이 비는 어휘(예: →MAG, 달러→NNB)는 구두점만 제외하고 표면형 보존 → 문맥 질의의 NNG form 과 일치
③ 충돌 폴백 kiwi_key_tokens ①의 축약 키가 이미 등록된 다른 그룹 키와 겹치면, 미정렬(더 긴) 키로 다시 만들어 그룹 손실 방지

또한 한 어휘의 키가 비거나 5토큰을 초과하면 그 어휘만 제외하고 나머지로 그룹을 구성합니다 (제외분은 WARNING 로그 + dropped_members 집계). 살아남은 키가 2개 미만이면 그 그룹은 등록하지 않습니다.

신조어·과축약 합성어는 사용자 사전에 등록합니다. 베시넷·칠러 처럼 Kiwi 가 모르는 신조어는 단독 토큰화 시 자모·동사로 깨지고, 출항편·출발편 처럼 의존명사()가 붙은 합성어는 ①에서 머리단어(출항)로 축약돼 다른 그룹과 충돌할 수 있습니다. 이런 어휘는 전체 단어를 user_dict 에 NNG/NNP 로 등록 하면 단독·문맥 토큰화가 모두 단일 토큰으로 고정돼 키=질의가 보장됩니다 (§5). 즉 폴백(②③)은 안전장치이고, 이 부류는 user_dict 등록이 정공법 입니다.

여기서 구두점 은 괄호 ( )·마침표 .·하이픈 - 같은 문장부호를 말합니다 — 그룹 구분자 쉼표 , 는 그 전 CSV 파싱에서 이미 분리되어 키 빌드가 보지 않습니다.

"위험물(DG)" → 토큰화 → 위험물 · ( · DG · ) → 구두점 제거 → [위험물, DG]
"수하물."    → 토큰화 → 수하물 · .          → 구두점 제거 → [수하물]

 

6.6 단방향 확장 (=>)

§6.5 까지의 그룹(콤마)은 모든 어휘가 상호 등가인 양방향(equivalent) 입니다. 반면 한쪽 방향으로만 확장해야 자연스러운 관계도 있습니다 — 예를 들어 포괄어 질의를 세부 품목으로 넓히되, 세부 품목 검색이 포괄어 문서까지 끌어와 정밀도를 떨어뜨리는 것은 막고 싶은 경우입니다. 이를 위해 본 가이드는 단방향 매핑 => 를 표준으로 지원합니다 (OpenSearch synonym_graph 의 explicit mapping 과 동일 문법).

동작LHS => RHS 는 좌측(LHS) 어휘로 검색될 때만 우측(RHS)을 확장에 더합니다. load_synonymsLHS 어휘만 매칭 키로 등록하고, 매칭 시 확장값을 [원어] + RHS 로 둡니다. RHS 는 키가 아니므로 역방향은 없습니다.

사전 : 비상장비 => 산소마스크, 소화기, 구명조끼
질의 "비상장비 위치"  → 확장 "비상 장비 산소 마스크 소화기 구명조끼 위치"   (원어 유지 + RHS 추가)
질의 "소화기"        → 확장 "소화기"                                     (역방향 없음)

왜 원어를 유지(RHS 추가)하는가 — 동의어는 검색 시점에만 확장하고 인덱싱 시에는 적용하지 않으므로(§6.4), 문서는 원어(비상장비) 그대로 색인됩니다. Solr => 처럼 원어를 RHS 로 치환 하면 검색 질의가 색인 원어와 어긋나 recall 을 잃습니다. 치환이 안전한 것은 인덱싱 시점에도 동의어를 적용해 색인이 canonical 로 정규화될 때뿐이며, 본 아키텍처는 검색 시점 전용이라 원어 유지가 맞습니다.

equivalent vs directional 선택 — 두 어휘가 서로 바꿔 써도 되면 equivalent(콤마), 한쪽으로만 넓혀야 하면 directional(=>). 대부분의 동의어는 equivalent 이고, => 는 위 비대칭 관계에서만 사용합니다. 작성 규칙·skip 조건은 §9.1, 정본 코드는 §10.3 을 참조하세요.

 

7. 복합명사 처리 전략

복합명사는 항공 매뉴얼 도메인의 핵심 어휘 유형이고 BM25 검색 품질이 가장 흔들리는 지점입니다. §8 의 split_complex 와 §5 의 add_user_word·add_pre_analyzed_word복합명사 처리 정책 한 가지 주제로 묶어 다룹니다.

 

7.1 복합명사가 어려운 이유 (한국어·BM25 관점)

한국어 복합명사는 조사 없이 명사들을 자유롭게 이어붙여 새 단어를 만들 수 있는 교착어 특성 때문에 토크나이저·검색 엔진 양쪽에 동시에 부담을 줍니다. 위탁수하물·초과수하물요금·위험물포장구분코드 처럼 항공 매뉴얼에 빈출하는 표현은 사전에 없는 즉석 합성이 대부분입니다.

BM25 관점에서 세 가지가 동시에 작용합니다.

  • TF 분포 왜곡위탁수하물 통째 색인 시 문서당 1회 카운트. 분해 시 두 토큰이 각각 1회씩 → 문서 길이(dl) 증가.
  • IDF 약화수하물 같은 분해 토큰은 코퍼스 전체에서 흔해 IDF 가 낮음. 위탁수하물 통째(희소·고 IDF) 보다 변별력 감소.
  • 쿼리-문서 비대칭 — 인덱싱은 통째인데 사용자가 수하물 위탁 절차 로 풀어 쓰면 BM25 점수 0. 반대도 마찬가지.

복합명사 정책은 결국 어느 비대칭을 허용하고, 어느 쪽 토큰을 얼마나 풍부하게 색인할지 의 트레이드오프입니다.

 

7.2 Kiwi 의 복합명사 처리 메커니즘

Kiwi 의 합성명사 분할 옵션 — tokenize(..., split_complex=...) (kiwipiepy ≥ 0.18):

  • 본 가이드 표준: True (Kiwi 기본값은 False)
  • 동작: LM 기반 N-best 분석에서 분할 가능 형태소를 최대한 분할
  • 예시: 위탁수하물위탁/NNG + 수하물/NNG

사용자 사전과 휴리스틱의 우선순위 — Kiwi 내부적으로 합성명사 분해는 LM 점수 기반 N-best 탐색의 분기이며, 사용자 사전에 합성어가 등록되어 있으면 score 가 LM 분해 후보와 경쟁합니다. Score 권장값은 §5.2 정본 참조.

 

7.3 복합명사 등록 방법 비교

방법 동작 장점 단점 적용 시나리오
split_complex=True LM 자동 분해 사용자 사전 불필요 도메인 특화 합성어 오분해 위험 일반 코퍼스, 도메인 사전 미정비
add_user_word("위탁수하물", "NNG", 3) 통째 단일 NNG 키워드 명확, 고유명사 안정 부분 매칭 불가 단일 키워드로 검색되는 고유명사·제품명
add_pre_analyzed_word(...) 분해 강제 결정적 분해 부분 매칭 가능, 재현성 100% 사전 작성 비용 도메인 핵심 합성어 + 부분 검색 요구
add_user_word 통째 + 검색 시 동의어 확장 단일 인식 + 확장 양쪽 효과 동의어 사전 운영 부담 약어·코드 매핑

 

7.4 인덱싱·검색 정책 비교

# 인덱싱 검색 Nori 등가 효과 위험
A 분해 분해 decompound_mode=discard 권장 (본 가이드 표준) — Hybrid (BM25+dense) 의 dense 채널이 어휘 변형 보완. 인덱스 단순 분해 토큰 IDF↓ — dense 채널이 보완
B 분해 +
통째 혼합
분해 또는 통째 decompound_mode=mixed recall 최대 인덱스 크기 1.3~1.8x, BM25 b 재튜닝 (향후 도입 시 별도 SOP) — 향후 옵션
C 통째 통째 decompound_mode=none 정확 매칭 단순 부분 매칭 0
D 통째 분해 (없음) 비추 — 비대칭으로 hit 사실상 불가

권장 — 정책 A (분해 기본, §10 정본): Hybrid 검색의 dense 채널이 어휘 변형을 보완해 정책 A 단독으로 충분합니다. Nori 기본값 discard 와 동일 정책.

정책 B 도입 시 (향후 옵션) — 사용자 사전 TSV 통째 등록 (§5.5) + add_pre_analyzed_word 강제 분해 (§5.3) 또는 blocklist 우회 (kiwi.tokenize(..., blocklist=...), kiwipiepy 0.21+) + 이중 패스 union (split_complex=False/True 결과 결합) + BM25 b 재튜닝 (향후 도입 시 별도 SOP). 사용자 사전 score 가 LM 분해를 이겨 정책 C 로 퇴화 하는 함정 회피가 핵심.

 

7.5 항공 도메인 복합명사 카탈로그 (시작 예시)

운영팀 사전 빌드 시 참고 — 실제 사전은 도메인 PR 로 확장.

카테고리 예시 어휘 권장 처리
객실·수하물 위탁수하물·기내수하물·초과수하물·체크인카운터·환승게이트 add_user_word NNG +3
화물·위험물 위험물·리튬배터리·드라이아이스·마스터빌(MAWB)·하우스빌(HAWB) add_user_word NNG/NNP +3~+5
운항·스케줄 정기노선·임시노선·연결편·B777기재·A380기재 add_user_word NNG/NNP +3~+5
마일리지·멤버십 스카이패스·모닝캄클럽·밀리언마일러·아시아나클럽·리프레시포인트·보너스항공권 add_user_word NNP +5
항공 코드 KE001·B777·UN3480 add_user_word NNP +5 (편명은 패턴 등록 — 항공 코드 처리 방안 결정 후)

 

📚 시리즈 — 한국어 RAG 검색 (Kiwi)

댓글