Update project
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Lab Terbimbing P-10: RAG Part 1 (Chunking, ChromaDB, Hit@k)
|
||||
|
||||
Persiapan:
|
||||
pip install chromadb sentence-transformers pypdf
|
||||
Taruh 2-3 PDF berbasis teks di folder dokumen/
|
||||
|
||||
Jalankan bertahap:
|
||||
python lab_p10.py lab1
|
||||
python lab_p10.py lab2
|
||||
python lab_p10.py lab3
|
||||
"""
|
||||
import random
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import chromadb
|
||||
from chromadb.utils import embedding_functions
|
||||
from pypdf import PdfReader
|
||||
|
||||
BASE = Path(__file__).resolve().parent # folder tempat lab_p10.py berada
|
||||
FOLDER = BASE / "dokumen"
|
||||
# Setiap kali script dijalankan -> folder indeks BARU (tidak bentrok dengan run lain)
|
||||
RUN_ID = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
DB_PATH = str(BASE / "chroma_db" / f"run_{RUN_ID}")
|
||||
MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
|
||||
|
||||
|
||||
# ---------- fungsi dasar ----------
|
||||
def baca_pdf(path):
|
||||
reader = PdfReader(path)
|
||||
return "\n".join((p.extract_text() or "") for p in reader.pages)
|
||||
|
||||
|
||||
def potong(teks, ukuran=800, tumpang=100):
|
||||
hasil, mulai = [], 0
|
||||
while mulai < len(teks):
|
||||
hasil.append(teks[mulai:mulai + ukuran])
|
||||
mulai += ukuran - tumpang
|
||||
return [h for h in hasil if h.strip()]
|
||||
|
||||
|
||||
def daftar_pdf():
|
||||
pdfs = sorted(FOLDER.glob("*.pdf"))
|
||||
if not pdfs:
|
||||
sys.exit(f"Tidak ada PDF di {FOLDER}. Letakkan 2-3 PDF dulu.")
|
||||
return pdfs
|
||||
|
||||
|
||||
def buat_koleksi(nama, ukuran, tumpang):
|
||||
print(f"Indeks run ini disimpan di: {DB_PATH}")
|
||||
emb = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=MODEL)
|
||||
client = chromadb.PersistentClient(path=DB_PATH)
|
||||
koleksi = client.get_or_create_collection(
|
||||
name=nama, embedding_function=emb, metadata={"hnsw:space": "cosine"}
|
||||
)
|
||||
for pdf in daftar_pdf():
|
||||
chunks = potong(baca_pdf(pdf), ukuran, tumpang)
|
||||
if not chunks:
|
||||
print(f"PERINGATAN: {pdf.name} kosong (mungkin PDF scan), dilewati.")
|
||||
continue
|
||||
koleksi.upsert(
|
||||
ids=[f"{pdf.stem}-{i}" for i in range(len(chunks))],
|
||||
documents=chunks,
|
||||
metadatas=[{"sumber": pdf.name, "chunk": i} for i in range(len(chunks))],
|
||||
)
|
||||
print(f"[{nama}] {pdf.name} -> {len(chunks)} chunk")
|
||||
return koleksi
|
||||
|
||||
|
||||
# ---------- LAB 1: eksperimen chunking ----------
|
||||
def lab1():
|
||||
pdf = daftar_pdf()[0]
|
||||
teks = baca_pdf(pdf)
|
||||
print(f"File: {pdf.name} | total {len(teks)} karakter")
|
||||
print("=== 500 karakter pertama ===")
|
||||
print(teks[:500])
|
||||
print("\n(Jika kosong/aneh: PDF hasil scan, butuh OCR.)\n")
|
||||
|
||||
for ukuran in (500, 1000, 1500):
|
||||
tumpang = int(ukuran * 0.15) # overlap 15%
|
||||
chunks = potong(teks, ukuran, tumpang)
|
||||
print(f"\n##### ukuran={ukuran}, overlap={tumpang} -> {len(chunks)} chunk #####")
|
||||
for c in random.sample(chunks, min(3, len(chunks))):
|
||||
print("-" * 50)
|
||||
print(c)
|
||||
print("-" * 50)
|
||||
print("\nCatat: jumlah chunk tiap ukuran, dan apakah ada chunk "
|
||||
"yang terpotong di tengah kalimat/tabel.")
|
||||
|
||||
|
||||
# ---------- LAB 2: bangun indeks & cari ----------
|
||||
PERTANYAAN_LAB2 = [
|
||||
"Berapa jumlah gelas sampel untuk setiap merek air mineral?",
|
||||
"Apa saja ukuran pemusatan data yang dihitung?",
|
||||
"Apa tujuan dari praktikum statistik deskriptif ini?",
|
||||
"Merek air mineral mana yang beratnya paling konsisten?",
|
||||
"Data apa saja yang termasuk skala pengukuran rasio?",
|
||||
]
|
||||
PARAFRASE_LAB2 = [
|
||||
"Seberapa lebar sebaran tinggi badan cowok dibanding cewek?",
|
||||
"Pakai aplikasi apa data hasil timbangan diolah?",
|
||||
]
|
||||
|
||||
|
||||
def tampil(koleksi, daftar):
|
||||
for q in daftar:
|
||||
hasil = koleksi.query(query_texts=[q], n_results=3)
|
||||
print(f"\nQ: {q}")
|
||||
for doc, meta, jarak in zip(hasil["documents"][0],
|
||||
hasil["metadatas"][0],
|
||||
hasil["distances"][0]):
|
||||
print(f" [{meta['sumber']} · chunk {meta['chunk']}] jarak={jarak:.3f}")
|
||||
print(" ", doc[:200].replace("\n", " "), "...")
|
||||
|
||||
|
||||
def lab2():
|
||||
koleksi = buat_koleksi("pedoman", 800, 100)
|
||||
print("\n===== 5 PERTANYAAN BIASA =====")
|
||||
tampil(koleksi, PERTANYAAN_LAB2)
|
||||
print("\n===== 2 PERTANYAAN PARAFRASE =====")
|
||||
tampil(koleksi, PARAFRASE_LAB2)
|
||||
print("\nCatat: apakah chunk teratas relevan? Apakah parafrase tetap ketemu?")
|
||||
|
||||
|
||||
# ---------- LAB 3: ukur Hit@k (+ MRR) ----------
|
||||
UJI = [
|
||||
("Berapa mean berat air mineral Alfa pada data tunggal?", "225,3"),
|
||||
("Berapa standar deviasi tinggi badan laki-laki?", "6,65"),
|
||||
("Berapa varians tinggi badan perempuan?", "8,97"),
|
||||
("Software apa yang dipakai untuk mengolah data?", "Minitab"),
|
||||
("Berapa gelas sampel yang ditimbang untuk tiap merek?", "40 gelas"),
|
||||
("Merek air mineral apa saja yang diteliti?", "Aqua, Alfamart, dan Vit"),
|
||||
("Apa kelebihan boxplot dibanding mean?", "tahan terhadap nilai ekstrim"),
|
||||
("Data apa yang termasuk skala nominal?", "NRP, jenis kelamin"),
|
||||
("Berapa skewness data kelompok merek Alfa?", "-0,1826"),
|
||||
("Berapa IQR tinggi badan laki-laki?", "11,25"),
|
||||
("Berapa mean berat air mineral Vit?", "201,75"),
|
||||
("Apa itu presisi dalam pengukuran?", "konsistensi hasil pengukuran"),
|
||||
("Berapa rata-rata jumlah SKS responden?", "20,765"),
|
||||
("Untuk apa statistika inferensial digunakan?", "statistika inferensial digunakan"),
|
||||
("Berapa range tinggi badan perempuan?", "8,20"),
|
||||
]
|
||||
|
||||
|
||||
def hit_at_k(koleksi, uji, k):
|
||||
benar = 0
|
||||
for q, kunci in uji:
|
||||
docs = koleksi.query(query_texts=[q], n_results=k)["documents"][0]
|
||||
if any(kunci.lower() in d.lower() for d in docs):
|
||||
benar += 1
|
||||
return benar / len(uji)
|
||||
|
||||
|
||||
def mrr(koleksi, uji, k=5):
|
||||
total = 0.0
|
||||
for q, kunci in uji:
|
||||
docs = koleksi.query(query_texts=[q], n_results=k)["documents"][0]
|
||||
for rank, d in enumerate(docs, 1):
|
||||
if kunci.lower() in d.lower():
|
||||
total += 1 / rank
|
||||
break
|
||||
return total / len(uji)
|
||||
|
||||
|
||||
def lab3():
|
||||
if len(UJI) < 15:
|
||||
print(f"PERINGATAN: baru {len(UJI)} pertanyaan uji; tugas meminta 15.\n")
|
||||
pengaturan = {"chunk500": (500, 75), "chunk1000": (1000, 150)}
|
||||
ringkasan = {}
|
||||
for nama, (ukuran, tumpang) in pengaturan.items():
|
||||
koleksi = buat_koleksi(nama, ukuran, tumpang)
|
||||
ringkasan[nama] = {f"Hit@{k}": hit_at_k(koleksi, UJI, k) for k in (1, 3, 5)}
|
||||
ringkasan[nama]["MRR"] = mrr(koleksi, UJI)
|
||||
|
||||
print("\n===== HASIL =====")
|
||||
for nama, skor in ringkasan.items():
|
||||
print(nama, " | ".join(f"{m}={v:.2f}" for m, v in skor.items()))
|
||||
print("\nTulis kesimpulan 3-4 kalimat: ukuran terbaik, pertanyaan yang gagal, dugaan penyebab.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
perintah = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
{"lab1": lab1, "lab2": lab2, "lab3": lab3}.get(
|
||||
perintah, lambda: print(__doc__)
|
||||
)()
|
||||
Reference in New Issue
Block a user