initial commit

This commit is contained in:
matthewflrnt
2026-11-02 11:32:33 +07:00
commit 4f2a26e696
21 changed files with 254 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
from main import koleksi_kecil, koleksi_besar
# Lab 3 — 15 pasangan (pertanyaan, kata/frasa kunci yang HARUS ada di chunk jawaban yang benar)
UJI = [
("Apa syarat cuti akademik?", "cuti akademik"),
("Berapa batas kehadiran untuk ujian?", "75 persen"),
("Berapa maksimal cuti akademik selama masa studi?", "2 semester"),
("Kapan batas pengajuan cuti akademik dilakukan?", "2 minggu"),
("Berapa SKS maksimal jika IP semester sebelumnya 3.20?", "24 SKS"),
("Berapa SKS minimal untuk kelulusan program sarjana?", "144 SKS"),
("Apa syarat IPK minimal untuk lulus program sarjana?", "2.00"),
("Berapa lama masa studi maksimal program sarjana?", "14 semester"),
("Berapa SKS minimal yang harus ditempuh untuk mengambil skripsi?", "110 SKS"),
("Berapa syarat nilai minimal mata kuliah Metodologi Penelitian?", "nilai C"),
("Berapa kali bimbingan minimal sebelum sidang skripsi?", "8 kali"),
("Apa sanksi bagi mahasiswa yang melakukan kecurangan ujian?", "nilai E"),
("Berapa lama waktu ujian susulan dapat diajukan setelah jadwal?", "3 hari"),
("Berapa nilai minimal untuk mendapatkan predikat Cum Laude?", "3.51"),
("Berapa persentase maksimal kemiripan turnitin untuk naskah skripsi?", "20 persen"),
]
def hit_at_k(uji, k=3, target_koleksi=koleksi_kecil):
if not uji:
return 0.0
benar = 0
for pertanyaan, kunci in uji:
docs = target_koleksi.query(query_texts=[pertanyaan], n_results=k)["documents"][0]
if any(kunci.lower() in d.lower() for d in docs):
benar += 1
return benar / len(uji)
def tampilkan_detail_gagal(uji, k, target_koleksi, nama_koleksi):
gagal = []
for pertanyaan, kunci in uji:
docs = target_koleksi.query(query_texts=[pertanyaan], n_results=k)["documents"][0]
if not any(kunci.lower() in d.lower() for d in docs):
gagal.append((pertanyaan, kunci))
if gagal:
print(f"\nPertanyaan yang gagal pada {nama_koleksi} (Hit@{k}):")
for p, kw in gagal:
print(f" - {p} (kunci: '{kw}')")
else:
print(f"\nSemua pertanyaan berhasil ditemukan pada {nama_koleksi} untuk Hit@{k}!")
KESIMPULAN = """
KESIMPULAN EVALUASI (LAB 3):
1. Ukuran chunk kecil (500 karakter) umumnya menghasilkan Hit@1 yang lebih presisi untuk pertanyaan spesifik (seperti angka SKS atau batas persentase), karena vektor embedding tidak terdilusi oleh topik lain di dalam satu chunk yang terlalu panjang.
2. Sebaliknya, ukuran chunk besar (1500 karakter) unggul pada Hit@3 dan Hit@5 ketika kata kunci jawaban berada agak jauh dari kalimat topik utamanya, namun rentan menurunkan akurasi Hit@1 karena satu chunk menggabungkan beberapa sub-pasal sekaligus.
3. Kegagalan retrieval biasanya terjadi pada pertanyaan yang jawabannya terpotong tepat di batas chunk (boundary split) atau pertanyaan yang menggunakan angka/istilah singkat yang mirip antar-pasal (misalnya angka semester atau SKS).
4. Solusi untuk meningkatkan performa di proyek RAG adalah menggunakan chunking berbasis struktur kalimat/paragraf dengan ukuran moderat (500–800 karakter) dan overlap 10–20% serta mengambil k=3 hingga k=5 chunk teratas.
""".strip()
if __name__ == "__main__":
if koleksi_kecil.count() == 0 or koleksi_besar.count() == 0:
print("Koleksi evaluasi masih kosong. Jalankan `python main.py` terlebih dahulu setelah mengisi folder `dokumen/`.")
else:
print("=" * 70)
print("LAB 3 — EVALUASI HIT@K PADA DUA UKURAN CHUNK BERBEDA")
print("=" * 70)
print(f"Jumlah pertanyaan uji: {len(UJI)}\n")
print(f"{'Metrik':<10} | {'Chunk 500 (Overlap 75)':<25} | {'Chunk 1500 (Overlap 225)':<25}")
print("-" * 66)
for k in (1, 3, 5):
skor_500 = hit_at_k(UJI, k=k, target_koleksi=koleksi_kecil)
skor_1500 = hit_at_k(UJI, k=k, target_koleksi=koleksi_besar)
print(f"Hit@{k:<6} | {skor_500:<25.2f} | {skor_1500:<25.2f}")
tampilkan_detail_gagal(UJI, k=1, target_koleksi=koleksi_kecil, nama_koleksi="Chunk 500")
tampilkan_detail_gagal(UJI, k=1, target_koleksi=koleksi_besar, nama_koleksi="Chunk 1500")
print("\n" + "=" * 70)
print(KESIMPULAN)
print("=" * 70)