File size: 8,371 Bytes
c7e536f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | """Bangun notebook showcase model (kode + teks + output nyata) lalu eksekusi
+ render ke HTML untuk ditampilkan di tab "Notebook" aplikasi.
Output:
- notebooks/05_model_showcase.ipynb (executed; bisa dibuka di HF Files tab)
- frontend/public/notebook.html (render Jupyter, di-embed React tab)
Notebook self-contained: tiap eksekusi nyatanya menyentuh index + eval CSV
yang sudah ada (cepat & deterministik). Jalankan ulang tiap update model:
cd backend && python -m scripts.build_showcase_notebook
"""
from __future__ import annotations
import sys
from pathlib import Path
import nbformat as nbf
from nbconvert import HTMLExporter
from nbconvert.preprocessors import ExecutePreprocessor
ROOT = Path(__file__).resolve().parents[2]
BACKEND = ROOT / "backend"
NB_OUT = ROOT / "notebooks" / "05_model_showcase.ipynb"
HTML_OUT = ROOT / "frontend" / "public" / "notebook.html"
def md(text: str):
return nbf.v4.new_markdown_cell(text)
def code(src: str):
return nbf.v4.new_code_cell(src)
SETUP = f'''
import sys, json, warnings
from pathlib import Path
warnings.filterwarnings("ignore")
ROOT = Path(r"{ROOT.as_posix()}")
sys.path.insert(0, str(ROOT / "backend"))
import pandas as pd
pd.set_option("display.max_colwidth", 46)
from app.indexing.loader import load_all_indexes
from app.indexing.hybrid import HybridIndex
from app.preprocessing import PreprocessingPipeline
from app.search.gazetteer import Gazetteer
from app.search.pipeline import smart_rank
from scripts.eval_smart import load_listings
idx = load_all_indexes(ROOT / "data" / "indexes", include_neural=True)
bm25, tfidf, neural = idx["bm25"], idx["tfidf"], idx["indobert"]
pipe = PreprocessingPipeline()
pre = lambda s: pipe.process(s).processed
hybrid = HybridIndex(bm25, neural, query_preprocessor=pre)
gz = Gazetteer.load()
listings = load_listings()
print(f"Corpus: {{len(listings)}} listing | vocab BM25: {{len(bm25.bm25.idf)}} term")
print(f"Index siap: {{', '.join(idx.keys())}} + smart + hybrid")
'''.strip()
PREP = '''
# Pipeline preprocessing 9-stage pada contoh teks (judul + deskripsi pemilik)
contoh = "Kost Putri AC KM Dlm dkt UNILA 800rb/bln, wifi kenceng"
res = pipe.process(contoh, trace=True)
print("INPUT :", res.raw)
for s in res.trace:
out = s["output"] if isinstance(s["output"], str) else " | ".join(map(str, s["output"]))
print(f" {s['stage']:<20} -> {out[:70]}")
print("HASIL :", res.processed)
print("Harga terdeteksi:", res.extracted_prices)
'''.strip()
QUERY = '''
# Bandingkan lima model pada satu query natural language
q = "kos putri dekat unila wifi murah"
def top(model, n=5):
if model == "bm25": ids = [(h.doc_id, h.score) for h in bm25.query(pre(q), top_k=n)]
elif model == "tfidf":ids = [(h.doc_id, h.score) for h in tfidf.query(pre(q), top_k=n)]
elif model == "neural":ids = [(h.doc_id, h.score) for h in neural.query(q, top_k=n)]
elif model == "hybrid":ids = [(h.doc_id, h.score) for h in hybrid.query(q, top_k=n)]
else: ids = smart_rank(q, bm25, listings, gz, top_k=n, preprocess=pre)[0]
return [listings[i].judul for i, _ in ids]
pd.DataFrame({m: top(m) for m in ["bm25","tfidf","neural","hybrid","smart"]})
'''.strip()
SMART = '''
# Smart pipeline: query understanding + geo + fusion (model live)
res, understood, relaxed = (lambda r: (r[0], r[1], r[2]))(
smart_rank(q, bm25, listings, gz, top_k=5, preprocess=pre))
print("Yang dipahami sistem dari query:")
for k, v in understood.items():
if v: print(f" {k:<10}: {v}")
print("\\nTop-5 smart:")
for did, score in res:
r = listings[did]
print(f" [{r.tipe:<6} Rp{r.harga_per_bulan:>8} {(r.kecamatan or '-'):<14}] {r.judul[:44]}")
'''.strip()
EVAL = '''
# Hasil evaluasi 30 query (dibaca dari CSV hasil eksperimen)
df = pd.read_csv(ROOT / "eval" / "results.csv")
agg = (df.groupby("model")[["p_at_5","p_at_10","ap","ndcg_at_10","rr"]]
.mean().round(3).sort_values("ap", ascending=False))
agg.columns = ["P@5","P@10","MAP","NDCG@10","MRR"]
agg
'''.strip()
CHART = '''
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(7, 3.4))
order = agg.sort_values("MAP")
colors = ["#2563eb" if m == "smart" else "#94a3b8" for m in order.index]
ax.barh(order.index, order["MAP"], color=colors)
ax.set_xlabel("MAP (standard top-K, n=30)"); ax.set_title("Perbandingan MAP per model β smart (biru) = model live")
for i, v in enumerate(order["MAP"]): ax.text(v + 0.004, i, f"{v:.3f}", va="center", fontsize=9)
plt.tight_layout(); plt.show()
'''.strip()
CONSTRAINT = '''
# Constraint Satisfaction @5: % top-5 yang penuhi SEMUA kebutuhan user
# (gender + budget + fasilitas + radius 3km kampus) β bebas pooling bias
cs = pd.read_csv(ROOT / "eval" / "results_constraints.csv")
cols = [c for c in cs.columns if c.startswith("cs_at_5_")]
cs_mean = cs[cols].mean().round(3).sort_values(ascending=False)
cs_mean.index = [c.replace("cs_at_5_","") for c in cs_mean.index]
cs_mean.to_frame("mean CS@5 (n=30)")
'''.strip()
EXPERIMENTS = '''
# Ringkasan eksperimen lain (dibaca dari artefak eval)
import json
abl = pd.read_csv(ROOT / "eval" / "preprocess_ablation.csv")[["config","map","delta_map"]]
pb = json.loads((ROOT / "eval" / "explore_pooling_bias.json").read_text())["per_model"]
print("Ablation preprocessing (delta MAP saat stage dimatikan):")
print(abl.to_string(index=False))
print("\\nPooling bias β MAP saat pool adil (5 model) vs BM25-only:")
for m, d in pb.items():
print(f" {m:<8} {d['map_bm25pool']:.3f} -> {d['map_unionpool']:.3f} (delta {d['delta']:+.3f})")
'''.strip()
def main() -> int:
nb = nbf.v4.new_notebook()
nb.cells = [
md("# KozyNear β Showcase Model IR\n\n"
"Notebook reproducible: lima model retrieval (TF-IDF, BM25, Neural "
"MiniLM, Hybrid, **Smart**) di corpus **227 listing kos REAL** "
"Bandar Lampung. Tiap sel dieksekusi sungguhan; output di bawah "
"adalah hasil nyata, bukan tangkapan layar.\n\n"
"Mata Kuliah Temu Kembali Informasi β Universitas Lampung."),
md("## 1. Setup: muat corpus + index + pipeline"),
code(SETUP),
md("## 2. Preprocessing 9-stage\n\nJargon domain (`KM Dlm`β`kamar mandi "
"dalam`), ekstraksi harga, stemming Sastrawi β langkah demi langkah."),
code(PREP),
md("## 3. Lima model pada satu query\n\n"
"`\"kos putri dekat unila wifi murah\"` β bandingkan judul top-5 "
"tiap model."),
code(QUERY),
md("## 4. Smart pipeline (model live)\n\nMemecah query jadi constraint "
"terstruktur (gender/harga/fasilitas/anchor), lalu fusi teks + geo "
"+ atribut dengan hard filter."),
code(SMART),
md("## 5. Evaluasi 30 query β metrik standard\n\nMAP, P@K, NDCG, MRR "
"per model."),
code(EVAL),
code(CHART),
md("## 6. Constraint Satisfaction @5 (lensa kebutuhan user)\n\n"
"Bebas pooling bias: mengukur apakah hasil benar-benar memenuhi "
"gender + budget + fasilitas + jarak kampus."),
code(CONSTRAINT),
md("## 7. Eksperimen pendukung\n\nAblation preprocessing & kuantifikasi "
"pooling bias."),
code(EXPERIMENTS),
md("## Kesimpulan\n\n**Smart** unggul di MAP standard (0.359) dan "
"dominan di CS@5 (0.867 vs BM25 0.527, p=0.0001) tanpa model neural "
"di runtime. Skor standard cenderung meremehkan smart karena "
"*pooling bias* (lihat sel 7): saat pool dibuat adil, jarak smart "
"vs BM25 makin lebar. Detail metodologi di `LAPORAN.md`."),
]
nb.metadata["kernelspec"] = {"name": "python3", "display_name": "Python 3", "language": "python"}
print("[execute] menjalankan notebook (load neural model, butuh ~1-2 menit)...")
ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
ep.preprocess(nb, {"metadata": {"path": str(BACKEND)}})
NB_OUT.parent.mkdir(parents=True, exist_ok=True)
nbf.write(nb, str(NB_OUT))
print(f"[saved] {NB_OUT}")
exporter = HTMLExporter()
exporter.exclude_input_prompt = False
body, _ = exporter.from_notebook_node(nb)
HTML_OUT.parent.mkdir(parents=True, exist_ok=True)
HTML_OUT.write_text(body, encoding="utf-8")
print(f"[saved] {HTML_OUT} ({len(body)//1024} KB)")
return 0
if __name__ == "__main__":
sys.exit(main())
|