Mukul Rayana
feat: eval scripts — BM25 baseline, adversarial probes, BERTScore, RAGAS, Wilcoxon (Day 14)
78fc1e6 | """ | |
| eval/condition_a.py | |
| Condition A — BM25 sparse retrieval baseline (no emotion components). | |
| Loads all chunk texts from SQLite, builds a BM25 index, retrieves top_k | |
| chunks for a query using keyword overlap only. | |
| Used by run_ablation.py as the Condition A retrieval function. | |
| """ | |
| import sqlite3 | |
| import json | |
| from rank_bm25 import BM25Okapi | |
| DB_PATH = "data/indexes/metadata.db" | |
| def load_bm25_index(db_path: str = DB_PATH): | |
| """Load all chunk texts from SQLite and build BM25 index. Returns (bm25, id_list, text_list).""" | |
| conn = sqlite3.connect(db_path) | |
| rows = conn.execute("SELECT id, text FROM chunks ORDER BY id").fetchall() | |
| conn.close() | |
| ids = [r[0] for r in rows] | |
| texts = [r[1] for r in rows] | |
| tokenized = [t.lower().split() for t in texts] | |
| bm25 = BM25Okapi(tokenized) | |
| return bm25, ids, texts | |
| def retrieve_bm25(query: str, bm25, ids, texts, top_k: int = 5): | |
| """Retrieve top_k chunks using BM25. Returns list of text strings.""" | |
| tokens = query.lower().split() | |
| scores = bm25.get_scores(tokens) | |
| import numpy as np | |
| top_indices = scores.argsort()[::-1][:top_k] | |
| return [texts[i] for i in top_indices] | |
| if __name__ == "__main__": | |
| print("Building BM25 index from SQLite (this takes ~60-90s for 1.67M chunks)...") | |
| bm25, ids, texts = load_bm25_index() | |
| print(f"BM25 index built: {len(texts):,} documents") | |
| # Quick sanity check | |
| results = retrieve_bm25("I feel hopeless and overwhelmed", bm25, ids, texts, top_k=3) | |
| print(f"Sample retrieval (3 chunks):") | |
| for i, r in enumerate(results): | |
| print(f" [{i+1}] {r[:100]}...") | |