This commit is contained in:
farrel
2026-11-02 11:26:54 +07:00
commit cbc21d9155
41 changed files with 651 additions and 0 deletions
Binary file not shown.
Binary file not shown.
+99
View File
@@ -0,0 +1,99 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/PageMode /UseNone /Pages 9 0 R /Type /Catalog
>>
endobj
8 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20261102034842+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102034842+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
9 0 obj
<<
/Count 2 /Kids [ 5 0 R 6 0 R ] /Type /Pages
>>
endobj
10 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1516
>>
stream
Gatm;gMZ%0&:Ml+%/cp.%Th.c"^3%,1qO:1Bptt($M]=\BSV^<\WhaiJ);Cq>OjAnU4E?uiaq8rR574!JrLjeT$%c)ErqtO.8U84'i9s9OF2]<PDaoD8EANF"=/j+-hMu.1^q#.*kklMn,j<A:/&KF8aM0iKbAbl,(AU+_cQr#(VTc<Ru^.f>:4=T:d5*Bq-Il!5^^m;;es'tp)&![>Y8Zt.3p3Ahc[j^J<Ul:R40PPb_-UqO?>o$PI<V0i5IE:oAQ9lYH2-A$g;9MT,@$?;rI:S6f6H7Gf\@%"cfonJ:-%T:#c3Z53ri;F7h%HLrkB:U\JrsJ7CQ_,0`[nOXL^7#j7B[XS_=qS%l,pi;TA0lQ:=iF8^@^Y_@G3^Xf&B#qq,50g$6i%q:4B_t-4@5=Xg[kYMl";1rl7Kd6r`*P)J!f/]mo_i8,N,ALn'e)jKtP!-'J5[MR$Dd^IU<NVNp4(FX7=VX:<HT'c;!`a2kA5BOj;><oQTm.*cn?sV.!&0@6LLF$]hmm!5aU5P-D4W5fVh*=rm;V.D2UdH<>_hZjMb3*Z'[i7@CsPU[-SLidit5CJ!doS6WN+Vm7ucK81#HaIY=u)B'=i[`1PO^_$e_bsQEL]2<^7m1:pq71b='L=N#[L^)ojDFof4X-\!#k:W(Y`3;c2Q8)M_XD3es`78lgj10Q\aY9Ph]e2M[eU\]RUcdhYI7*%.Q%hS!g_=_q"B</)OaoBM)QQWJsXhl&2_9u-gF`.f5p'4?',"CoaoNOl37W>t#C.fupZY6WjK>eZ)9KC;\P9G%!C;m+c-%G3oZWjZq%89J=R&$'SI5IU0!_89r3?8!qoa6QS>Vq.d0hBY't;(fA"9?DGIWi4S)]/*cOOql]W-MYZ/>4n_??m&;u#7S%p>?97$EB-<&?5T5+M&5NV`'$kP`);1o@<Tg.<liLp1YQZg<oF1@LEWqEEQ#gc1lU[R[_bn$Hid>%r%Jkt=M7rk=]^_!Hoo/IT&*K8aHID*1Q#"Dq$MtqC\(`%YbpKH*lWl*@6Yn(V-!Jtr)Q>P59Wp2S6U89;B5@M[!%%K>VA92945Lf+'l%:rl>u!ACX[7r+<r"S@*haVE=">^GZGbEad5,[7-#0P]rtF6p=g[%Mj/b5A1W"=1#SD:r1^lC[l7^B_5_Zoo*53@\R+uaSd3OG4;<2)(=:;4g3T[:Tl;I+NLck<@@Y.Z<C<)d(24aX2Xs.X4kSK?1]:BF-J,Fcb!#o<aVFdENE3#`?<+DqU2pV,<am4.`Fb\9b=_mSaD8hjIHi>`XE&5dKBo,nV'Ep&1fG`9Qe@p'U^uRj4^5'i3np:>.j<i7aAVi[4PZSG;ZGm@cW6XEidkY<,@OoQJqYqN/ma>$W2h`hq?7ir?scZ@DhS<a78&EmVf]f;VBs=oL2=Qh>p&"+odduD/'a0`/Lmm7W8`V3_mu3i_?I!bjZ]Q22<(I%!qegYM%e`UU*.r&X$QERNdm8#hk8&66ZG]8"D&]gKgqc<k3On>O2A#\G-RE[Dr(~>endstream
endobj
11 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1002
>>
stream
Gau0B95iQ=%)1n+&34;hTN[@_.^2Rn[6&JcXgr:1d#%$9[Ui7#2[%%tq^CQ'[nG*?:]qO:8UPpH7ku%cqJj8&C4N?F>hj;V+j;$Y7kB8f*IOB<IZ@MG#9j89AP&9/^][Gs.#"k6,7SY0&ePH\d>Wob)K;VuSn0Iu_Y!B"K)<qjJ*,5gQL(ubR0\).!fQ&/"<k!/o#=A^"Sg9_K9[<e4Q@FD2,$3/Pt"%l.pSj#)YHuGoa^,"R(mjV#7;Xu(o^<'./8H1Oq`QOR4j5;09h,OX!u`JP(Zc(kW84b:E?6l0KZBHUMsER6jIaQV`G3ne/MDsA1a.Y1tX!c<M>#@ErnD04o@XAOPnKRVQWDr9@`48@W8Fb<](f?AtZ-B%Wt(,ZIb!:n;3A&]%Ia%a,u('JF"\W/W,IUfcJ]c5>:Vh=7?7)NNnc!GR@r<Al55UXek:*nIP)>pSu4JG,kG!,k,HJ.Ic@j(gef\T6IT,*MntF_S7b9SVU\32@Dpg77%ug2'AX5TnnNFcVkNpo+M2_VFUFRU^ZCcmIiY'_g!@8`_R>oEK5^DG2IS6SmYe=*3jL@ER?5PII=S_U>fi\;]+Lq.2T"Q6A8DM<p?>H><:b#F^)tn;\hI._(u@\7+p=I+4tfqpa\d5iNUk<c>eE^8u`Mc`\b[O(=gaJKdN^oUKbEo3TatK&(A!X5HsZmoXDO-i*Caop6,rN-*2s\SW?%hBW=HNof7h\jR!Na0=TPsM!r]j'i[o$8qa&5rQqCTVs[\s<DkF$`_6^6K;C#*qZ=AYTZ%tVX[oBhi"cEc>Y;=hI1?kK/:R2p$uNX</GIm2;)V)3(gdt=@b`F@=AU,DC="e@#*YpYC+7KFSj#'iit%l'fUZY3i"H,Jl'!6dmMr'#bl=<-d2n,DLYtYo5.`GBgYmSf="^]RY3("(a]@-TQL]gak6fsrSl5$Xqk^"pMfl)#6[<e%O;N+,=Z"V=[UM(_Qde8/]fHR$$Io3!5AHgG#=e^n]I6P]~>endstream
endobj
xref
0 12
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000436 00000 n
0000000640 00000 n
0000000844 00000 n
0000000912 00000 n
0000001192 00000 n
0000001257 00000 n
0000002865 00000 n
trailer
<<
/ID
[<8f92dc9b38dcbce41e380e8ab68ab56d><8f92dc9b38dcbce41e380e8ab68ab56d>]
% ReportLab generated PDF document -- digest (opensource)
/Info 8 0 R
/Root 7 0 R
/Size 12
>>
startxref
3959
%%EOF
@@ -0,0 +1 @@
INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ
@@ -0,0 +1 @@
invalid type: string "1. Histogram Data Berat Air Mineral Vit \n \n \nGambar 22. Tabel Data Kelompok Berat Air Mineral Aqua \n \n \nGambar 23. Histogram Data Berat Air Mineral Aqua \n \n\n \nGambar 24. Pengolahan Data Hasil Kuisioner \n \nGambar 25. Tabel Urutan Data Hasil Kuisioner \n \n \n \n \n \n\nLAMPIRAN 2 \n \n \n \n \n\n", expected i64mart \n \n \nadan Mahasiswa UKWMS Angkatan 2025 \n \n
@@ -0,0 +1 @@
INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ
Binary file not shown.
@@ -0,0 +1 @@
INSERT INTO "embedding_metadata" ("id", "key", "string_value", "int_value", "float_value", "bool_value") VALUES (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) ON CONFLICT ("id", "key") DO UPDATE SET "string_value" = "excluded"."string_value", "int_value" = "excluded"."int_value", "float_value" = "excluded"."float_value", "bool_value" = "excluded"."bool_value"ÿÿÿÿÿÿÿ
+548
View File
@@ -0,0 +1,548 @@
import os
from pathlib import Path
import chromadb
from chromadb.utils import embedding_functions
from pypdf import PdfReader
# =====================================================
# KONFIGURASI PROGRAM
# =====================================================
BASE_DIR = Path(__file__).resolve().parent
DB_PATH = str(BASE_DIR / "chroma_db")
UKURAN_CHUNK = [500, 1000, 1500]
OVERLAP_PERSEN = 0.10
MODEL_EMBEDDING = "paraphrase-multilingual-MiniLM-L12-v2"
print("Memuat model embedding...")
embedding = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=MODEL_EMBEDDING
)
client = chromadb.PersistentClient(path=DB_PATH)
# =====================================================
# FUNGSI MEMBACA PDF
# =====================================================
def ambil_file_pdf():
return list(BASE_DIR.glob("*.pdf"))
def baca_pdf(path):
reader = PdfReader(str(path))
teks = ""
for halaman in reader.pages:
teks += (halaman.extract_text() or "") + "\n"
return teks
def pilih_pdf(files):
"""Memilih satu PDF agar eksperimen konsisten."""
if not files:
print("Belum ada file PDF!")
print("Letakkan PDF di folder yang sama dengan file Python ini.")
return None
if len(files) == 1:
return files[0]
print("\nDaftar dokumen PDF:")
for i, file in enumerate(files, 1):
print(f"{i}. {file.name}")
while True:
pilihan = input("Pilih nomor PDF: ").strip()
if pilihan.isdigit() and 1 <= int(pilihan) <= len(files):
return files[int(pilihan) - 1]
print("Pilihan tidak valid.")
# =====================================================
# FUNGSI CHUNKING
# =====================================================
def chunking(teks, ukuran=1000, overlap=100):
if ukuran <= 0 or overlap < 0 or overlap >= ukuran:
raise ValueError("Ukuran atau overlap tidak valid.")
chunks = []
start = 0
while start < len(teks):
akhir = min(start + ukuran, len(teks))
potongan = teks[start:akhir]
if potongan.strip():
chunks.append(potongan)
start += ukuran - overlap
return chunks
# =====================================================
# LAB 1 - EKSPERIMEN CHUNKING
# =====================================================
def lab_chunking():
print("\n" + "=" * 60)
print("LAB 1 - EKSPERIMEN CHUNKING")
print("=" * 60)
file = pilih_pdf(ambil_file_pdf())
if file is None:
return
teks = baca_pdf(file)
print(f"\nDokumen : {file.name}")
print(f"Panjang teks : {len(teks)} karakter")
if not teks.strip():
print("Dokumen tidak memiliki teks yang dapat dibaca.")
return
for ukuran in UKURAN_CHUNK:
for persentase in [0.10, 0.20]:
overlap = int(ukuran * persentase)
chunks = chunking(teks, ukuran, overlap)
print("\n" + "-" * 55)
print(f"Ukuran chunk : {ukuran}")
print(f"Overlap : {overlap} ({persentase:.0%})")
print(f"Jumlah chunk : {len(chunks)}")
print("Tiga contoh chunk:")
# Tampilkan maksimal tiga contoh chunk.
# Pemilihan posisi dibuat tersebar agar tidak selalu awal dokumen.
if len(chunks) <= 3:
indeks = list(range(len(chunks)))
else:
indeks = [0, len(chunks) // 2, len(chunks) - 1]
for nomor, idx in enumerate(indeks, 1):
print(f"\nContoh {nomor} (Chunk {idx + 1}):")
print(chunks[idx][:350])
print("\nEksperimen chunking selesai!")
# =====================================================
# FUNGSI MEMBANGUN KOLEKSI
# =====================================================
def bangun_koleksi(nama_koleksi, ukuran_chunk, file_pdf, reset=True):
"""Membangun koleksi ChromaDB dari satu dokumen PDF."""
teks = baca_pdf(file_pdf)
if not teks.strip():
print("PDF kosong atau tidak dapat dibaca.")
return None
overlap = int(ukuran_chunk * OVERLAP_PERSEN)
chunks = chunking(teks, ukuran_chunk, overlap)
if reset:
try:
client.delete_collection(nama_koleksi)
except Exception:
pass
collection = client.create_collection(
name=nama_koleksi,
embedding_function=embedding,
metadata={"hnsw:space": "cosine"}
)
ids = [f"chunk_{i}" for i in range(len(chunks))]
metadatas = [
{
"sumber": file_pdf.name,
"nomor_chunk": i,
"ukuran_chunk": ukuran_chunk
}
for i in range(len(chunks))
]
batch_size = 100
for start in range(0, len(chunks), batch_size):
end = start + batch_size
collection.upsert(
ids=ids[start:end],
documents=chunks[start:end],
metadatas=metadatas[start:end]
)
return collection
# =====================================================
# LAB 2 - MEMBANGUN INDEKS CHROMADB
# =====================================================
def lab_indexing():
print("\n" + "=" * 60)
print("LAB 2 - INDEXING CHROMADB")
print("=" * 60)
file = pilih_pdf(ambil_file_pdf())
if file is None:
return
print(f"\nMembaca: {file.name}")
collection = bangun_koleksi(
nama_koleksi="rag_part1",
ukuran_chunk=1000,
file_pdf=file,
reset=True
)
if collection is None:
return
print("\nIndexing berhasil!")
print("Ukuran chunk :", 1000)
print("Total chunk :", collection.count())
print("Database :", DB_PATH)
# =====================================================
# MENGAMBIL KOLEKSI CHROMADB
# =====================================================
def ambil_koleksi():
try:
return client.get_collection(
name="rag_part1",
embedding_function=embedding
)
except Exception:
print("\nDatabase belum tersedia!")
print("Silakan jalankan Lab 2 terlebih dahulu.")
return None
# =====================================================
# FUNGSI PENCARIAN DOKUMEN
# =====================================================
def cari_dokumen(pertanyaan, k=3, tampilkan=True, collection=None):
if collection is None:
collection = ambil_koleksi()
if collection is None:
return []
total = collection.count()
if total == 0:
print("Database masih kosong!")
return []
k = min(k, total)
hasil = collection.query(
query_texts=[pertanyaan],
n_results=k
)
dokumen = hasil["documents"][0]
metadata = hasil["metadatas"][0]
jarak = hasil["distances"][0]
if tampilkan:
print("\nPertanyaan:", pertanyaan)
print("\nHasil pencarian:")
for i, doc in enumerate(dokumen):
print("\n" + "-" * 45)
print(f"HASIL KE-{i + 1}")
print("-" * 45)
print("Sumber:", metadata[i].get("sumber", "-"))
print("Chunk:", metadata[i].get("nomor_chunk", "-"))
print("Jarak:", round(jarak[i], 4))
print("Isi:")
print(doc[:500])
return dokumen
# =====================================================
# LAB 2 - PENCARIAN DOKUMEN
# =====================================================
def lab_pencarian():
print("\n" + "=" * 60)
print("LAB 2 - PENCARIAN DOKUMEN")
print("=" * 60)
collection = ambil_koleksi()
if collection is None or collection.count() == 0:
print("Silakan lakukan indexing terlebih dahulu.")
return
print("\nMasukkan pertanyaan untuk mencari informasi.")
print("Ketik '0' untuk kembali ke menu.")
while True:
pertanyaan = input("\nPertanyaan: ").strip()
if pertanyaan == "0":
break
if not pertanyaan:
print("Pertanyaan tidak boleh kosong.")
continue
cari_dokumen(pertanyaan, k=3, collection=collection)
# =====================================================
# LAB 2 - PENGUJIAN PARAFRASE
# =====================================================
def lab_parafrase():
print("\n" + "=" * 60)
print("LAB 2 - PENGUJIAN PARAFRASE")
print("=" * 60)
collection = ambil_koleksi()
if collection is None or collection.count() == 0:
print("Silakan lakukan indexing terlebih dahulu.")
return
pertanyaan_asli = input("\nPertanyaan asli: ").strip()
if not pertanyaan_asli:
print("Pertanyaan tidak boleh kosong.")
return
pertanyaan_parafrase = input("Pertanyaan parafrase: ").strip()
if not pertanyaan_parafrase:
print("Parafrase tidak boleh kosong.")
return
print("\n--- HASIL PERTANYAAN ASLI ---")
cari_dokumen(pertanyaan_asli, k=3, collection=collection)
print("\n--- HASIL PERTANYAAN PARAFRASE ---")
cari_dokumen(pertanyaan_parafrase, k=3, collection=collection)
# =====================================================
# LAB 3 - EVALUASI HIT@K
# =====================================================
def buat_koleksi_evaluasi(ukuran_chunk, file_pdf):
nama_koleksi = f"rag_eval_{ukuran_chunk}"
print(f"\nMembangun koleksi {nama_koleksi}...")
collection = bangun_koleksi(
nama_koleksi=nama_koleksi,
ukuran_chunk=ukuran_chunk,
file_pdf=file_pdf,
reset=True
)
if collection is not None:
print("Indexing selesai.")
print("Jumlah chunk:", collection.count())
return collection
def hitung_hit_at_k(collection, data_uji, k):
jumlah_hit = 0
total = len(data_uji)
for nomor, (pertanyaan, keyword) in enumerate(data_uji, 1):
jumlah_dokumen = min(k, collection.count())
hasil = collection.query(
query_texts=[pertanyaan],
n_results=jumlah_dokumen
)
dokumen = hasil["documents"][0]
# Hit jika minimal satu dari K dokumen teratas
# memuat kata/frasa kunci jawaban.
ditemukan = any(
keyword.casefold() in teks.casefold()
for teks in dokumen
)
if ditemukan:
jumlah_hit += 1
status = "HIT" if ditemukan else "MISS"
print(f"{nomor:02d}. {status} | {pertanyaan}")
skor = jumlah_hit / total if total else 0
return jumlah_hit, total, skor
def lab_evaluasi():
print("\n" + "=" * 60)
print("LAB 3 - EVALUASI HIT@K")
print("=" * 60)
print("""
Evaluasi menggunakan:
- 15 pertanyaan uji
- Dua koleksi: chunk 500 dan 1000 karakter
- Overlap 10%
- Hit@1, Hit@3, dan Hit@5
Masukkan kata/frasa yang benar-benar terdapat
pada bagian dokumen yang menjawab pertanyaan.
""")
file_pdf = pilih_pdf(ambil_file_pdf())
if file_pdf is None:
return
data_uji = []
for i in range(15):
print("\n" + "-" * 50)
print(f"PERTANYAAN KE-{i + 1}")
pertanyaan = input("Pertanyaan: ").strip()
keyword = input("Kata kunci jawaban: ").strip()
if not pertanyaan or not keyword:
print("Pertanyaan dan kata kunci wajib diisi.")
print("Evaluasi dibatalkan. Jalankan kembali menu 5.")
return
data_uji.append((pertanyaan, keyword))
# Bangun dua koleksi dari PDF yang sama.
koleksi_500 = buat_koleksi_evaluasi(500, file_pdf)
if koleksi_500 is None:
return
koleksi_1000 = buat_koleksi_evaluasi(1000, file_pdf)
if koleksi_1000 is None:
return
koleksi = {
"Chunk 500": koleksi_500,
"Chunk 1000": koleksi_1000
}
hasil_evaluasi = {}
for nama, collection in koleksi.items():
print("\n" + "=" * 60)
print(f"EVALUASI {nama}")
print("=" * 60)
hasil_evaluasi[nama] = {}
for k in [1, 3, 5]:
print(f"\n--- PENGUJIAN HIT@{k} ---")
hit, total, skor = hitung_hit_at_k(
collection,
data_uji,
k
)
hasil_evaluasi[nama][k] = {
"hit": hit,
"total": total,
"skor": skor
}
print(f"\nHit@{k} = {hit}/{total} = {skor:.2%}")
# Tampilkan perbandingan.
print("\n" + "=" * 68)
print("TABEL PERBANDINGAN HASIL HIT@K")
print("=" * 68)
print(
f"{'Ukuran Chunk':<18}"
f"{'Hit@1':<16}"
f"{'Hit@3':<16}"
f"{'Hit@5':<16}"
)
print("-" * 68)
for nama in koleksi:
h1 = hasil_evaluasi[nama][1]["skor"]
h3 = hasil_evaluasi[nama][3]["skor"]
h5 = hasil_evaluasi[nama][5]["skor"]
print(
f"{nama:<18}"
f"{h1:<16.2%}"
f"{h3:<16.2%}"
f"{h5:<16.2%}"
)
print("-" * 68)
print("""
Catatan:
Hit@K adalah proporsi pertanyaan yang memiliki
minimal satu dokumen relevan pada K hasil teratas.
Evaluasi ini menggunakan pencocokan kata/frasa
kunci sederhana, bukan penilaian relevansi manual.
Periksa kembali hasil MISS dan kecocokan keyword
agar kesimpulan praktikum lebih akurat.
""")
print("Evaluasi Hit@K selesai!")
# =====================================================
# MENU UTAMA
# =====================================================
def main():
while True:
print("\n")
print("=" * 60)
print(" RAG PART 1 SYSTEM")
print("=" * 60)
print("1. Lab 1 - Eksperimen Chunking")
print("2. Lab 2 - Indexing ChromaDB")
print("3. Lab 2 - Pencarian Dokumen")
print("4. Lab 2 - Pengujian Parafrase")
print("5. Lab 3 - Evaluasi Hit@K")
print("6. Keluar")
print("=" * 60)
pilihan = input("Pilih menu (1-6): ").strip()
if pilihan == "1":
lab_chunking()
elif pilihan == "2":
lab_indexing()
elif pilihan == "3":
lab_pencarian()
elif pilihan == "4":
lab_parafrase()
elif pilihan == "5":
lab_evaluasi()
elif pilihan == "6":
print("\nProgram selesai. Terima kasih!")
break
else:
print("\nPilihan tidak valid!")
# =====================================================
# MENJALANKAN PROGRAM
# =====================================================
if __name__ == "__main__":
main()