--- id: tfidf-bilingual-scoring title: "TF-IDF 이중언어 스코어링" category: "AI_and_ML" status: "draft" verification_status: "applied" canonical_id: "" aliases: ["TF-IDF", "토크나이저", "tokenizer", "한국어 영어 토큰화", "동의어 확장", "검색 점수", "stop words", "섹션 청킹"] duplicate_of: "" source_trust_level: "A" confidence_score: 0.92 created_at: 2026-06-13 updated_at: 2026-06-13 review_reason: "" merge_history: [] tags: ["tfidf", "tokenizer", "search", "nlp", "korean", "connectai"] raw_sources: ["ConnectAI/src/retrieval/scoring.ts", "ConnectAI/src/retrieval/chunker.ts"] applied_in: ["ConnectAI"] github_commit: "" --- # [[TF-IDF 이중언어 스코어링]] ## 🎯 한 줄 통찰 (One-line insight) 임베딩 엔진 없이도 쓸 수 있는 가벼운 검색의 핵심은 "좋은 토크나이저 + TF-IDF 가중"이며, ConnectAI 는 **한국어/영어 혼합 토크나이저·불용어·동의어 확장·제목 가중·충돌 신호** 를 더해 단순 `includes()` 매칭을 넘어선 점수를 낸다 [S1]. ## 🧠 핵심 개념 (Core concepts) 1. **TF-IDF:** 용어 빈도(TF, 문서 내 흔함) × 역문서빈도(IDF, 전체에서 희소함). 흔하면서 그 문서에만 자주 나오는 단어가 고득점 [S1]. 2. **이중언어 토큰화:** 한글-영문 경계를 분리(`성능optimization` → `성능` `optimization`), 특수기호 보존(C++, C#, .net) [S1]. 3. **불용어(Stop words):** 검색에 무의미한 단어(영/한 각각 집합)를 제거 [S1]. 4. **동의어 확장:** 질의 토큰을 관련어로 확장(`성능` → `performance`, `optimization`, `최적화`) [S1]. 5. **제목 가중:** 제목 일치는 본문보다 3배 가중(`TITLE_MULTIPLIER: 3.0`) [S1]. 6. **토큰 캐시:** 같은 텍스트의 토큰화를 Map 으로 캐시(한도 초과 시 전체 clear) [S1]. ## 🧩 추출된 패턴 (Extracted patterns) - **중앙 설정 객체:** `SCORING_CONFIG` 에 불용어·동의어·임계값·가중치를 모아 한 곳에서 조정 [S1]. - **경계 분리 정규식:** `replace(/([a-z0-9]+)([가-힣]+)/gi, '$1 $2')` 로 언어 경계 분할, `split(/[^a-z0-9가-힣+#.-]+/g)` 로 특수기호(C++) 보존 [S1]. - **TF 계산 1회화:** `buildTermCounts` 로 문서당 용어 빈도 맵을 한 번 만들고 질의 용어마다 재사용 — O(질의×문서) 재스캔 회피 [S1]. - **IDF smoothing:** 문서 수가 적을 때도 안정적이도록 평활화 적용 [S1]. - **충돌 신호 탐지:** "반대/충돌/conflict/vs" 등 지표 단어 수로 conflictSeverity 산출 — 지식 충돌 가능 문서를 표시 [S1]. - **순수 함수 분리:** chunker/scoring 은 fs·네트워크 의존 없는 순수 함수라 단위 테스트·재현이 쉽다 [S2]. ## 📖 세부 내용 (Details) ### 토크나이저 (가장 중요한 부품) ```typescript const normalized = text.toLowerCase() .replace(/[​-‍]/g, '') // zero-width 제거 .replace(/[^\w\s가-힣_+#.-]/g, ' '); // 의미 없는 기호 → 공백 const splitText = normalized .replace(/([a-z0-9]+)([가-힣]+)/gi, '$1 $2') // 영→한 경계 분리 .replace(/([가-힣]+)([a-z0-9]+)/gi, '$1 $2');// 한→영 경계 분리 const tokens = splitText.split(/[^a-z0-9가-힣+#.-]+/g) // C++, C#, .net 보존 .map(t => t.trim().replace(/[.,]$/g, '')) .filter(t => /[가-힣]/.test(t) ? t.length >= 1 : t.length >= 2) // 한글 1자+, 영문 2자+ .filter(t => !STOP_EN.has(t) && !STOP_KO.has(t)); ``` 한국어는 한 글자도 의미를 가질 수 있어 1자 이상 허용, 영문은 2자 이상으로 노이즈를 줄인다 [S1]. ### 동의어 확장 질의 `[성능]` → `[성능, performance, optimization, 최적화, speed]`. Set 으로 중복 제거 후 반환. brain 문서가 영어로, 질의가 한국어로 와도(또는 반대) 매칭되게 하는 양국어 다리 [S1]. ### 섹션 청킹과의 결합 긴 문서를 통째 색인하면 5000자 다주제 문서가 흐릿한 한 단위가 되어 정밀도가 떨어진다. `chunker.ts` 가 헤딩(`#`~`######`) 경계로 섹션을 나누고, 짧은 섹션은 병합·긴 섹션은 문단 경계로 재분할한다. fenced code block(```) 안의 `#` 는 헤딩으로 보지 않는다. 헤딩 breadcrumb 을 보존해 청크가 문맥을 잃지 않게 한다 [S2]. ## ⚖️ 모순 및 업데이트 (Contradictions & updates) - **TF-IDF 의 한계:** 어휘가 다르면(동의어 사전에 없는 환언) 못 잡는다. 그래서 임베딩 하이브리드로 보완한다 → [[RAG 검색 파이프라인]]. - **동의어 사전의 유지보수:** 수작업 사전이라 도메인이 늘면 누락이 생긴다. 핵심 도메인 용어 위주로 관리하는 절충. - **형태소 분석 부재:** 한국어 조사/어미를 정밀 분해하지 않는다(경량 우선). 정밀도가 더 필요하면 형태소 분석기 도입 여지. ## 🛠️ 적용 사례 (Applied in summary) - `ConnectAI/src/retrieval/scoring.ts` — tokenize/expandQuery/TF-IDF/충돌 탐지 [S1]. - `ConnectAI/src/retrieval/chunker.ts` — splitIntoSections 섹션 청킹 [S2]. ## 💻 코드 패턴 (Code patterns) ```typescript // 1) 중앙 설정 객체 — 가중치/임계값 한 곳에서 (src/retrieval/scoring.ts) const SCORING_CONFIG = { STOP_WORDS_EN: new Set(['the','a','and',/* ... */]), STOP_WORDS_KO: new Set(['그리고','그런데',/* ... */]), SYNONYM_DATA: [['성능', ['performance','optimization','최적화','speed']], /* ... */], TITLE_MULTIPLIER: 3.0, GLOBAL_CACHE_LIMIT: 2000, }; // 2) TF 계산 1회화 (src/retrieval/scoring.ts) function buildTermCounts(tokens: string[]): Map { const counts = new Map(); for (const t of tokens) counts.set(t, (counts.get(t) || 0) + 1); return counts; // 질의 용어마다 재스캔 대신 이 맵을 조회 } // 3) 동의어 확장 (src/retrieval/scoring.ts) export function expandQuery(tokens: string[]): string[] { const expanded = new Set(tokens); for (const t of tokens) (synonymMap.get(t) ?? []).forEach(s => expanded.add(s)); return Array.from(expanded); } // 4) 헤딩 경계 섹션 청킹 — fence 안의 # 무시 (src/retrieval/chunker.ts) const fence = line.trimStart().startsWith('```'); if (fence) inFence = !inFence; const m = !inFence ? line.match(HEADING_RE) : null; // 코드블록 내 #는 헤딩 아님 ``` ## ✅ 검증 상태 및 신뢰도 - **상태:** draft - **검증 단계:** applied - **출처 신뢰도:** A - **신뢰 점수:** 0.92 - **중복 검사 결과:** 신규 생성 (New discovery) ## 🔗 지식 그래프 (Knowledge Graph) - **상위/루트:** [[ConnectAI 아키텍처 개요]] - **관련 개념:** [[RAG 검색 파이프라인]], [[5계층 메모리 시스템]], [[TypeScript 기초와 타입 시스템]] - **참조 맥락:** 로컬 LLM 이 가벼운 텍스트 검색·토큰화·점수 함수를 작성할 때(특히 한/영 혼용) 참조. ## 📚 출처 (Sources) - [S1] ConnectAI/src/retrieval/scoring.ts — 토크나이저, TF-IDF, 동의어, 불용어, 충돌 탐지, 캐시 - [S2] ConnectAI/src/retrieval/chunker.ts — 섹션 청킹(순수 함수) ## 📝 변경 이력 (Change history) - 2026-06-13: ConnectAI 코드 분석 기반 초안 생성.