hee_!J
feat(experiments): D9 ํ•œ๊ตญ์–ด reranker ํ‰๊ฐ€ (Dongjin-kr/ko-reranker)
1d550b9
Raw
History Blame Contribute Delete
9.51 kB
"""D6 ํ›„์†: ํ•œ๊ตญ์–ด reranker(dongjin-kr/ko-reranker) ํšจ๊ณผ ํ‰๊ฐ€
D6์—์„œ BAAI/bge-reranker-base๊ฐ€ Hybrid ๋Œ€๋น„ ํšจ๊ณผ ์—†์Œ์ด ํ™•์ธ๋จ (์˜์–ด ํ•™์Šต ๋ชจ๋ธ + ํ•œ๊ตญ์–ด ์ฝ”ํผ์Šค mismatch).
ํ•œ๊ตญ์–ด ํŠนํ™” reranker๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋’ค์ง‘ํžˆ๋Š”์ง€ ๊ฒ€์ฆ.
ํ‰๊ฐ€ ๋ฐฉ๋ฒ•:
- D2์˜ 6๊ฐœ ๋Œ€ํ‘œ ์ฟผ๋ฆฌ(์ง์ ‘ + ์˜๋ฏธ ์šฐํšŒ) ์‚ฌ์šฉ
- 3๊ฐœ ๋ชจ๋“œ ร— 6 ์ฟผ๋ฆฌ = 18 ์กฐํ•ฉ
- hybrid (no rerank, baseline)
- hybrid + BAAI/bge-reranker-base (์˜์–ด reranker)
- hybrid + Dongjin-kr/ko-reranker (ํ•œ๊ตญ์–ด reranker)
- ๊ฐ ๋ชจ๋“œ์˜ top-3 ๊ฒฐ๊ณผ๋ฅผ CRAG grader(gpt-4o-mini)๋กœ 0~1 ์ ์ˆ˜
- avg score / ๋ชจ๋“œ๋ณ„ ๋น„๊ต โ†’ ํ•œ๊ตญ์–ด reranker๊ฐ€ ์˜๋ฏธ ์žˆ๋Š” ๊ฐœ์„  ๊ฐ€์ ธ์˜ค๋Š”์ง€ ํŒ๋‹จ
์‹คํ–‰: python -m experiments.reranker_compare.benchmark
"""
import os
import time
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from agents.rag.crag import grade_retrieval
from agents.rag.hybrid_store import hybrid_search
from agents.rag.store import load_document
plt.rcParams["font.family"] = ["Apple SD Gothic Neo", "AppleGothic", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
OUT_DIR = Path(__file__).parent
CHART_DIR = OUT_DIR / "charts"
QUERIES = [
("CD ์‚ฐํฌ ์ง์ ‘", "Photo Step CD-X ์‚ฐํฌ ์›์ธ ๋ Œ์ฆˆ ๋…ธ๊ด‘"),
("CMP ์ง์ ‘", "CMP ์Šฌ๋Ÿฌ๋ฆฌ ์œ ๋Ÿ‰ ์ด์ƒ SLURRY_FLOW"),
("Etch ์ง์ ‘", "Etch ํŠธ๋ Œ์น˜ ๊นŠ์ด ๋ถ€์กฑ ์‹๊ฐ ๊ฐ€์Šค"),
("์˜๋ฏธ ์šฐํšŒ 1", "๋…ธ๊ด‘ ์žฅ๋น„ ํ‘œ๋ฉด ์˜ค์—ผ ์ฒญ์†Œ"),
("์˜๋ฏธ ์šฐํšŒ 2", "ํ›„๊ณต์ • ์ˆ˜์œจ ์†์‹ค ์ •๋Ÿ‰ ์˜ํ–ฅ"),
("์˜๋ฏธ ์šฐํšŒ 3", "์ •๋น„ ์ฃผ๊ธฐ ํ‘œ์ค€ ๊ฐ€์ด๋“œ"),
]
MODES = [
("hybrid (no rerank)", None),
("BAAI/bge-reranker-base (์˜์–ด)", "BAAI/bge-reranker-base"),
("Dongjin-kr/ko-reranker (ํ•œ๊ตญ์–ด)", "Dongjin-kr/ko-reranker"),
]
TOP_K = 3
CANDIDATES = 10
def _rerank_with_model(query: str, candidates: list[str], model_name: str, top_k: int) -> list[str]:
"""๋ชจ๋ธ ๋ช…์‹œ์  ์ง€์ •ํ•œ cross-encoder rerank"""
os.environ["RERANK_MODEL"] = model_name
# cache invalidation์„ ์œ„ํ•ด rerank ๋ชจ๋“ˆ ํ•จ์ˆ˜ ์ง์ ‘ ํ˜ธ์ถœ
from agents.rag.rerank import _build_reranker
docs = [load_document(d) for d in candidates]
pairs = [[query, doc] for doc in docs]
scores = _build_reranker(model_name).predict(pairs)
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
return [doc_id for doc_id, _ in ranked[:top_k]]
def collect():
print("=== ์›Œ๋ฐ์—… (BAAI + ko-reranker ๋กœ๋“œ) ===")
t0 = time.time(); _rerank_with_model("warmup", ["FMEA-PH-007"], "BAAI/bge-reranker-base", 1); print(f" BAAI: {time.time()-t0:.1f}s")
t0 = time.time(); _rerank_with_model("warmup", ["FMEA-PH-007"], "Dongjin-kr/ko-reranker", 1); print(f" ko-reranker: {time.time()-t0:.1f}s")
rows = []
for label, query in QUERIES:
print(f"\n[{label}] '{query}'")
# hybrid top-10 candidates ํ•œ ๋ฒˆ ์‚ฐ์ถœ (๋ชจ๋“  ๋ชจ๋“œ ๊ณตํ†ต)
candidates = hybrid_search(query, top_k=CANDIDATES)
result_row = {"label": label, "query": query, "modes": {}}
for mode_name, model in MODES:
if model is None:
# no rerank: hybrid top-3 ๊ทธ๋Œ€๋กœ
doc_ids = candidates[:TOP_K]
rerank_ms = 0.0
else:
t0 = time.time()
doc_ids = _rerank_with_model(query, candidates, model, TOP_K)
rerank_ms = (time.time() - t0) * 1000
docs = [{"doc_id": d, "snippet": load_document(d)[:600]} for d in doc_ids if load_document(d)]
grades = grade_retrieval(query, docs)
avg_score = sum(g.get("score", 0.0) for g in grades) / max(len(grades), 1)
result_row["modes"][mode_name] = {
"doc_ids": doc_ids,
"avg_score": round(avg_score, 3),
"rerank_ms": rerank_ms,
"grades": grades,
}
print(f" {mode_name:38s} avg={avg_score:.3f} ms={rerank_ms:.0f} docs={doc_ids}")
rows.append(result_row)
return rows
def aggregate(rows):
return {
mode_name: {
"avg_score": np.mean([r["modes"][mode_name]["avg_score"] for r in rows]),
"rerank_ms": np.mean([r["modes"][mode_name]["rerank_ms"] for r in rows]),
}
for mode_name, _ in MODES
}
def make_chart(agg, rows):
CHART_DIR.mkdir(exist_ok=True)
mode_names = [n for n, _ in MODES]
short_names = ["No Rerank", "BAAI (EN)", "ko-reranker"]
avg_scores = [agg[n]["avg_score"] for n in mode_names]
colors = ["#94a3b8", "#3b82f6", "#ef4444"]
fig, ax = plt.subplots(figsize=(8.5, 5.5))
bars = ax.bar(short_names, avg_scores, color=colors)
for b, v in zip(bars, avg_scores):
ax.text(b.get_x() + b.get_width() / 2, v + 0.01, f"{v:.3f}",
ha="center", fontsize=10, fontweight="bold")
ax.set_ylim(0, 1.05)
ax.set_ylabel("ํ‰๊ท  LLM relevance score (6 ์ฟผ๋ฆฌ ํ‰๊ท )")
ax.set_title("Reranker ๋น„๊ต - ํ•œ๊ตญ์–ด reranker๊ฐ€ D6 ๊ฒฐ๊ณผ๋ฅผ ๋’ค์ง‘๋‚˜?")
ax.grid(axis="y", alpha=0.3)
fig.tight_layout()
fig.savefig(CHART_DIR / "reranker_comparison.png", dpi=150)
plt.close(fig)
def write_results(rows, agg):
base_score = agg["hybrid (no rerank)"]["avg_score"]
en_score = agg["BAAI/bge-reranker-base (์˜์–ด)"]["avg_score"]
ko_score = agg["Dongjin-kr/ko-reranker (ํ•œ๊ตญ์–ด)"]["avg_score"]
lines = [
"# D9 (D6 ํ›„์†): ํ•œ๊ตญ์–ด reranker ํ‰๊ฐ€",
"",
"D6์—์„œ `BAAI/bge-reranker-base`(์˜์–ด ํ•™์Šต)๊ฐ€ Hybrid ๋Œ€๋น„ ํšจ๊ณผ ์—†์Œ์ด ํ™•์ธ๋˜์–ด,",
"ํ•œ๊ตญ์–ด ํŠนํ™” reranker `Dongjin-kr/ko-reranker`๋กœ ์žฌํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.",
"",
"## ์‹คํ—˜ ์„ค์ •",
"",
"- ์ฟผ๋ฆฌ: D2์˜ 6๊ฐœ ๋Œ€ํ‘œ ์ฟผ๋ฆฌ (์ง์ ‘ 3, ์˜๋ฏธ ์šฐํšŒ 3)",
"- ๋ฐฑ์—”๋“œ: hybrid (BM25+FAISS+RRF) top-10 ํ›„๋ณด๋ฅผ ๋‘ reranker๋กœ ์žฌ์ •๋ ฌ",
"- ์ฑ„์ : CRAG grader (gpt-4o-mini)๊ฐ€ top-3 ๊ฒฐ๊ณผ๋ฅผ 0~1๋กœ ํ‰๊ฐ€",
"- ๋น„๊ต: hybrid (no rerank) / BAAI (์˜์–ด) / ko-reranker (ํ•œ๊ตญ์–ด)",
"",
"## ๊ฒฐ๊ณผ ์š”์•ฝ (6 ์ฟผ๋ฆฌ ํ‰๊ท )",
"",
"| ๋ชจ๋“œ | ํ‰๊ท  relevance | rerank ํ‰๊ท  latency | vs No Rerank |",
"|---|---|---|---|",
f"| hybrid (no rerank) | {base_score:.3f} | 0 ms | baseline |",
f"| BAAI/bge-reranker-base (์˜์–ด) | {en_score:.3f} | {agg['BAAI/bge-reranker-base (์˜์–ด)']['rerank_ms']:.0f} ms | {(en_score - base_score):+.3f} |",
f"| Dongjin-kr/ko-reranker (ํ•œ๊ตญ์–ด) | {ko_score:.3f} | {agg['Dongjin-kr/ko-reranker (ํ•œ๊ตญ์–ด)']['rerank_ms']:.0f} ms | {(ko_score - base_score):+.3f} |",
"",
"## ์‹œ๊ฐํ™”",
"",
"![Reranker ๋น„๊ต](charts/reranker_comparison.png)",
"",
"## ์ฟผ๋ฆฌ๋ณ„ ์ƒ์„ธ",
"",
"| ์ฟผ๋ฆฌ | hybrid | BAAI (EN) | ko-reranker (KO) |",
"|---|---|---|---|",
]
for r in rows:
cells = [f"{r['modes'][n]['avg_score']:.2f}" for n, _ in MODES]
lines.append(f"| {r['label']} | " + " | ".join(cells) + " |")
# ์ฑ„ํƒ ๊ฒฐ๋ก  - ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ž๋™ ํŒ๋‹จ
ko_wins = ko_score > en_score and ko_score > base_score
ko_helps = ko_score > base_score
decision = "ko-reranker ์ฑ„ํƒ" if ko_wins else ("ko-reranker ๊ฒ€ํ†  ๊ฐ€๋Šฅ (no rerank๋ณด๋‹จ ์šฐ์œ„)" if ko_helps else "๋‘ reranker ๋ชจ๋‘ ๋ณธ ์ฝ”ํผ์Šค์—์„  hybrid์— ๋ฏธ๋‹ฌ")
lines += [
"",
"## ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ",
"",
f"1. **No Rerank baseline**: {base_score:.3f}",
f"2. **์˜์–ด reranker (BAAI)**: {en_score:.3f} (vs baseline {(en_score - base_score):+.3f}) - D6์—์„œ ๋ณธ ํŒจํ„ด ์žฌํ™•์ธ",
f"3. **ํ•œ๊ตญ์–ด reranker (Dongjin-kr)**: {ko_score:.3f} (vs baseline {(ko_score - base_score):+.3f})",
f"4. **๊ฒฐ๋ก **: {decision}",
"",
"## ์ฑ„ํƒ",
"",
]
if ko_wins:
lines += [
f"**๊ธฐ๋ณธ reranker๋ฅผ `Dongjin-kr/ko-reranker`๋กœ ๊ถŒ์žฅ** (`RERANK_MODEL` ํ™˜๊ฒฝ๋ณ€์ˆ˜).",
f"ํ•œ๊ตญ์–ด ๋„๋ฉ”์ธ์—์„œ ์˜์–ด reranker ๋Œ€๋น„ +{(ko_score - en_score):.3f} ์ ์ˆ˜ ์šฐ์œ„, baseline ๋Œ€๋น„ +{(ko_score - base_score):.3f} ์šฐ์œ„.",
]
elif ko_helps:
lines += [
"**ko-reranker๋Š” ์˜์–ด reranker๋ณด๋‹จ ์˜๋ฏธ ์žˆ๋Š” ๊ฐœ์„ ** (baseline ๋Œ€๋น„ ์–‘์ˆ˜).",
"ํ•˜์ง€๋งŒ hybrid ๋‹จ๋… ๋Œ€๋น„ ์šฐ์œ„๊ฐ€ ๊ฒฐ์ •์ ์ด์ง€ ์•Š์•„ ๊ธฐ๋ณธ backend๋Š” hybrid ์œ ์ง€.",
"์ฝ”ํผ์Šค 100+ ํ™•์žฅ ์‹œ ko-reranker๋กœ ์žฌํ‰๊ฐ€ ๊ถŒ์žฅ (ํ™˜๊ฒฝ๋ณ€์ˆ˜ `RAG_BACKEND=hybrid_rerank` + `RERANK_MODEL=Dongjin-kr/ko-reranker`).",
]
else:
lines += [
f"**๋‘ reranker ๋ชจ๋‘ ๋ณธ ์ฝ”ํผ์Šค์—์„  hybrid ๋‹จ๋…์— ๋ฏธ๋‹ฌ** (BAAI {(en_score - base_score):+.3f}, ko-reranker {(ko_score - base_score):+.3f}).",
"๊ทผ๋ณธ ์›์ธ: ์ฝ”ํผ์Šค๊ฐ€ ~10๋ฌธ์„œ๋กœ ์ž‘์•„ hybrid top-3์ด ์ด๋ฏธ ์ •๋‹ต์— ๊ทผ์ ‘ โ†’ reranker๊ฐ€ ๋” ์ข‹๊ฒŒ ์ •๋ ฌํ•  ์—ฌ์ง€ ๋ถ€์กฑ.",
"**D6 ๊ฒฐ๋ก  (์ฝ”ํผ์Šค ํ™•์žฅ์ด reranker ํšจ์šฉ์˜ ์„ ๊ฒฐ ์กฐ๊ฑด) ์žฌํ™•์ธ**. ์ฝ”ํผ์Šค 100+ ์‹œ ์žฌํ‰๊ฐ€.",
]
lines.append("")
(OUT_DIR / "results.md").write_text("\n".join(lines), encoding="utf-8")
print(f"--- ์ €์žฅ: {OUT_DIR / 'results.md'} ---")
def main():
rows = collect()
print("\n--- ์ง‘๊ณ„ ---")
agg = aggregate(rows)
for n, v in agg.items():
print(f" {n}: avg={v['avg_score']:.3f}, ms={v['rerank_ms']:.0f}")
make_chart(agg, rows)
write_results(rows, agg)
if __name__ == "__main__":
main()