Template
tugas10
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Lab 2 - Bangun indeks & cari (25 menit).
|
||||
|
||||
1. Letakkan 2-3 PDF di folder dokumen/, jalankan indeks, pastikan tiap file melaporkan jumlah chunk.
|
||||
2. Ajukan 5 pertanyaan dengan query(); periksa apakah chunk teratas benar-benar relevan.
|
||||
3. Ajukan 2 pertanyaan berparafrase; apakah pencarian semantik tetap menemukannya?
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from laporan_utils import Laporan
|
||||
from rag_utils import bangun_indeks, cocok, query
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
UKURAN, OVERLAP = 1000, 0.15
|
||||
|
||||
# kw = kata kunci yang HARUS ada di chunk teratas agar dianggap relevan
|
||||
PERTANYAAN = [
|
||||
{"q": "Siapa yang memperkenalkan RAG dan pada tahun berapa?", "kw": ["Lewis", "2020"]},
|
||||
{"q": "Berapa dimensi embedding yang dihasilkan model MiniLM?", "kw": ["384"]},
|
||||
{"q": "Apa itu overfitting dan apa cirinya?", "kw": ["overfitting", "data latih"]},
|
||||
{"q": "Undang-undang apa yang mengatur pelindungan data pribadi di Indonesia?", "kw": ["27 Tahun 2022"]},
|
||||
{"q": "Bagaimana cara memitigasi prompt injection?", "kw": ["memisahkan instruksi"]},
|
||||
]
|
||||
PARAFRASE = [
|
||||
{"q": "Bagaimana mencegah model bahasa mengarang jawaban yang keliru?", "kw": ["halusinasi"],
|
||||
"catatan": "Teks memakai kata 'halusinasi'; pertanyaan memakai 'mengarang jawaban yang keliru'."},
|
||||
{"q": "Kapan sebuah model dianggap terlalu menghafal data latihan?", "kw": ["overfitting"],
|
||||
"catatan": "Teks memakai 'overfitting' dan 'data latih'; pertanyaan memakai 'menghafal' dan 'data latihan'."},
|
||||
]
|
||||
|
||||
|
||||
def ringkas(t, n=150):
|
||||
t = " ".join(t.split())
|
||||
return t if len(t) <= n else t[:n] + "..."
|
||||
|
||||
|
||||
def jalankan(indeks, daftar, k=3):
|
||||
hasil = []
|
||||
for s in daftar:
|
||||
atas = query(indeks, s["q"], k)
|
||||
rel = [cocok(a["teks"], s["kw"]) for a in atas]
|
||||
hasil.append((s, atas, rel))
|
||||
return hasil
|
||||
|
||||
|
||||
def main():
|
||||
# ---- Langkah 1
|
||||
print("=== Langkah 1: membangun indeks ===")
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
idx = bangun_indeks(ukuran=UKURAN, overlap=OVERLAP, backend="auto")
|
||||
log_indeks = buf.getvalue()
|
||||
print(log_indeks)
|
||||
idx_leksikal = bangun_indeks(ukuran=UKURAN, overlap=OVERLAP, backend="tfidf", verbose=False)
|
||||
per_file = {}
|
||||
for m in idx.meta:
|
||||
per_file[m["file"]] = per_file.get(m["file"], 0) + 1
|
||||
|
||||
# ---- Langkah 2
|
||||
print("=== Langkah 2: 5 pertanyaan ===")
|
||||
h2 = jalankan(idx, PERTANYAAN)
|
||||
for s, atas, rel in h2:
|
||||
print(f"\nQ: {s['q']} (kata kunci: {s['kw']})")
|
||||
for r, (a, ok) in enumerate(zip(atas, rel), 1):
|
||||
print(f" #{r} skor={a['skor']:.3f} {a['file']}#{a['no']} relevan={ok} | {ringkas(a['teks'])}")
|
||||
n_top1 = sum(rel[0] for _, _, rel in h2)
|
||||
n_top3 = sum(any(rel) for _, _, rel in h2)
|
||||
|
||||
# ---- Langkah 3
|
||||
print("\n=== Langkah 3: parafrase ===")
|
||||
sem = jalankan(idx, PARAFRASE)
|
||||
lek = jalankan(idx_leksikal, PARAFRASE)
|
||||
for (s, a1, r1), (_, a2, r2) in zip(sem, lek):
|
||||
print(f"\nQ: {s['q']}")
|
||||
print(f" {idx.backend}: top1 relevan={r1[0]} top3 relevan={any(r1)} | {a1[0]['file']}#{a1[0]['no']}")
|
||||
print(f" tfidf : top1 relevan={r2[0]} top3 relevan={any(r2)} | {a2[0]['file']}#{a2[0]['no']}")
|
||||
|
||||
# ---- Kesimpulan dari angka
|
||||
sem_top3 = sum(any(r) for _, _, r in sem)
|
||||
lek_top3 = sum(any(r) for _, _, r in lek)
|
||||
sem_top1 = sum(r[0] for _, _, r in sem)
|
||||
lek_top1 = sum(r[0] for _, _, r in lek)
|
||||
kes = (f"Pada 5 pertanyaan langsung, chunk peringkat 1 relevan pada {n_top1}/5 pertanyaan dan salah satu dari "
|
||||
f"3 chunk teratas relevan pada {n_top3}/5 pertanyaan. Pada 2 pertanyaan parafrase, backend "
|
||||
f"'{idx.backend}' menemukan chunk relevan di peringkat 1 pada {sem_top1}/2 dan di 3 teratas pada "
|
||||
f"{sem_top3}/2, sedangkan pembanding TF-IDF (kecocokan bentuk kata) {lek_top1}/2 dan {lek_top3}/2. ")
|
||||
gagal_top1 = [s["q"] for s, atas, rel in h2 if not rel[0]]
|
||||
if gagal_top1:
|
||||
kes += ("Pertanyaan yang chunk peringkat 1-nya belum relevan: " + "; ".join(f'"{q}"' for q in gagal_top1) +
|
||||
". Penyebab yang paling mungkin: kalimat jawaban terbelah di batas chunk sehingga kata kuncinya "
|
||||
"terpisah, sementara chunk di sebelahnya memuat topik yang mirip. ")
|
||||
if sem_top1 == lek_top1 and sem_top3 == lek_top3:
|
||||
kes += ("Pada dua parafrase ini, semantik dan TF-IDF memberi hasil yang sama, jadi tes ini belum bisa "
|
||||
"membedakan keduanya: pertanyaan masih berbagi akar kata dengan teks (model, jawaban, data latih) "
|
||||
"dan TF-IDF n-gram karakter menangkap variasi bentuk kata. ")
|
||||
if idx.backend == "lsa":
|
||||
kes += ("Catatan: backend LSA hanya semantik ringan (hubungan antar kata dipelajari dari korpus yang kecil), "
|
||||
"sehingga parafrase yang benar-benar tidak berbagi kata akan sulit. Untuk hasil lebih kuat, pasang "
|
||||
"sentence-transformers; kode ini otomatis memakainya.")
|
||||
print("\nKesimpulan:", kes)
|
||||
|
||||
# ---- Laporan PDF
|
||||
L = Laporan(os.path.join(BASE, "Lab2_Indeks_dan_Pencarian.pdf"), "Lab 2 - Bangun Indeks & Cari",
|
||||
f"ukuran chunk {UKURAN}, overlap {int(OVERLAP * 100)}%, backend: {idx.backend}")
|
||||
L.h1("Langkah 1. Membangun indeks")
|
||||
L.p("Tiga PDF diletakkan di folder dokumen/. Setiap file melaporkan jumlah chunk-nya:")
|
||||
L.tabel([["File", "Jumlah chunk"]] + [[f, n] for f, n in per_file.items()] + [["Total", len(idx.chunks)]],
|
||||
[9 * 28.35, 4 * 28.35])
|
||||
L.kode(log_indeks.rstrip())
|
||||
L.h1("Langkah 2. Lima pertanyaan dan pemeriksaan chunk teratas")
|
||||
L.p("Sebuah chunk dianggap relevan bila memuat semua kata kunci jawaban. Tabel menunjukkan 3 chunk teratas.")
|
||||
for s, atas, rel in h2:
|
||||
L.h2(s["q"])
|
||||
L.tabel([["Peringkat", "Skor", "Sumber", "Relevan?", "Cuplikan"]] +
|
||||
[[r, f"{a['skor']:.3f}", f"{a['file']} #{a['no']}", "ya" if ok else "tidak", ringkas(a["teks"], 140)]
|
||||
for r, (a, ok) in enumerate(zip(atas, rel), 1)],
|
||||
[1.6 * 28.35, 1.3 * 28.35, 3.8 * 28.35, 1.6 * 28.35, 8.7 * 28.35])
|
||||
L.p(f"Ringkasan: chunk peringkat 1 relevan pada {n_top1}/5 pertanyaan; ada chunk relevan di 3 teratas pada {n_top3}/5.")
|
||||
L.h1("Langkah 3. Pertanyaan berparafrase")
|
||||
L.p("Dua pertanyaan memakai kata yang berbeda dari teks dokumen. Dibandingkan: pencarian semantik "
|
||||
f"({idx.backend}) vs TF-IDF.")
|
||||
baris = [["Pertanyaan", "Catatan", f"{idx.backend}: top1 / top3", "TF-IDF: top1 / top3"]]
|
||||
for (s, a1, r1), (_, a2, r2) in zip(sem, lek):
|
||||
baris.append([s["q"], s["catatan"], f"{'ya' if r1[0] else 'tidak'} / {'ya' if any(r1) else 'tidak'}",
|
||||
f"{'ya' if r2[0] else 'tidak'} / {'ya' if any(r2) else 'tidak'}"])
|
||||
L.tabel(baris, [5 * 28.35, 6 * 28.35, 3 * 28.35, 3 * 28.35])
|
||||
for (s, a1, r1), _ in zip(sem, lek):
|
||||
L.p(f"Chunk teratas untuk \"{s['q']}\": {a1[0]['file']} #{a1[0]['no']} (skor {a1[0]['skor']:.3f})")
|
||||
L.kode(ringkas(a1[0]["teks"], 400))
|
||||
L.h1("Kesimpulan")
|
||||
L.p(kes)
|
||||
L.halaman_baru()
|
||||
L.h1("Lampiran: kode lab2_indeks_cari.py")
|
||||
L.kode(open(__file__, encoding="utf-8").read())
|
||||
L.simpan()
|
||||
print("\nLaporan: Lab2_Indeks_dan_Pencarian.pdf")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user