From df598a83914cceb2faf7efc8de99fed8235e1130 Mon Sep 17 00:00:00 2001 From: Hulio Fernando Febrian Soejatno Date: Sun, 1 Nov 2026 23:23:10 -0500 Subject: [PATCH] Upload files to "/" --- lab_p10.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 lab_p10.py diff --git a/lab_p10.py b/lab_p10.py new file mode 100644 index 0000000..0f85f89 --- /dev/null +++ b/lab_p10.py @@ -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__) + )() \ No newline at end of file