commit 0d282059304ea2a61cdc0c1cea7dc493c85b089e Author: Muhammad.haekal Rahmatullah Date: Mon Nov 2 01:41:24 2026 -0500 Upload files to "/" diff --git a/Indeks_Gapeka.py b/Indeks_Gapeka.py new file mode 100644 index 0000000..b93dad8 --- /dev/null +++ b/Indeks_Gapeka.py @@ -0,0 +1,128 @@ +import random +from pathlib import Path + +# FUNGSI MEMBACA PDF +def baca_pdf(path): + reader = PdfReader(path) + return "\n".join( + (p.extract_text() or "") + for p in reader.pages + ) + +# FUNGSI MEMOTONG TEKS (CHUNKING) +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()] + +# MENCARI PDF DI FOLDER DOKUMEN +daftar_pdf = list(Path("dokumen").glob("*.pdf")) + +if not daftar_pdf: + print("❌ Tidak ada file PDF di folder 'dokumen'! Pastikan Anda membuat folder bernama 'dokumen' dan memasukkan file PDF terkait KAI di dalamnya.") +else: + # Mengambil PDF pertama + pdf = daftar_pdf[0] + + # Membaca isi PDF + teks = baca_pdf(pdf) + + print(f"\n📄 Nama PDF: {pdf.name}") + print(f"📊 Total karakter: {len(teks)}") + + # Menampilkan 500 karakter pertama + print("\n===== 500 KARAKTER PERTAMA =====") + print(teks[:500]) + + # Eksperimen tiga ukuran chunk + for ukuran in (500, 1000, 1500): + overlap = int(ukuran * 0.1) + chunks = potong( + teks, + ukuran=ukuran, + tumpang=overlap + ) + + print("\n" + "=" * 50) + print(f"UKURAN CHUNK: {ukuran}") + print(f"OVERLAP: {overlap}") + print(f"JUMLAH CHUNK: {len(chunks)}") + + # Mengambil maksimal 3 chunk secara acak + sampel = random.sample( + range(len(chunks)), + min(3, len(chunks)) + ) + + for nomor in sampel: + print(f"\n--- CHUNK {nomor} ---") + print(chunks[nomor]) + + +# LAB 2: EMBEDDING DAN CHROMADB +if daftar_pdf: + # Model embedding multilingual + emb = embedding_functions.SentenceTransformerEmbeddingFunction( + model_name="paraphrase-multilingual-MiniLM-L12-v2" + ) + + # Membuat database lokal + client = chromadb.PersistentClient(path="./chroma_db") + + def bangun_indeks(nama_koleksi, ukuran): + # Menghapus koleksi lama agar tidak tercampur + try: + client.delete_collection(name=nama_koleksi) + except Exception: + pass + + # Membuat koleksi baru + koleksi = client.create_collection( + name=nama_koleksi, + embedding_function=emb, + metadata={"hnsw:space": "cosine"} + ) + + for pdf in daftar_pdf: + teks = baca_pdf(pdf) + overlap = int(ukuran * 0.1) + chunks = potong( + teks, + ukuran=ukuran, + tumpang=overlap + ) + + if not chunks: + print(pdf.name, "tidak memiliki teks.") + continue + + koleksi.upsert( + ids=[ + f"{pdf.stem}-{ukuran}-{i}" + for i in range(len(chunks)) + ], + documents=chunks, + metadatas=[ + { + "sumber": pdf.name, + "chunk": i, + "ukuran": ukuran + } + for i in range(len(chunks)) + ] + ) + + print(pdf.name, "->", len(chunks), "chunk") + + print(f"\n✅ Koleksi '{nama_koleksi}' berhasil dibuat.") + print("Total data dalam koleksi:", koleksi.count()) + + # Membuat dua koleksi untuk eksperimen + bangun_indeks("pedoman_500", 500) + bangun_indeks("pedoman_1000", 1000) + +else: + print("⚠️ Indeks gagal dibuat karena tidak ada file PDF.") \ No newline at end of file diff --git a/LAPORAN GAPEKA 2026.docx b/LAPORAN GAPEKA 2026.docx new file mode 100644 index 0000000..b97be7e Binary files /dev/null and b/LAPORAN GAPEKA 2026.docx differ diff --git a/cari_gapeka.py b/cari_gapeka.py new file mode 100644 index 0000000..1d9e791 --- /dev/null +++ b/cari_gapeka.py @@ -0,0 +1,146 @@ +import math +import re +from collections import Counter + + +class KAIGapekaRAG: + """Sistem RAG Sederhana berbasis Pure Python untuk topik GAPEKA 2026 PT KAI.""" + + def __init__(self, knowledge_base): + print("[INFO] Menginisialisasi Basis Pengetahuan KAI Gapeka 2026...") + self.documents = knowledge_base + self.vocab = self._build_vocab(self.documents) + # Pra-penghitungan vektor TF-IDF untuk dokumen lokal + self.doc_vectors = [ + self._compute_tfidf(doc, self.vocab, self.documents) + for doc in self.documents + ] + + def _tokenize(self, text): + """Membersihkan teks dan memecahnya menjadi kata-kata (token).""" + return re.findall(r"\b\w+\b", text.lower()) + + def _build_vocab(self, docs): + """Membangun kamus kata unik dari seluruh dokumen.""" + vocab = set() + for doc in docs: + vocab.update(self._tokenize(doc)) + return sorted(list(vocab)) + + def _compute_tfidf(self, text, vocab, all_docs): + """Menghitung bobot TF-IDF secara manual tanpa library eksternal.""" + tokens = self._tokenize(text) + tf = Counter(tokens) + total_tokens = len(tokens) if tokens else 1 + + vector = {} + for word in vocab: + # Term Frequency (TF) + term_freq = tf[word] / total_tokens + + # Inverse Document Frequency (IDF) + doc_count = sum(1 for d in all_docs if word in self._tokenize(d)) + idf = ( + math.log(len(all_docs) / (doc_count + 1)) + 1 if doc_count > 0 else 0 + ) + + vector[word] = term_freq * idf + return vector + + def _cosine_similarity(self, vec1, vec2): + """Menghitung kemiripan kosinus antara dua vektor TF-IDF.""" + intersection = set(vec1.keys()).intersection(set(vec2.keys())) + numerator = sum(vec1[x] * vec2[x] for x in intersection) + + sum1 = sum(val ** 2 for val in vec1.values()) + sum2 = sum(val ** 2 for val in vec2.values()) + denominator = math.sqrt(sum1) * math.sqrt(sum2) + + if denominator == 0: + return 0.0 + return numerator / denominator + + def retrieve(self, query, top_k=1): + """Mencari dokumen paling relevan menggunakan Cosine Similarity.""" + query_vector = self._compute_tfidf(query, self.vocab, self.documents) + scores = [] + + for i, doc_vec in enumerate(self.doc_vectors): + score = self._cosine_similarity(query_vector, doc_vec) + scores.append((score, self.documents[i])) + + # Urutkan berdasarkan skor tertinggi + scores.sort(key=lambda x: x[0], reverse=True) + return [doc for score, doc in scores[:top_k]] + + def generate_answer(self, query): + """Tahap Generation: Menggabungkan hasil retrieval dengan format jawaban RAG.""" + retrieved_docs = self.retrieve(query, top_k=1) + context = ( + retrieved_docs[0] + if retrieved_docs + else "Maaf, informasi tidak ditemukan." + ) + + # Template Generator Extractive RAG + answer = ( + f"==================================================\n" + f"HASIL GENERASI RAG - PT KERETA API INDONESIA (GAPEKA 2026)\n" + f"==================================================\n" + f"Pertanyaan : {query}\n\n" + f"Konteks Relevan (Retrieved):\n-> {context}\n\n" + f"Jawaban Sistem:\n" + f"Berdasarkan informasi resmi kebijakan GAPEKA 2026 PT KAI, " + f"berikut adalah detail informasinya:\n" + f'"{context}"\n' + f"==================================================" + ) + return answer, retrieved_docs + + +# ========================================== +# EKSEKUSI PROGRAM UTAMA +# ========================================== +if __name__ == "__main__": + # 1. Dataset / Basis Pengetahuan Lokal KAI GAPEKA 2026 + kai_kb = [ + ( + "PT Kereta Api Indonesia (KAI) resmi memberlakukan Grafik Perjalanan " + "Kereta Api (GAPEKA) 2026 mulai tanggal 1 Desember 2026." + ), + ( + "Tiket perjalanan kereta api untuk jadwal GAPEKA 2026 sudah dapat " + "dipesan secara online melalui aplikasi Access by KAI." + ), + ( + "Terdapat penyesuaian serta penyederhanaan nama pada beberapa " + "perjalanan kereta api agar lebih simpel dan mudah diingat oleh " + "penumpang." + ), + ( + "KAI menambah titik pemberhentian di sejumlah stasiun untuk " + "mendukung mobilitas antarkota dan kawasan aglomerasi." + ), + ( + "KAI menghadirkan layanan rute baru seperti KA Borobudur " + "(Lempuyangan–Gambir PP, fakultatif) dan KA Pandawa " + "(Yogyakarta–Semarang Tawang PP) yang mulai beroperasi pada 1 " + "Desember 2026." + ), + ] + + # 2. Inisialisasi Sistem RAG + rag = KAIGapekaRAG(kai_kb) + + # 3. Contoh Daftar Pengujian Pertanyaan Tugas RAG + daftar_pertanyaan = [ + "Kapan GAPEKA 2026 mulai diberlakukan dan di mana bisa pesan tiketnya?", + "Apa saja rute kereta api baru yang dihadirkan KAI dalam GAPEKA 2026?", + "Bagaimana kebijakan terkait nama kereta api dalam GAPEKA 2026?" + ] + + # 4. Menjalankan Pipeline Pengujian + for idx, sample_query in enumerate(daftar_pertanyaan, 1): + print(f"\n[PENGUJIAN {idx}]") + final_answer, sources = rag.generate_answer(sample_query) + print(final_answer) \ No newline at end of file diff --git a/evaluasi_gapeka.py b/evaluasi_gapeka.py new file mode 100644 index 0000000..354dfc1 --- /dev/null +++ b/evaluasi_gapeka.py @@ -0,0 +1,206 @@ +import math +import re +from collections import Counter + + +class KAIGapekaRAG: + """Sistem RAG Sederhana berbasis Pure Python untuk topik GAPEKA 2026 PT KAI.""" + + def __init__(self, knowledge_base): + print("[INFO] Menginisialisasi Basis Pengetahuan KAI Gapeka 2026...") + self.documents = knowledge_base + self.vocab = self._build_vocab(self.documents) + # Pra-penghitungan vektor TF-IDF untuk dokumen lokal + self.doc_vectors = [ + self._compute_tfidf(doc, self.vocab, self.documents) + for doc in self.documents + ] + + def _tokenize(self, text): + """Membersihkan teks dan memecahnya menjadi kata-kata (token).""" + return re.findall(r"\b\w+\b", text.lower()) + + def _build_vocab(self, docs): + """Membangun kamus kata unik dari seluruh dokumen.""" + vocab = set() + for doc in docs: + vocab.update(self._tokenize(doc)) + return sorted(list(vocab)) + + def _compute_tfidf(self, text, vocab, all_docs): + """Menghitung bobot TF-IDF secara manual tanpa library eksternal.""" + tokens = self._tokenize(text) + tf = Counter(tokens) + total_tokens = len(tokens) if tokens else 1 + + vector = {} + for word in vocab: + # Term Frequency (TF) + term_freq = tf[word] / total_tokens + + # Inverse Document Frequency (IDF) + doc_count = sum(1 for d in all_docs if word in self._tokenize(d)) + idf = ( + math.log(len(all_docs) / (doc_count + 1)) + 1 if doc_count > 0 else 0 + ) + + vector[word] = term_freq * idf + return vector + + def _cosine_similarity(self, vec1, vec2): + """Menghitung kemiripan kosinus antara dua vektor TF-IDF.""" + intersection = set(vec1.keys()).intersection(set(vec2.keys())) + numerator = sum(vec1[x] * vec2[x] for x in intersection) + + sum1 = sum(val ** 2 for val in vec1.values()) + sum2 = sum(val ** 2 for val in vec2.values()) + denominator = math.sqrt(sum1) * math.sqrt(sum2) + + if denominator == 0: + return 0.0 + return numerator / denominator + + def retrieve_with_scores(self, query, top_k=5): + """Mengembalikan daftar dokumen dan skornya berdasarkan Cosine Similarity.""" + query_vector = self._compute_tfidf(query, self.vocab, self.documents) + scores = [] + + for i, doc_vec in enumerate(self.doc_vectors): + score = self._cosine_similarity(query_vector, doc_vec) + scores.append((score, self.documents[i])) + + # Urutkan berdasarkan skor tertinggi + scores.sort(key=lambda x: x[0], reverse=True) + return scores[:top_k] + + def generate_answer(self, query): + """Tahap Generation: Menggabungkan hasil retrieval dengan format jawaban RAG.""" + retrieved_scores = self.retrieve_with_scores(query, top_k=1) + context = ( + retrieved_scores[0][1] + if retrieved_scores and retrieved_scores[0][0] > 0 + else "Maaf, informasi tidak ditemukan." + ) + + answer = ( + f"==================================================\n" + f"HASIL GENERASI RAG - PT KERETA API INDONESIA (GAPEKA 2026)\n" + f"==================================================\n" + f"Pertanyaan : {query}\n\n" + f"Konteks Relevan (Retrieved):\n-> {context}\n\n" + f"Jawaban Sistem:\n" + f"Berdasarkan informasi resmi kebijakan GAPEKA 2026 PT KAI, " + f"berikut adalah detail informasinya:\n" + f'"{context}"\n' + f"==================================================" + ) + return answer, context + + +# ========================================== +# MODUL EVALUASI RETRIEVAL (Hit@K & MRR) +# ========================================== + +# Daftar Uji (Pertanyaan dan kata kunci yang harus ada di dokumen yang diretrubusi) +UJI = [ + ("Kapan GAPEKA 2026 mulai diberlakukan?", "1 desember 2026"), + ("Di mana bisa pesan tiket kereta GAPEKA 2026?", "access by kai"), + ("Apa ada perubahan nama kereta api?", "penyederhanaan nama"), + ("Apakah ada penambahan titik pemberhentian stasiun?", "titik pemberhentian"), + ("Sebutkan rute kereta api baru yang beroperasi!", "ka borobudur"), +] + + +def cari_peringkat(rag_system, pertanyaan, kunci, k=5): + """Mencari peringkat dokumen pertama yang mengandung kata kunci.""" + hasil = rag_system.retrieve_with_scores(pertanyaan, top_k=k) + kunci_bersih = " ".join(kunci.lower().split()) + + for peringkat, (score, doc) in enumerate(hasil, start=1): + doc_bersih = " ".join(doc.lower().split()) + if kunci_bersih in doc_bersih and score > 0: + return peringkat + return None + + +def hit_at_k(rag_system, uji_data, k=3): + """Menghitung metrik Hit@K.""" + benar = 0 + for pertanyaan, kunci in uji_data: + peringkat = cari_peringkat(rag_system, pertanyaan, kunci, k) + if peringkat is not None: + benar += 1 + return benar / len(uji_data) + + +def hitung_mrr(rag_system, uji_data): + """Menghitung metrik Mean Reciprocal Rank (MRR@5).""" + total = 0 + for pertanyaan, kunci in uji_data: + peringkat = cari_peringkat(rag_system, pertanyaan, kunci, 5) + if peringkat is not None: + total += 1 / peringkat + return total / len(uji_data) + + +# ========================================== +# EKSEKUSI PROGRAM UTAMA +# ========================================== +if __name__ == "__main__": + # 1. Dataset / Basis Pengetahuan Lokal KAI GAPEKA 2026 + kai_kb = [ + ( + "PT Kereta Api Indonesia (KAI) resmi memberlakukan Grafik Perjalanan " + "Kereta Api (GAPEKA) 2026 mulai tanggal 1 Desember 2026." + ), + ( + "Tiket perjalanan kereta api untuk jadwal GAPEKA 2026 sudah dapat " + "dipesan secara online melalui aplikasi Access by KAI." + ), + ( + "Terdapat penyesuaian serta penyederhanaan nama pada beberapa " + "perjalanan kereta api agar lebih simpel dan mudah diingat oleh penumpang." + ), + ( + "KAI menambah titik pemberhentian di sejumlah stasiun untuk " + "mendukung mobilitas antarkota dan kawasan aglomerasi." + ), + ( + "KAI menghadirkan layanan rute baru seperti KA Borobudur " + "(Lempuyangan–Gambir PP, fakultatif) dan KA Pandawa " + "(Yogyakarta–Semarang Tawang PP) yang mulai beroperasi pada 1 Desember 2026." + ), + ] + + # 2. Inisialisasi Sistem RAG + rag = KAIGapekaRAG(kai_kb) + + # 3. Contoh Uji Jalankan Pipeline RAG Tunggal + sample_query = "Kapan GAPEKA 2026 mulai diberlakukan dan di mana bisa pesan tiketnya?" + final_answer, sources = rag.generate_answer(sample_query) + print("\n" + final_answer) + + # 4. Evaluasi Performa Retrieval (Hit@K & MRR) + print("\n" + "=" * 50) + print("EVALUASI PERFORMA RETRIEVAL KAI GAPEKA 2026") + print("=" * 50) + print(f"Jumlah Pertanyaan Uji : {len(UJI)}") + + for k in (1, 3, 5): + nilai_hit = hit_at_k(rag, UJI, k) + print(f"Hit@{k} = {nilai_hit:.2f}") + + mrr_score = hitung_mrr(rag, UJI) + print(f"MRR@5 = {mrr_score:.2f}") + + print("\nPERTANYAAN YANG GAGAL DITEMUKAN (Jika ada):") + gagal = False + for pertanyaan, kunci in UJI: + peringkat = cari_peringkat(rag, pertanyaan, kunci, 5) + if peringkat is None: + gagal = True + print(f"- {pertanyaan}") + + if not gagal: + print("Semua pertanyaan berhasil ditemukan oleh sistem retrieval!") + print("=" * 50) \ No newline at end of file