- 파이프라인: prefix → 후보생성 → 다중검증(temps) → 합의 → 컷오프 → 저장/체크포인트 → 최종집계.
구조는 좋아요. 병렬성·결정성·관측성만 보강하면 실전형 됩니다.
1) 성능(요청/비용/스루풋)
1-1. LLM 호출 수(대략치)
- 접두사 수:
2letter/alpha
= 26×26=676. - 접두사 당: 후보생성 1회 + 후보 개수(≈ batch×overgen_factor)의 검증×temps.
- 예:
batch=35, overgen=1.6, temps=3
→ 후보 ≈ 56개 → 검증 호출 ≈ 56×3=168 + 후보생성 1 = 169/접두사. - 전체: 676×169 ≈ 114K 호출. (큰 비용/시간!)
개선
- 2단계 검증: 1차
temps=[0.3,0.6]
, 경계값만 3번째 온도 추가. - 캐시: 단어 전역 캐시로 재검증 방지(접두사 교차 중복).
- 동적 overgen: 접두사별 실수확률이 낮으면 다음 라운드에서 overgen↑, 높으면 ↓.
# __init__
self.validate_cache = {}
def self_consistency(self, word):
if word in self.validate_cache:
return self.validate_cache[word]
# ... 기존 검증 ...
self.validate_cache[word] = (ok, meta)
return ok, meta
1-2. 동시성
지금은 순차 requests. Ollama는 동시처리를 꽤 받쳐요.
- async + 세마포어로 접두사 내 후보 검증 병렬화(예: 동시 4~8).
- 실패/타임아웃은 지수백오프+지터.
(원하면 httpx.AsyncClient
버전으로 교체 패치 줄게.)
1-3. 저장주기 기준
i % save_every
는 “열거 인덱스” 기준이라 이미 완료된 접두사도 i는 증가 → 실제 처리량과 불일치.
processed = 0
for pfx in prefixes:
if pfx in done:
continue
# 처리 성공/실패 후:
processed += 1
if processed % save_every == 0 or self.shutdown_requested:
self.save_progress(payload)
1-4. 대용량 JSON I/O
- 수만/수십만 엔트리면 JSON도 큼.
- 옵션화:
--gzip-out
로.json.gz
저장, 혹은.ndjson
(스트리밍 친화). - 뷰어(웹) 고려하면
.json
유지 + gzip 전송이 현실적.
2) 안정성(파싱/신호/복구/호환)
2-1. JSON 추출 정규식
현재 \{.*\}
(그리디) → 여러 블록 섞이면 과탐.
def _extract_json(self, text: str) -> str:
m = re.search(r"\{[\s\S]*?\}", text) # 비탐욕
return m.group(0) if m else "{}"
추가로 Ollama가 지원하면:
"format": "json" # 강제 JSON 모드 (모델/서버 버전별 지원 여부 확인)
2-2. 유니코드 기호 정규화
엔대시/엠대시/굴림따옴표 → ASCII 변환 안하면 필터에 걸러짐.
def _clean_line_to_word(self, line: str) -> str:
line = (line.replace("–","-").replace("—","-").replace("’","'")
.replace("“","\"").replace("”","\""))
# 이후 기존 처리...
2-3. 신호 처리와 저장
SIGINT/SIGTERM
플래그만 세움 → 호출 중 블로킹이면 저장 지연.
- atexit 후크로 마지막에도 저장.
- ask_ollama 전/후에
if self.shutdown_requested: break
를 좀 더 자주 체크.
2-4. Ollama 옵션 호환
repeat_penalty
등 모델별 무시될 수 있음. 옵션 세트를 인자화:
ap.add_argument("--ollama-option", action="append",
help="key=value (e.g., seed=42, num_ctx=4096)")
# 파싱해 dict로 넣기
3) 데이터 품질(어휘/희귀/POS/정의/예문)
3-1. rare_prefixes 과도
sb
, sd
, sn
, sp
같은 “실제로 흔한” 조합까지 strict → 리콜↓.
- 간소화: q*/x*/z*/kx/kz/… 정도로 줄이기.
- 또는 적응형 strict: 접두사별 채택률이
batch
10% 미만이면 다음 회차 strict로 전환.
# 처리 후:
self.prefix_yield[prefix].append(len(accepted))
# 에폭 혹은 다음 실행 때 이력 기반으로 strict 여부 결정
3-2. validator 프롬프트 튜닝
현재는 LLM에게 ‘rarity 1–5’를 맡김 → 편차 큼. 룰을 더 구체화해서 일관성↑.
- rarity: 1=일상(신문/TV), 2=보통(일반서적), 3=전문/드문, 4=아주 드문/시대착오, 5=틀림/허구
- proper_noun: 대문자 고정/지명/인명/브랜드/두문자어
- pos: noun|verb|adjective|adverb|preposition|conjunction|interjection 중 하나
- example: 20단어 이하, 현실적 맥락, 헤드워드 강조 불필요
가능하면 스키마까지:
prompt = """You are a dictionary editor. Return ONLY JSON:
{
"word": "...",
"pos": "noun|verb|adjective|adverb|preposition|conjunction|interjection",
"short_def": "...", // <= 20 words, non-circular
"example": "...", // <= 20 words, realistic
"proper_noun": false,
"rarity": 1,
"confidence": 0.8,
"accept": true,
"reasons": []
}
Rules:
- Reject proper nouns, abbreviations, misspellings, or rarity>=4.
- ...
Target word: '{word}'
"""
(Ollama가 format=json
지원하면 꼭 같이 사용 추천)
3-3. self-consistency 합의 로직
- 현재 단순 다수결 + 평균 confidence. score는
conf * (accepts/tries)
. - 경계단어가 과락되는 경향 → 가중 다수결(Borda-like) 혹은 보정식 추천.
score = conf * (0.5 + 0.5 * (len(accepts)/len(results))) # 보정
# 또는 최소 투표수 + 최소 conf 동시 충족: accepts>=2 and conf>=0.65
3-4. 정의/예문 머지
- “최단 텍스트”를 고르는 건 최소 정보 선택이 되어 품질 저하 가능.
- 간단 개선: 문장 유사도(토큰 중복비) 중심 선택.
def _pick_central(items):
items = [s.strip() for s in items if s and s.strip()]
if not items: return ""
def overlap(a,b):
ta,tb = set(a.lower().split()), set(b.lower().split())
inter = len(ta & tb); denom = max(1, min(len(ta), len(tb)))
return inter/denom
best, best_sc = None, -1
for i,a in enumerate(items):
sc = sum(overlap(a,b) for j,b in enumerate(items) if i!=j)
if sc>best_sc: best, best_sc = a, sc
return best[:160]
3-5. 예문 길이/품질 강제
프롬프트에서 20단어 제한했지만 런타임 체크 없음. 간단히 잘라냄.
def _trim_words(s, n=20):
ws = s.split()
return " ".join(ws[:n])
merged_ex = _trim_words(merged_ex, 20)
merged_def = _trim_words(merged_def, 20)
3-6. POS 표준화
LLM은 adj
, adjective
등 표기가 섞일 수 있음 → 표준화 테이블로 정리.
POS_MAP = {
"adj": "adjective", "adjective": "adjective",
"adv": "adverb", "adverb": "adverb",
"n": "noun", "noun": "noun",
"v": "verb", "verb": "verb",
"prep":"preposition","preposition":"preposition",
"conj":"conjunction","conjunction":"conjunction",
"interj":"interjection","interjection":"interjection",
}
pos = POS_MAP.get(pos.strip().lower(), pos.strip().lower())
3-7. stopwords와 축약형
"it's"
가 stopwords에 있어 자동 배제(의도 OK). 다만 사전이라면 축약형을 별 항목으로 둘지 정책 필요. 현재 설계는 “일상 빈출 기능어 제외”에 맞춰져 있어 문제 없음.
4) 운영성(로그/메트릭/리포트/재현성)
4-1. 메트릭 세분화
지금은 전역 카운트 몇 개뿐. 접두사 히트율·평균 score·평균 rarity 등 튜닝 근거가 필요.
# 접두사 레벨 집계
self.prefix_metrics = defaultdict(lambda: {"cand":0,"acc":0,"rej":0,"avg_score":0.0})
# 단어 채택 시:
m = self.prefix_metrics[prefix]
m["acc"] += 1
m["avg_score"] = (m["avg_score"]*(m["acc"]-1) + meta["score"])/m["acc"]
최종 JSON metadata.statistics
에 accept_rate, avg_score, avg_rarity 추가.
4-2. 재현성(seed)
Ollama 일부 모델은 seed
옵션 제공. 지원 시 인자로 노출해 디버깅/재현성↑.
ap.add_argument("--seed", type=int, help="Ollama sampling seed")
# options에 포함
4-3. 실패 단어 아카이브
거절 사유를 접두사별로 모아두면 프롬프트 개선에 즉효.
self.reject_log = [] # {word,prefix,reasons,tries}
# not ok or cutoff 시 append
4-4. 종료/에러 시 강제 저장
atexit
+ 예외 훅에서 마지막 스냅샷 남기기.
import atexit
atexit.register(lambda: self.save_progress(current_payload))
5) 프롬프트(생성/검증) 세부
5-1. 후보생성(strict) 프롬프트
좋은데, “정확히 그 문구(No common words found.)”를 소문자 처리로 검사한 건 안전. 다만 대형 모델이 한 줄로 콤마 구분을 내보내면 줄분리 실패 가능 → “one word per line”은 이미 있음. 추가로 숫자/불릿 금지를 더 강조해도 좋음.
- One word per line.
- No numbering, bullets, commas, slashes.
5-2. 검증 프롬프트 스키마/룰 강화
앞서 적은 스키마 + rarity 루브릭 + pos 목록 고정 + example/def 길이 규칙을 꼭 반영.
가능하면 "format":"json"
.
6) finalize(후처리) 품질
6-1. 중복해결 기준
지금은 score 큰 것을 남김 → OK. pos/def/example도 최고 score 쪽을 쓰도록 일치시키면 좋음. 현재는 단순 병합이라, 두 검증결과가 상이한 POS일 때 최다 POS를 쓰고 def/example는 중앙값을 쓰는 혼합 전략. 괜찮지만, 한 결과의 일관성을 유지하고 싶다면 “최고 score 개체의 필드 전부”를 채택하는 모드도 옵션화.
6-2. 통계 확장
accept_rate = accepted/attempts
avg_score
,avg_confidence
,avg_rarity
prefix_hit_rate
(0 accepted 비율)
이 4개만 있어도 다음 러닝에서 파라미터 조정 근거가 충분해요.
7) 실제 운영 시 권장 파라미터 프로파일
- 리콜 우선(초벌 사전 구축)
--mode alpha --batch 25 --overgen 2.2 --temps 0.2,0.5 --score-cut 0.5 --rarity-cut 4
- 정밀 우선(공개 전 골라내기)
--mode 2letter --batch 30 --overgen 1.4 --temps 0.2,0.4,0.8 --score-cut 0.65 --rarity-cut 3
- 균형
--mode adaptive --batch 30 --overgen 1.8 --temps 0.3,0.6 --score-cut 0.55
8) 웹 뷰어/인덱싱까지 생각한 구조
- 현재 JSON 스키마는 뷰어 만들기 딱 좋음:
entries[*].word|pos|definition|example|rarity|score
. - **단일 HTML(CSS+JS)**로 로컬 파일 선택 → 인덱스 빌드(Trie/접두사 검색) → 필터(품사/희귀도/길이).
- 원하면 다음 턴에 하나의 HTML 파일로 검색·정렬·페이지네이션·다크모드까지 넣어 드림.
9) 요약: 반드시 넣을 “작은 패치” 7개
- JSON 추출 비탐욕 정규식 + (가능하면)
format=json
. - 유니코드 기호 정규화(– — ’ “ ”).
- 저장 트리거를 “실제 처리 건수” 기준으로.
- 전역 검증 캐시(중복 호출 제거).
- 합의 점수 보정식 or 경계 케이스 3번째 온도 조건부 추가.
- 정의/예문 선택을 “중앙값(유사도 중심)”으로 개선 + 20단어 트림.
- rare_prefixes 간소화 또는 적응형 strict.
답글 남기기