사전 아키텍처 & 흐름

  • 파이프라인: 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.statisticsaccept_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개

  1. JSON 추출 비탐욕 정규식 + (가능하면) format=json.
  2. 유니코드 기호 정규화(– — ’ “ ”).
  3. 저장 트리거를 “실제 처리 건수” 기준으로.
  4. 전역 검증 캐시(중복 호출 제거).
  5. 합의 점수 보정식 or 경계 케이스 3번째 온도 조건부 추가.
  6. 정의/예문 선택을 “중앙값(유사도 중심)”으로 개선 + 20단어 트림.
  7. rare_prefixes 간소화 또는 적응형 strict.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다