Spaces:
Sleeping
Sleeping
| # data_preparation.py | |
| import os, re, time, json | |
| from typing import List, Dict, Tuple | |
| import numpy as np | |
| import faiss | |
| from datasets import load_dataset, DownloadConfig | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| # Embedding sağlayıcısı (E5) -> providers.embed | |
| from providers import embed | |
| # ========================= | |
| # AYARLAR | |
| # ========================= | |
| DATASET_NAME = "wikimedia/wikipedia" # HF dataset adı | |
| WIKI_CONFIG = "20231101.tr" # Türkçe dump sürümü | |
| MAX_PAGES = 500 # hızlı demo için 500 | |
| CHUNK_SIZE = 1000 # büyük parça = daha az chunk | |
| CHUNK_OVERLAP= 100 | |
| MAX_CHUNKS = 8000 # güvenli üst limit (None yapıldığında sınırsız) | |
| VSTORE_DIR = "vectorstore" | |
| META_JSONL = "meta.jsonl" | |
| FAISS_FILE = "index.faiss" | |
| SIGN_FILE = "signature.json" | |
| # ========================= | |
| # Yardımcılar | |
| # ========================= | |
| def ensure_dir(p: str): | |
| os.makedirs(p, exist_ok=True) | |
| def slugify_title(title: str) -> str: | |
| t = (title or "").strip().replace(" ", "_") | |
| t = re.sub(r"[^\w\-ÇçĞğİıÖöŞşÜü]+", "", t, flags=re.UNICODE) | |
| return t or "Sayfa" | |
| # Gereksiz template token temizliği | |
| TOKEN_PAT = re.compile(r"<\|/?(system|user|assistant|start|end)\|>|<\/?s>|<\/?unk>", re.IGNORECASE) | |
| def clean_text(s: str) -> str: | |
| if not s: | |
| return "" | |
| s = TOKEN_PAT.sub(" ", s) | |
| return re.sub(r"\s+", " ", s).strip() | |
| # ========================= | |
| # Chunking | |
| # ========================= | |
| def chunk_documents(rows: List[Dict]) -> Tuple[List[str], List[Dict]]: | |
| splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=CHUNK_SIZE, | |
| chunk_overlap=CHUNK_OVERLAP, | |
| ) | |
| texts_raw: List[str] = [] | |
| metas: List[Dict] = [] | |
| for r in rows: | |
| title = clean_text(r.get("title", "")) | |
| text = clean_text(r.get("text", "")) | |
| if not text: | |
| continue | |
| url = f"https://tr.wikipedia.org/wiki/{slugify_title(title)}" | |
| chunks = splitter.split_text(text) | |
| for i, ch in enumerate(chunks): | |
| ch = ch.strip() | |
| if not ch: | |
| continue | |
| texts_raw.append(ch) # meta'ya RAW metin yazılıyor | |
| metas.append({"title": title or "(başlık yok)", "chunk_id": i, "source": url}) | |
| if MAX_CHUNKS and len(texts_raw) >= MAX_CHUNKS: | |
| return texts_raw, metas | |
| return texts_raw, metas | |
| # ========================= | |
| # FAISS (HNSW) — hızlı ANN arama | |
| # ========================= | |
| def build_faiss_index(vecs: np.ndarray) -> faiss.Index: | |
| """ | |
| Cosine benzerliği için L2 normalize edip Inner-Product ile HNSW kullanılır. | |
| HNSW, IndexFlat'e yakın doğrulukta olup sorgu süresini ciddi düşürür. | |
| """ | |
| faiss.normalize_L2(vecs) | |
| dim = vecs.shape[1] | |
| M = 32 # graph degree | |
| index = faiss.IndexHNSWFlat(dim, M, faiss.METRIC_INNER_PRODUCT) | |
| index.hnsw.efConstruction = 80 # inşa kalitesi (yüksek = daha iyi/az fark) | |
| index.add(vecs) | |
| return index | |
| # ========================= | |
| # Ana akış | |
| # ========================= | |
| def main(): | |
| t0 = time.time() | |
| print("👉 Wikipedia(TR) yükleniyor...") | |
| split_expr = f"train[:{MAX_PAGES}]" if MAX_PAGES else "train" | |
| ds = load_dataset( | |
| DATASET_NAME, WIKI_CONFIG, | |
| split=split_expr, | |
| download_config=DownloadConfig(max_retries=5), | |
| ) | |
| print(f"Toplam sayfa (seçim sonrası): {len(ds)}") | |
| print("👉 Chunk'lanıyor...") | |
| texts_raw, metas = chunk_documents([dict(x) for x in ds]) | |
| print(f"Toplam chunk: {len(texts_raw)}") | |
| if not texts_raw: | |
| raise SystemExit("⚠️ Metin bulunamadı.") | |
| print("👉 Embedding hesaplanıyor (E5)...") | |
| # E5 kuralı: embed edilirken PASSAGE prefix kullan, meta'da görünmesin | |
| texts_for_emb = [f"passage: {t}" for t in texts_raw] | |
| vecs = np.asarray(embed(texts_for_emb), dtype="float32") | |
| if vecs.ndim != 2: | |
| raise ValueError(f"Beklenen (N,D) vektör, gelen {vecs.shape}") | |
| print("👉 FAISS (HNSW) indeks oluşturuluyor...") | |
| index = build_faiss_index(vecs) | |
| print("👉 Kaydediliyor...") | |
| ensure_dir(VSTORE_DIR) | |
| faiss.write_index(index, os.path.join(VSTORE_DIR, FAISS_FILE)) | |
| meta_path = os.path.join(VSTORE_DIR, META_JSONL) | |
| with open(meta_path, "w", encoding="utf-8") as f: | |
| for t, m in zip(texts_raw, metas): | |
| f.write(json.dumps({"text": t, "metadata": m}, ensure_ascii=False) + "\n") | |
| sign_path = os.path.join(VSTORE_DIR, SIGN_FILE) | |
| with open(sign_path, "w", encoding="utf-8") as f: | |
| json.dump({ | |
| "dataset": f"{DATASET_NAME}:{WIKI_CONFIG}", | |
| "max_pages": MAX_PAGES, | |
| "chunk_size": CHUNK_SIZE, | |
| "chunk_overlap": CHUNK_OVERLAP, | |
| "max_chunks": MAX_CHUNKS, | |
| "faiss": {"type": "HNSWFlat", "metric": "IP", "M": 32, "efConstruction": 80}, | |
| "emb_model": os.getenv("EMB_MODEL", "intfloat/multilingual-e5-small"), | |
| }, f, ensure_ascii=False, indent=2) | |
| print(f"✅ Tamamlandı. Süre: {(time.time()-t0):.1f} sn | Çıktı klasörü: {VSTORE_DIR}") | |
| if __name__ == "__main__": | |
| main() |