pengumpulan tugas
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 246 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
invalid type: string "clients.\n2. Professional Behavior\nEmployees are expected to act with integrity and treat colleagues, clients, and partners with respect.\nWe have a zero-tolerance policy for:\nHarassment, bullying, or discrimination of any kind based on race, gender, religion, age, or sexual\norientation.\nInsubordination or deliberate refusal to perform reasonable duties.\nÿWÿÿÿÿÿÿÿÿÿÿÿÿ
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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"ÿÿÿÿÿÿÿ
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,220 @@
|
|||||||
|
"""
|
||||||
|
PERTEMUAN 10: RAG Part 1 - Embedding & Vector Database
|
||||||
|
Complete Lab Runner: Lab 1 (Chunking), Lab 2 (Indexing & Search), Lab 3 (Hit@k Evaluation)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import random
|
||||||
|
from pypdf import PdfReader
|
||||||
|
import chromadb
|
||||||
|
from chromadb.utils import embedding_functions
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Utility: PDF Reader & Fixed-Size Splitter
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
def baca_pdf(path: Path) -> str:
|
||||||
|
"""Extracts text from all pages of a PDF."""
|
||||||
|
reader = PdfReader(path)
|
||||||
|
return "\n".join((p.extract_text() or "") for p in reader.pages)
|
||||||
|
|
||||||
|
def potong(teks: str, ukuran: int = 800, tumpang: int = 100) -> list[str]:
|
||||||
|
"""Fixed-size chunking with overlap."""
|
||||||
|
chunks, start = [], 0
|
||||||
|
while start < len(teks):
|
||||||
|
chunk = teks[start:start + ukuran].strip()
|
||||||
|
if chunk:
|
||||||
|
chunks.append(chunk)
|
||||||
|
start += ukuran - tumpang
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Lab 1: Chunking Experiment
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
def run_lab1(pdf_path: Path):
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("LAB 1: CHUNKING EXPERIMENT")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
text = baca_pdf(pdf_path)
|
||||||
|
print(f"\n[1] Reading PDF: {pdf_path.name} (Total length: {len(text)} chars)")
|
||||||
|
print("-" * 50)
|
||||||
|
print("First 500 characters preview:")
|
||||||
|
print(text[:500])
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
configs = [(500, 50), (1000, 100), (1500, 150)]
|
||||||
|
print("\n[2] Chunk Counts Across Configurations (overlap ~10%):")
|
||||||
|
for size, overlap in configs:
|
||||||
|
chunks = potong(text, ukuran=size, tumpang=overlap)
|
||||||
|
print(f" - Chunk Size {size} (overlap {overlap}) -> {len(chunks)} chunks")
|
||||||
|
|
||||||
|
print("\n[3] Inspection for Truncated Sentences (3 sample chunks each):")
|
||||||
|
random.seed(42)
|
||||||
|
for size, overlap in configs:
|
||||||
|
chunks = potong(text, ukuran=size, tumpang=overlap)
|
||||||
|
sample_count = min(3, len(chunks))
|
||||||
|
samples = random.sample(chunks, sample_count)
|
||||||
|
print(f"\n--- Chunk Size {size} Samples ({sample_count} samples) ---")
|
||||||
|
for i, s in enumerate(samples, 1):
|
||||||
|
starts_mid = not s[0].isupper()
|
||||||
|
ends_mid = s[-1] not in ".!?"
|
||||||
|
print(f"Sample {i} (len {len(s)}):")
|
||||||
|
print(f" Preview: {s[:80]}... [ends with: '{s[-40:]}']")
|
||||||
|
print(f" Cutoff check: Starts mid-sentence={starts_mid}, Ends mid-sentence={ends_mid}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Lab 2: Indexing & Querying
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
def setup_collection(client: chromadb.PersistentClient, name: str, emb_fn, chunk_size: int, chunk_overlap: int, docs_dir: Path):
|
||||||
|
# Reset collection if exists for clean repeatable runs
|
||||||
|
try:
|
||||||
|
client.delete_collection(name=name)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
col = client.create_collection(
|
||||||
|
name=name,
|
||||||
|
embedding_function=emb_fn,
|
||||||
|
metadata={"hnsw:space": "cosine"}
|
||||||
|
)
|
||||||
|
|
||||||
|
for pdf in docs_dir.glob("*.pdf"):
|
||||||
|
raw_text = baca_pdf(pdf)
|
||||||
|
chunks = potong(raw_text, ukuran=chunk_size, tumpang=chunk_overlap)
|
||||||
|
if not chunks:
|
||||||
|
continue
|
||||||
|
col.upsert(
|
||||||
|
ids=[f"{pdf.stem}-sz{chunk_size}-{i}" for i in range(len(chunks))],
|
||||||
|
documents=chunks,
|
||||||
|
metadatas=[{"sumber": pdf.name, "chunk": i, "size": chunk_size} for i in range(len(chunks))]
|
||||||
|
)
|
||||||
|
return col
|
||||||
|
|
||||||
|
def run_lab2(col):
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("LAB 2: BUILD INDEX & SEARCH")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 5 standard queries
|
||||||
|
queries = [
|
||||||
|
"What is the minimum password length required?",
|
||||||
|
"What are the core working hours for remote staff?",
|
||||||
|
"What is the company policy on harassment and discrimination?",
|
||||||
|
"Where must electronic company data be stored?",
|
||||||
|
"What are the consequences of violating the Code of Conduct?"
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n[1] Testing 5 Standard Queries (top-3 results):")
|
||||||
|
for q in queries:
|
||||||
|
res = col.query(query_texts=[q], n_results=3)
|
||||||
|
top_doc = res["documents"][0][0]
|
||||||
|
top_meta = res["metadatas"][0][0]
|
||||||
|
top_dist = res["distances"][0][0]
|
||||||
|
print(f"\nQuery: '{q}'")
|
||||||
|
print(f" -> Top match: [{top_meta['sumber']} | chunk {top_meta['chunk']}] (distance: {top_dist:.4f})")
|
||||||
|
print(f" -> Snippet: {top_doc[:120].replace(chr(10), ' ')}...")
|
||||||
|
|
||||||
|
# 2 paraphrased queries (different wording, semantic match)
|
||||||
|
paraphrased = [
|
||||||
|
("Can I log in using a free cafe internet connection?", "Remote work / VPN on public Wi-Fi"),
|
||||||
|
("What is the penalty if I take a second job or outside gig?", "Conflict of interest / outside employment")
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n[2] Testing 2 Paraphrased Queries (Semantic vs Keyword):")
|
||||||
|
for q, intent in paraphrased:
|
||||||
|
res = col.query(query_texts=[q], n_results=1)
|
||||||
|
top_doc = res["documents"][0][0]
|
||||||
|
top_meta = res["metadatas"][0][0]
|
||||||
|
top_dist = res["distances"][0][0]
|
||||||
|
print(f"\nParaphrased: '{q}' (Target: {intent})")
|
||||||
|
print(f" -> Retrieved: [{top_meta['sumber']} | chunk {top_meta['chunk']}] (distance: {top_dist:.4f})")
|
||||||
|
print(f" -> Snippet: {top_doc[:140].replace(chr(10), ' ')}...")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Lab 3: Evaluation (Hit@k)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 15 targeted (query, required_keyword) pairs across all 3 PDFs
|
||||||
|
TEST_SET = [
|
||||||
|
("What is the minimum password length required?", "12 characters"),
|
||||||
|
("How often must employees update their passwords?", "90 days"),
|
||||||
|
("Is multi-factor authentication mandatory?", "MFA"),
|
||||||
|
("Where should suspected security incidents be reported?", "security@nexusglobal.dummy"),
|
||||||
|
("What is the shortcut to lock the screen on Windows?", "Win + L"),
|
||||||
|
("What are the mandatory core hours for remote employees?", "10:00 AM to 3:00 PM"),
|
||||||
|
("How quickly must equipment hardware issues be reported?", "24 hours"),
|
||||||
|
("What is required before connecting to public Wi-Fi?", "VPN"),
|
||||||
|
("Who decides if an employee is eligible for remote work?", "department head"),
|
||||||
|
("What should be done with sensitive paper documents?", "shredded"),
|
||||||
|
("What outside business activities must be disclosed to HR?", "outside employment"),
|
||||||
|
("What is the zero-tolerance policy regarding discrimination?", "zero-tolerance"),
|
||||||
|
("What are examples of intellectual property assets?", "client lists"),
|
||||||
|
("What disciplinary actions can follow policy violations?", "termination"),
|
||||||
|
("Where must electronic corporate data be stored?", "encrypted company servers")
|
||||||
|
]
|
||||||
|
|
||||||
|
def hit_at_k(col, test_set, k: int) -> float:
|
||||||
|
"""Computes Hit@k: proportion of queries where answer keyword appears in top-k chunks."""
|
||||||
|
hits = 0
|
||||||
|
for query, keyword in test_set:
|
||||||
|
docs = col.query(query_texts=[query], n_results=k)["documents"][0]
|
||||||
|
if any(keyword.lower() in d.lower() for d in docs):
|
||||||
|
hits += 1
|
||||||
|
return hits / len(test_set)
|
||||||
|
|
||||||
|
def run_lab3(col_500, col_1000):
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("LAB 3: MEASURE HIT@K EVALUATION")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print(f"Evaluating {len(TEST_SET)} test queries across two chunk size collections:\n")
|
||||||
|
print(f"{'Collection':<25} | {'Hit@1':<8} | {'Hit@3':<8} | {'Hit@5':<8}")
|
||||||
|
print("-" * 55)
|
||||||
|
|
||||||
|
for name, col in [("Collection (size=500)", col_500), ("Collection (size=1000)", col_1000)]:
|
||||||
|
h1 = hit_at_k(col, TEST_SET, k=1)
|
||||||
|
h3 = hit_at_k(col, TEST_SET, k=3)
|
||||||
|
h5 = hit_at_k(col, TEST_SET, k=5)
|
||||||
|
print(f"{name:<25} | {h1:<8.2f} | {h3:<8.2f} | {h5:<8.2f}")
|
||||||
|
|
||||||
|
print("\nDetailed Miss Analysis for size=500 at k=1:")
|
||||||
|
for query, keyword in TEST_SET:
|
||||||
|
docs = col_500.query(query_texts=[query], n_results=1)["documents"][0]
|
||||||
|
if not any(keyword.lower() in d.lower() for d in docs):
|
||||||
|
print(f" - Missed: '{query}' (Target keyword: '{keyword}')")
|
||||||
|
print(f" Top retrieved snippet: {docs[0][:90].replace(chr(10), ' ')}...")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Main Execution
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
docs_dir = Path("dokumen")
|
||||||
|
pdf_files = list(docs_dir.glob("*.pdf"))
|
||||||
|
if not pdf_files:
|
||||||
|
print("Error: No PDF files found in 'dokumen/' folder.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialize Embedding Function (runs locally)
|
||||||
|
print("Loading multilingual embedding model (paraphrase-multilingual-MiniLM-L12-v2)...")
|
||||||
|
emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||||
|
model_name="paraphrase-multilingual-MiniLM-L12-v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
client = chromadb.PersistentClient(path="./chroma_db")
|
||||||
|
|
||||||
|
# Lab 1
|
||||||
|
run_lab1(pdf_files[0])
|
||||||
|
|
||||||
|
# Index two collections for comparison (size 500 & size 1000)
|
||||||
|
print("\nIndexing documents into ChromaDB collections...")
|
||||||
|
col_500 = setup_collection(client, "pedoman_500", emb_fn, chunk_size=500, chunk_overlap=50, docs_dir=docs_dir)
|
||||||
|
col_1000 = setup_collection(client, "pedoman_1000", emb_fn, chunk_size=1000, chunk_overlap=100, docs_dir=docs_dir)
|
||||||
|
print("Indexed collections 'pedoman_500' and 'pedoman_1000' successfully.")
|
||||||
|
|
||||||
|
# Lab 2 (using col_500)
|
||||||
|
run_lab2(col_500)
|
||||||
|
|
||||||
|
# Lab 3
|
||||||
|
run_lab3(col_500, col_1000)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Pertemuan 10: RAG Part 1 - Embedding & Vector Database
|
||||||
|
|
||||||
|
Repositori ini berisi implementasi praktikum **Retrieval-Augmented Generation (RAG) Bagian 1** untuk mata kuliah Kecerdasan Buatan (*Artificial Intelligence*). Proyek ini berfokus pada tahapan krusial sebelum generasi teks oleh LLM, yaitu: pemotongan teks (*chunking*), pembuatan *vector embedding*, penyimpanan ke dalam *vector database* (**ChromaDB**), pencarian semantik (*semantic search*), hingga evaluasi performa pengambilan dokumen menggunakan metrik **$\text{Hit}@k$**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Penjelasan Kode (`rag_lab_runner.py`)
|
||||||
|
|
||||||
|
Skrip `rag_lab_runner.py` dibagi menjadi beberapa modul fungsional:
|
||||||
|
|
||||||
|
1. **Fungsi Utilitas (`baca_pdf` & `potong`):**
|
||||||
|
- `baca_pdf(path)`: Membaca seluruh halaman file PDF dan menggabungkannya menjadi satu *string* utuh.
|
||||||
|
- `potong(teks, ukuran, tumpang)`: Memotong teks menjadi bagian-bagian (*chunks*) berukuran tetap (`ukuran`) dengan pergeseran karakter sebesar $\text{step} = \text{ukuran} - \text{tumpang}$ untuk menjaga konteks antarpotongan (*overlap*).
|
||||||
|
|
||||||
|
2. **Lab 1 (`run_lab1`):**
|
||||||
|
- Mengekstrak teks dari salah satu dokumen (`Code of Conduct.pdf`) dan membandingkan jumlah *chunk* yang dihasilkan pada tiga konfigurasi: `(500, 50)`, `(1000, 100)`, dan `(1500, 150)`.
|
||||||
|
- Melakukan inspeksi pemotongan kalimat (*cutoff check*) dengan memeriksa apakah suatu *chunk* diawali huruf kecil (`starts_mid`) atau tidak diakhiri tanda baca akhir kalimat `.!?` (`ends_mid`).
|
||||||
|
|
||||||
|
3. **Lab 2 (`setup_collection` & `run_lab2`):**
|
||||||
|
- Membuat koleksi vektor di ChromaDB (`pedoman_500` dan `pedoman_1000`) menggunakan metrik *cosine distance*.
|
||||||
|
- Menguji pencarian menggunakan **5 kueri standar** (mengambil *top-3* tetapi menampilkan hasil teratas beserta skor jaraknya) dan **2 kueri parafrase** untuk menguji pemahaman semantik model ketika kata kunci eksak tidak digunakan.
|
||||||
|
|
||||||
|
4. **Lab 3 (`hit_at_k` & `run_lab3`):**
|
||||||
|
- Mengevaluasi performa sistem menggunakan **15 pasangan kueri dan kata kunci target (`TEST_SET`)**.
|
||||||
|
- Menghitung nilai $\text{Hit}@k$ untuk $k \in \{1, 3, 5\}$ dengan rumus:
|
||||||
|
$$\text{Hit}@k = \frac{\text{Jumlah kueri yang kata kuncinya ditemukan pada top-}k\text{ chunks}}{\text{Total kueri pengujian } (N = 15)}$$
|
||||||
|
- Menampilkan analisis kegagalan (*Miss Analysis*) pada kueri yang gagal ditemukan di urutan pertama ($k=1$).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Hasil Eksperimen & Pembahasan Output
|
||||||
|
|
||||||
|
Berikut adalah hasil eksekusi program di Windows PowerShell beserta analisis untuk masing-masing laboratorium.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Lab 1: Chunking Experiment
|
||||||
|
|
||||||
|
Pada tahap ini, program membaca file `Code of Conduct.pdf` yang memiliki total panjang **2.015 karakter**.
|
||||||
|
|
||||||
|
.png)
|
||||||
|
*Gambar 1: Pembacaan karakter awal PDF, perbandingan jumlah chunk pada 3 konfigurasi, dan sampel ukuran 500.*
|
||||||
|
|
||||||
|
.png)
|
||||||
|
*Gambar 2: Pemeriksaan kalimat terpotong (cutoff check) pada sampel ukuran 500, 1000, dan 1500.*
|
||||||
|
|
||||||
|
.png)
|
||||||
|
*Gambar 3: Detail akhir pemeriksaan sampel pada konfigurasi chunk 1500.*
|
||||||
|
|
||||||
|
#### Analisis Hasil Lab 1:
|
||||||
|
1. **Perbandingan Jumlah Chunk:**
|
||||||
|
- **Ukuran 500 (overlap 50):** Menghasilkan **5 chunks**.
|
||||||
|
- **Ukuran 1000 (overlap 100):** Menghasilkan **3 chunks**.
|
||||||
|
- **Ukuran 1500 (overlap 150):** Menghasilkan **2 chunks**.
|
||||||
|
- Semakin kecil ukuran *chunk*, semakin banyak jumlah potongan dokumen yang disimpan di database, namun informasi di tiap *chunk* menjadi lebih spesifik.
|
||||||
|
|
||||||
|
2. **Fenomena Kalimat Terpotong (*Truncated Sentences*):**
|
||||||
|
- Karena metode yang digunakan adalah *fixed-size character chunking* (memotong tepat pada hitungan karakter ke-$N$ tanpa memedulikan batas kata atau kalimat), hampir seluruh sampel memiliki status `Ends mid-sentence=True` dan sebagian besar `Starts mid-sentence=True`.
|
||||||
|
- Contoh nyata terlihat pada **Chunk Size 500 Sample 1** yang terpotong tepat di tengah kata: `...Employees are ex`, serta **Sample 3** yang diawali potongan kata `st...` dan diakhiri `...softwar`.
|
||||||
|
- **Solusi Mitigasi:** Penggunaan *overlap* (`tumpang`) sebesar 10% sangat membantu agar kata atau kalimat yang terbelah di ujung suatu *chunk* tetap utuh di bagian awal *chunk* berikutnya.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Lab 2: Build Index & Search
|
||||||
|
|
||||||
|
Pada Lab 2, pencarian dilakukan terhadap koleksi `pedoman_500` (ukuran *chunk* 500 karakter). Jarak dihitung menggunakan *Cosine Distance* (semakin kecil nilai `distance` mendekati $0$, semakin mirip/relevan dokumen tersebut dengan kueri).
|
||||||
|
|
||||||
|
.jpg)
|
||||||
|
*Gambar 4: Hasil pengujian 5 kueri standar dan 2 kueri parafrase pada koleksi ChromaDB.*
|
||||||
|
|
||||||
|
#### Analisis Hasil Lab 2:
|
||||||
|
1. **Pengujian 5 Kueri Standar:**
|
||||||
|
Seluruh kueri standar berhasil memetakan pencarian ke dokumen PDF yang tepat dengan jarak *cosine* yang rendah ($0.3113$ hingga $0.4653$):
|
||||||
|
- Kueri panjang *password* $\rightarrow$ `Data Protection Policy.pdf | chunk 2` (`distance: 0.3603`), langsung memuat jawaban *"at least 12 cha[racters]"*.
|
||||||
|
- Kueri pelecehan & diskriminasi $\rightarrow$ `Code of Conduct.pdf | chunk 1` (`distance: 0.3113` - kemiripan tertinggi).
|
||||||
|
- Kueri tempat penyimpanan data elektronik $\rightarrow$ `Data Protection Policy.pdf | chunk 1` (`distance: 0.3670`).
|
||||||
|
|
||||||
|
2. **Pengujian 2 Kueri Parafrase (Semantic vs Keyword):**
|
||||||
|
- **Kueri 1:** *"Can I log in using a free cafe internet connection?"* (Target maksud: Kebijakan kerja jarak jauh / penggunaan VPN di Wi-Fi publik).
|
||||||
|
- **Hasil:** Mengambil `Remote Work Policy.pdf | chunk 3` (`distance: 0.6669`). Meskipun di kueri menggunakan istilah *"free cafe internet"*, model embedding memahami konteksnya berkaitan dengan *remote work*.
|
||||||
|
- **Kueri 2:** *"What is the penalty if I take a second job or outside gig?"* (Target maksud: Konflik kepentingan / sanksi pekerjaan sampingan).
|
||||||
|
- **Hasil:** Mengambil `Code of Conduct.pdf | chunk 4` (`distance: 0.6437`) yang membahas sanksi mulai dari pelatihan wajib hingga pemutusan hubungan kerja (*termination of employment*).
|
||||||
|
- Skor jarak pada kueri parafrase ($\approx 0.64 - 0.66$) lebih besar dibandingkan kueri standar ($\approx 0.31 - 0.46$) karena tidak adanya kesamaan leksikal (kata per kata), namun pencarian vektor tetap mampu mengarahkan ke dokumen yang berkaitan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Lab 3: Measure Hit@k Evaluation
|
||||||
|
|
||||||
|
Pada Lab 3, dilakukan pengujian kuantitatif menggunakan 15 kueri (`TEST_SET`) untuk membandingkan performa antara `Collection (size=500)` dan `Collection (size=1000)`.
|
||||||
|
|
||||||
|
.png)
|
||||||
|
*Gambar 5: Tabel hasil evaluasi metrik Hit@1, Hit@3, Hit@5 beserta analisis kegagalan (Miss Analysis) pada k=1.*
|
||||||
|
|
||||||
|
#### Tabel Ringkasan Evaluasi $\text{Hit}@k$
|
||||||
|
|
||||||
|
| Collection | $\text{Hit}@1$ | $\text{Hit}@3$ | $\text{Hit}@5$ |
|
||||||
|
| :--- | :---: | :---: | :---: |
|
||||||
|
| **Collection (`size=500`)** | $0.80$ ($12/15$) | **$1.00$ ($15/15$)** | **$1.00$ ($15/15$)** |
|
||||||
|
| **Collection (`size=1000`)** | $0.80$ ($12/15$) | $0.93$ ($14/15$) | $0.93$ ($14/15$) |
|
||||||
|
|
||||||
|
#### Analisis Hasil Lab 3:
|
||||||
|
1. **Perbandingan Ukuran Chunk (`500` vs `1000`):**
|
||||||
|
- Pada **$k=1$**, kedua koleksi sama-sama mencapai akurasi **$0.80$ ($80\%$)**, artinya 12 dari 15 pertanyaan langsung menemukan jawaban di urutan paling atas (*top-1*).
|
||||||
|
- Pada **$k=3$ dan $k=5$**, koleksi **`size=500` unggul mutlak dengan skor sempurna $1.00$ ($100\%$)**, sedangkan `size=1000` tertahan di **$0.93$ ($93\%$)**.
|
||||||
|
- **Mengapa `size=500` lebih baik?** *Chunk* yang lebih kecil memiliki representasi vektor (embedding) yang lebih terfokus pada satu topik spesifik. Pada `size=1000`, satu *chunk* memuat banyak sub-bab sekaligus sehingga makna vektornya menjadi "tercampur" (*diluted embedding*), yang menyebabkan informasi spesifik lebih sulit menempati peringkat teratas.
|
||||||
|
|
||||||
|
2. **Analisis Kegagalan (*Miss Analysis*) pada `size=500` saat $k=1$:**
|
||||||
|
Terdapat 3 kueri yang meleset di peringkat ke-1 (namun berhasil ditemukan ketika cakupan diperluas ke $k=3$):
|
||||||
|
- **Miss 1:** `'What is required before connecting to public Wi-Fi?'` (Target: `'VPN'`)
|
||||||
|
- *Penyebab:* Top-1 justru mengambil `Data Protection Policy.pdf` tentang akses data umum, karena kata *"connecting"* dan *"required"* memiliki kedekatan semantik umum dengan aturan akses data. Potongan tentang VPN berada di urutan ke-2 atau ke-3.
|
||||||
|
- **Miss 2:** `'What should be done with sensitive paper documents?'` (Target: `'shredded'`)
|
||||||
|
- *Penyebab:* Kata *"sensitive ... documents"* sangat dominan beririsan dengan bagian awal aturan penyimpanan data di `Data Protection Policy.pdf` (urutan ke-1), sementara instruksi penghancuran kertas (*shredded*) berada di *chunk* selanjutnya.
|
||||||
|
- **Miss 3:** `'What disciplinary actions can follow policy violations?'` (Target: `'termination'`)
|
||||||
|
- *Penyebab:* Kueri ini mengambil `Code of Conduct.pdf | chunk 1` tentang perilaku profesional di urutan pertama, sedangkan kata kunci `'termination'` berada di bagian akhir dokumen (`chunk 4`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Kesimpulan
|
||||||
|
|
||||||
|
1. **Trade-off Ukuran Chunk:** Metode *fixed-size chunking* dengan ukuran yang lebih kecil (`chunk_size=500`, `overlap=50`) memberikan presisi pengambilan (*retrieval*) yang lebih tinggi ($\text{Hit}@3 = 1.00$) dibandingkan ukuran besar (`chunk_size=1000`, $\text{Hit}@3 = 0.93$) pada dokumen kebijakan yang padat informasi.
|
||||||
|
2. **Kelemahan Fixed-Size Splitting:** Pemotongan berbasis jumlah karakter murni sering membelah kata dan kalimat di tengah jalan (`Ends mid-sentence=True`). Untuk pengembangan selanjutnya, metode ini dapat ditingkatkan menggunakan *Recursive Character Text Splitter* (memotong berdasarkan paragraf `\n\n`, kalimat `. `, lalu spasi).
|
||||||
|
3. **Pentingnya Nilai $k$ pada RAG:** Mengandalkan hanya 1 dokumen teratas ($k=1$) berisiko kehilangan konteks penting (akurasi hanya $80\%$). Dengan mengatur parameter pengambilan ke $k=3$ (`n_results=3`), sistem berhasil mencapai cakupan informasi $100\%$ untuk diteruskan ke *Large Language Model* (LLM).
|
||||||
Reference in New Issue
Block a user