commit 99fe6f0d76af6b0ab99d46f30a58407bee8f6beb Author: Raphael Date: Mon Nov 2 10:33:58 2026 +0700 P-10 RAG Embedding dan Vector Database diff --git a/cari.py b/cari.py new file mode 100644 index 0000000..35a10c8 --- /dev/null +++ b/cari.py @@ -0,0 +1,122 @@ + +import chromadb +from chromadb.utils import embedding_functions + + +# ========================================== +# PENGATURAN +# ========================================== + +FOLDER_DATABASE = "./chroma_db" + +MODEL_EMBEDDING = "paraphrase-multilingual-MiniLM-L12-v2" + + +# ========================================== +# MEMBUAT KONEKSI +# ========================================== + +def hubungkan_database(ukuran): + client = chromadb.PersistentClient( + path=FOLDER_DATABASE + ) + + embedding = ( + embedding_functions.SentenceTransformerEmbeddingFunction( + model_name=MODEL_EMBEDDING + ) + ) + + nama_koleksi = f"dokumen_chunk_{ukuran}" + + koleksi = client.get_collection( + name=nama_koleksi, + embedding_function=embedding + ) + + return koleksi + + +# ========================================== +# MELAKUKAN PENCARIAN +# ========================================== + +def cari_dokumen(): + print("\n===== SISTEM PENCARIAN DOKUMEN RAG =====") + print("Ukuran chunk tersedia: 500, 1000, 1500") + + try: + ukuran = int(input("Pilih ukuran chunk: ")) + + if ukuran not in [500, 1000, 1500]: + print("Ukuran chunk tidak tersedia.") + return + + koleksi = hubungkan_database(ukuran) + + jumlah_data = koleksi.count() + + if jumlah_data == 0: + print("Indeks masih kosong. Jalankan indeks.py dahulu.") + return + + print(f"\nJumlah chunk dalam indeks: {jumlah_data}") + + while True: + pertanyaan = input( + "\nMasukkan pertanyaan (ketik 'keluar' untuk berhenti): " + ).strip() + + if pertanyaan.lower() == "keluar": + break + + if not pertanyaan: + print("Pertanyaan tidak boleh kosong.") + continue + + jumlah_hasil = min(3, jumlah_data) + + hasil = koleksi.query( + query_texts=[pertanyaan], + n_results=jumlah_hasil + ) + + print("\n===== HASIL PENCARIAN =====") + + for nomor, (dokumen, metadata, jarak) in enumerate( + zip( + hasil["documents"][0], + hasil["metadatas"][0], + hasil["distances"][0] + ), + start=1 + ): + print(f"\nHasil ke-{nomor}") + print(f"Sumber : {metadata['sumber']}") + print(f"Halaman: {metadata['halaman']}") + print(f"Chunk : {metadata['chunk']}") + print(f"Jarak : {jarak:.4f}") + print("Isi dokumen:") + print(dokumen) + print("--------------------------------") + + print( + "Catatan: nilai jarak lebih kecil " + "berarti hasil lebih dekat." + ) + + + except ValueError: + print("Masukkan angka ukuran chunk yang benar.") + + except Exception as error: + print("Terjadi kesalahan:", error) + print("Pastikan indeks sudah dibuat dengan indeks.py.") + + +# ========================================== +# PROGRAM UTAMA +# ========================================== + +if __name__ == "__main__": + cari_dokumen() diff --git a/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/data_level0.bin b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/data_level0.bin new file mode 100644 index 0000000..5efb1b9 Binary files /dev/null and b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/data_level0.bin differ diff --git a/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/header.bin b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/header.bin differ diff --git a/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/length.bin b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/length.bin new file mode 100644 index 0000000..84b31d0 Binary files /dev/null and b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/length.bin differ diff --git a/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/link_lists.bin b/chroma_db/11a5b9ab-a57a-47ce-b5f6-d0522b167fbe/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/data_level0.bin b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/data_level0.bin new file mode 100644 index 0000000..5efb1b9 Binary files /dev/null and b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/data_level0.bin differ diff --git a/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/header.bin b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/header.bin differ diff --git a/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/length.bin b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/length.bin new file mode 100644 index 0000000..e336d22 Binary files /dev/null and b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/length.bin differ diff --git a/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/link_lists.bin b/chroma_db/7b627a55-a76e-4ae6-a082-f2c2d6fec532/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/data_level0.bin b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/data_level0.bin new file mode 100644 index 0000000..5efb1b9 Binary files /dev/null and b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/data_level0.bin differ diff --git a/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/header.bin b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/header.bin new file mode 100644 index 0000000..bb54792 Binary files /dev/null and b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/header.bin differ diff --git a/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/length.bin b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/length.bin new file mode 100644 index 0000000..87e3533 Binary files /dev/null and b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/length.bin differ diff --git a/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/link_lists.bin b/chroma_db/c8f66ff5-b2ab-4c73-b3ef-9c3612768262/link_lists.bin new file mode 100644 index 0000000..e69de29 diff --git a/chroma_db/chroma.sqlite3 b/chroma_db/chroma.sqlite3 new file mode 100644 index 0000000..66c3ac9 Binary files /dev/null and b/chroma_db/chroma.sqlite3 differ diff --git a/dokumen/dokumen1.pdf b/dokumen/dokumen1.pdf new file mode 100644 index 0000000..9c152ce Binary files /dev/null and b/dokumen/dokumen1.pdf differ diff --git a/dokumen/dokumen2.pdf b/dokumen/dokumen2.pdf new file mode 100644 index 0000000..b6b23c1 Binary files /dev/null and b/dokumen/dokumen2.pdf differ diff --git a/dokumen/dokumen3.pdf b/dokumen/dokumen3.pdf new file mode 100644 index 0000000..4039296 Binary files /dev/null and b/dokumen/dokumen3.pdf differ diff --git a/evaluasi_retrieval.py b/evaluasi_retrieval.py new file mode 100644 index 0000000..0ac5ea7 --- /dev/null +++ b/evaluasi_retrieval.py @@ -0,0 +1,147 @@ + +import chromadb +from chromadb.utils import embedding_functions + + +# ========================================== +# PENGATURAN +# ========================================== + +FOLDER_DATABASE = "./chroma_db" + +MODEL_EMBEDDING = "paraphrase-multilingual-MiniLM-L12-v2" + + +# ========================================== +# 15 PERTANYAAN PENGUJIAN +# GANTI PERTANYAAN DAN KATA KUNCI +# SESUAI ISI PDF YANG KAMU GUNAKAN. +# ========================================== + +UJI = [ + ("Pertanyaan 1 dari dokumen", "kata kunci jawaban 1"), + ("Pertanyaan 2 dari dokumen", "kata kunci jawaban 2"), + ("Pertanyaan 3 dari dokumen", "kata kunci jawaban 3"), + ("Pertanyaan 4 dari dokumen", "kata kunci jawaban 4"), + ("Pertanyaan 5 dari dokumen", "kata kunci jawaban 5"), + ("Pertanyaan 6 dari dokumen", "kata kunci jawaban 6"), + ("Pertanyaan 7 dari dokumen", "kata kunci jawaban 7"), + ("Pertanyaan 8 dari dokumen", "kata kunci jawaban 8"), + ("Pertanyaan 9 dari dokumen", "kata kunci jawaban 9"), + ("Pertanyaan 10 dari dokumen", "kata kunci jawaban 10"), + ("Pertanyaan 11 dari dokumen", "kata kunci jawaban 11"), + ("Pertanyaan 12 dari dokumen", "kata kunci jawaban 12"), + ("Pertanyaan 13 dari dokumen", "kata kunci jawaban 13"), + ("Pertanyaan 14 dari dokumen", "kata kunci jawaban 14"), + ("Pertanyaan 15 dari dokumen", "kata kunci jawaban 15"), +] + + +# ========================================== +# MENGHUBUNGKAN KE CHROMADB +# ========================================== + +def hubungkan_database(ukuran): + client = chromadb.PersistentClient( + path=FOLDER_DATABASE + ) + + embedding = ( + embedding_functions.SentenceTransformerEmbeddingFunction( + model_name=MODEL_EMBEDDING + ) + ) + + nama_koleksi = f"dokumen_chunk_{ukuran}" + + koleksi = client.get_collection( + name=nama_koleksi, + embedding_function=embedding + ) + + return koleksi + + +# ========================================== +# MENGHITUNG HIT@K DAN MRR +# ========================================== + +def evaluasi(koleksi, k): + benar = 0 + total_reciprocal_rank = 0 + + jumlah_data = koleksi.count() + + if jumlah_data == 0: + return 0, 0 + + jumlah_hasil = min(k, jumlah_data) + + for pertanyaan, kata_kunci in UJI: + + hasil = koleksi.query( + query_texts=[pertanyaan], + n_results=jumlah_hasil + ) + + daftar_dokumen = hasil["documents"][0] + + peringkat_benar = None + + for nomor, dokumen in enumerate(daftar_dokumen, start=1): + if kata_kunci.lower() in dokumen.lower(): + peringkat_benar = nomor + break + + if peringkat_benar is not None: + benar += 1 + total_reciprocal_rank += 1 / peringkat_benar + + hit = benar / len(UJI) + mrr = total_reciprocal_rank / len(UJI) + + return hit, mrr + + +# ========================================== +# PROGRAM UTAMA EVALUASI +# ========================================== + +def jalankan_evaluasi(): + print("\n===== EVALUASI RETRIEVAL RAG =====") + + print("Jumlah pertanyaan:", len(UJI)) + + print("\nHasil perbandingan ukuran chunk:") + print("-----------------------------------------------") + print("Ukuran | Hit@1 | Hit@3 | Hit@5 | MRR") + print("-----------------------------------------------") + + for ukuran in [500, 1000, 1500]: + + try: + koleksi = hubungkan_database(ukuran) + + hasil_1, mrr_1 = evaluasi(koleksi, 1) + hasil_3, mrr_3 = evaluasi(koleksi, 3) + hasil_5, mrr_5 = evaluasi(koleksi, 5) + + # MRR dihitung menggunakan hasil top-5. + print( + f"{ukuran:6} | " + f"{hasil_1:.2f} | " + f"{hasil_3:.2f} | " + f"{hasil_5:.2f} | " + f"{mrr_5:.2f}" + ) + + except Exception as error: + print(f"Chunk {ukuran}: gagal dievaluasi.") + print("Keterangan:", error) + + print("-----------------------------------------------") + print("Evaluasi selesai.") + + +if __name__ == "__main__": + jalankan_evaluasi() diff --git a/indeks.py b/indeks.py new file mode 100644 index 0000000..d6870b2 --- /dev/null +++ b/indeks.py @@ -0,0 +1,174 @@ + +from pathlib import Path +from pypdf import PdfReader +import chromadb +from chromadb.utils import embedding_functions + + +# ========================================== +# PENGATURAN +# ========================================== + +FOLDER_DOKUMEN = Path("dokumen") +FOLDER_DATABASE = "./chroma_db" + +UKURAN_CHUNK = [500, 1000, 1500] +MODEL_EMBEDDING = "paraphrase-multilingual-MiniLM-L12-v2" + + +# ========================================== +# MEMBACA FILE PDF +# ========================================== + +def baca_pdf(path): + reader = PdfReader(path) + daftar_halaman = [] + + for nomor, halaman in enumerate(reader.pages, start=1): + teks = halaman.extract_text() or "" + + if teks.strip(): + daftar_halaman.append({ + "teks": teks, + "halaman": nomor + }) + + return daftar_halaman + + +# ========================================== +# MEMOTONG TEKS MENJADI CHUNK +# ========================================== + +def potong(teks, ukuran=500, tumpang=50): + hasil = [] + mulai = 0 + + while mulai < len(teks): + bagian = teks[mulai:mulai + ukuran] + + if bagian.strip(): + hasil.append(bagian) + + mulai += ukuran - tumpang + + return hasil + + +# ========================================== +# MEMBUAT INDEKS CHROMADB +# ========================================== + +def buat_indeks(): + if not FOLDER_DOKUMEN.exists(): + print("Folder dokumen tidak ditemukan.") + return + + daftar_pdf = list(FOLDER_DOKUMEN.glob("*.pdf")) + + if not daftar_pdf: + print("Belum ada file PDF di folder dokumen.") + return + + print("Menghubungkan ke ChromaDB...") + + client = chromadb.PersistentClient( + path=FOLDER_DATABASE + ) + + embedding = ( + embedding_functions.SentenceTransformerEmbeddingFunction( + model_name=MODEL_EMBEDDING + ) + ) + + print("Model embedding siap digunakan.") + + # Membuat koleksi untuk setiap ukuran chunk. + for ukuran in UKURAN_CHUNK: + + nama_koleksi = f"dokumen_chunk_{ukuran}" + + koleksi = client.get_or_create_collection( + name=nama_koleksi, + embedding_function=embedding, + metadata={"hnsw:space": "cosine"} + ) + + print("\n================================") + print(f"MEMBUAT INDEKS CHUNK {ukuran}") + print("================================") + + total_chunk = 0 + + for pdf in daftar_pdf: + halaman_pdf = baca_pdf(pdf) + + ids = [] + documents = [] + metadatas = [] + + for data in halaman_pdf: + teks = data["teks"] + + overlap = int(ukuran * 0.10) + + chunks = potong( + teks, + ukuran=ukuran, + tumpang=overlap + ) + + for nomor, chunk in enumerate(chunks): + id_chunk = ( + f"{pdf.stem}_halaman_" + f"{data['halaman']}_chunk_" + f"{nomor}_ukuran_{ukuran}" + ) + + ids.append(id_chunk) + documents.append(chunk) + + metadatas.append({ + "sumber": pdf.name, + "halaman": data["halaman"], + "chunk": nomor, + "ukuran_chunk": ukuran + }) + + if ids: + # Simpan dalam kelompok agar lebih aman + # untuk dokumen dengan banyak chunk. + for awal in range(0, len(ids), 100): + akhir = awal + 100 + + koleksi.upsert( + ids=ids[awal:akhir], + documents=documents[awal:akhir], + metadatas=metadatas[awal:akhir] + ) + + print( + f"{pdf.name}: {len(ids)} chunk " + f"untuk ukuran {ukuran}" + ) + + total_chunk += len(ids) + + else: + print( + f"{pdf.name}: tidak ada teks yang terbaca." + ) + + print(f"Total chunk: {total_chunk}") + + print("\nSemua indeks berhasil dibuat!") + print(f"Lokasi database: {FOLDER_DATABASE}") + + +# ========================================== +# PROGRAM UTAMA +# ========================================== + +if __name__ == "__main__": + buat_indeks()