smartcore-v1 / code /kod /faz7_prep_rag_v15b.py
kdirgul's picture
v1.5b faz7: RAG-SFT {messages} converter (system=direktif/user=baglam+soru/assistant=kisa cevap), kisa-extractive+abstain, faz6 sablonu tek-kaynak, smoke-dogrulandi
1e2abdd verified
Raw
History Blame Contribute Delete
9.12 kB
"""
Faz 7 v1.5b — RAG-aware SFT verisi, çok-turlu {messages} chat formatı (kısa-extractive + abstain).
v1 (faz7_prep_rag_sft.py = {instruction,input,output}) KORUNUR; bu YENİ üretici faz6_sft.encode_messages
ile uyumlu {messages} verir. RAG chat'e doğal oturur: system=direktif, user=bağlam+soru, assistant=kısa cevap.
KARAR: RAG-SFT KISA-EXTRACTIVE kalır (sohbet SFT'sinden AYRI; factuality > akıcılık) + abstain ("bilmiyorum").
Kaynaklar (extractive, cevap bağlamda span — şema DOĞRULANDI v1'de):
EN = rajpurkar/squad_v2 (answerable + unanswerable=abstain HAZIR)
TR = ucsahin/TR-Extractive-QA-82K (answer span; abstain YOK → sentetik alakasız-eşleme)
Çıktı: {"messages":[system,user,assistant], "lang", "src"} JSONL → faz6_sft.py --data (encode_messages).
Şablon faz6_prep_v15b.render() (### Sistem/### Kullanıcı/### Asistan) — tek-kaynak import.
⚠️ faz7_rag.py inference AYNI SYS/user_msg/is_tr'yi kullanmalı (train=inference hizası) → oradan import edilecek.
Çalıştırma (Colab/yerel; datasets+sentencepiece kurulu, HF login):
HF_TOKEN=hf_xxx python faz7_prep_rag_v15b.py --out rag_v15b.jsonl --n_en 15000 --n_tr 15000 --answer_mode sentence
"""
import os, sys, json, re, random, argparse
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from faz6_prep_v15b import render, valid, load_tok # tek-kaynak şablon + tokenizer
EN_REPO = "rajpurkar/squad_v2"
TR_REPO = "ucsahin/TR-Extractive-QA-82K"
ABSTAIN = {"tr": "bilmiyorum", "en": "I don't know"}
# TR tespiti: özel karakter VEYA TR soru-kelimesi (faz7_rag ile AYNI tutulur — inference hizası)
_TR_WORDS = {"nedir", "ne", "hangi", "neresi", "nerede", "nereye", "kim", "kimdir", "neden",
"niçin", "niye", "mıdır", "midir", "mudur", "müdür", "kaç", "kaçtır", "nasıl",
"mı", "mi", "mu", "mü", "için", "ile", "kaçıncı"}
# RAG system direktifi (dil-duyarlı) — inference'ta faz7_rag AYNISINI system mesajı yapmalı
SYS = {
"tr": ("Sen bir RAG asistanısın. SADECE aşağıdaki bağlamı kullanarak soruyu KISA ve doğrudan yanıtla; "
"ezbere/dışarıdan bilgi ekleme, adım adım düşünme. Cevap bağlamda yoksa \"bilmiyorum\" de."),
"en": ("You are a RAG assistant. Answer the question using ONLY the context below, briefly and directly; "
"do not add outside knowledge or think step by step. If the answer is not in the context, say \"I don't know\"."),
}
# ───────────── saf-mantık (yerelde gerçek tokenizer'la test edilebilir) ─────────────
def is_tr(question):
ql = question.lower()
if any(ch in ql for ch in "çğıöşü"):
return True
words = set(ql.replace("?", " ").replace("'", " ").replace(".", " ").split())
return bool(words & _TR_WORDS)
def user_msg(question, context):
q, ctx = question.strip(), context.strip()
if is_tr(question):
return f"Bağlam:\n{ctx}\n\nSoru: {q}"
return f"Context:\n{ctx}\n\nQuestion: {q}"
def sentence_with(context, answer):
"""answer span'inin geçtiği TAM cümleyi döndür (cümle-düzeyi extractive). Bulamazsa span."""
for s in re.split(r"(?<=[.!?])\s+", context.strip()):
if answer in s:
return s.strip()
return answer
def tok_len(sp, messages):
return len(sp.encode(render(messages), out_type=int)) + 1
def make_row(sp, question, context, answer, max_len, sentence=False):
"""{messages:[system,user,assistant]}; max_len aşılırsa None (KESME YOK). Cevap KISA (span/cümle/abstain)."""
q, ctx, a = question.strip(), context.strip(), answer.strip()
if not (q and ctx and a):
return None
out = sentence_with(ctx, a) if sentence else a
lang = "tr" if is_tr(q) else "en"
messages = [
{"role": "system", "content": SYS[lang]},
{"role": "user", "content": user_msg(q, ctx)},
{"role": "assistant", "content": out},
]
if not valid(messages) or tok_len(sp, messages) > max_len:
return None
return {"messages": messages, "lang": lang, "src": "rag"}
def is_abstain(row):
return row["messages"][-1]["content"].strip() in ABSTAIN.values()
def synth_tr_abstain(sp, rows, k, max_len, rng):
"""Alakasız soru↔bağlam eşle → 'bilmiyorum' (TR abstain; ucsahin'de doğal yok)."""
out, n = [], len(rows)
if n < 2 or k <= 0:
return out
for _ in range(k * 4):
if len(out) >= k:
break
i, j = rng.randrange(n), rng.randrange(n)
if i == j:
continue
q, ans_i = rows[i][0], rows[i][2]
ctx = rows[j][1]
if ans_i and ans_i in ctx: # cevap tesadüfen bu bağlamda → mismatch değil, atla
continue
r = make_row(sp, q, ctx, ABSTAIN["tr"], max_len)
if r:
out.append(r)
return out[:k]
# ───────────── yükleyiciler ─────────────
def gather_en(sp, args):
from datasets import load_dataset
ds = load_dataset(EN_REPO, split="train")
sent = args.answer_mode == "sentence"
pos, neg = [], []
for ex in ds:
texts = (ex.get("answers") or {}).get("text") or []
if texts:
r = make_row(sp, ex.get("question", ""), ex.get("context", ""), texts[0], args.max_len, sentence=sent)
if r:
pos.append(r)
else:
r = make_row(sp, ex.get("question", ""), ex.get("context", ""), ABSTAIN["en"], args.max_len)
if r:
neg.append(r)
if args.cap_src and len(pos) >= args.cap_src and len(neg) >= args.cap_src:
break
print(f"[en] {EN_REPO}: answerable={len(pos)} abstain={len(neg)}", flush=True)
return pos, neg
def gather_tr(sp, args):
from datasets import load_dataset
ds = load_dataset(TR_REPO, split="train")
sent = args.answer_mode == "sentence"
pos, rows = [], []
for ex in ds:
q = (ex.get("question") or "").strip(); ctx = (ex.get("context") or "").strip()
a = (ex.get("answer") or "").strip()
r = make_row(sp, q, ctx, a, args.max_len, sentence=sent)
if r:
pos.append(r); rows.append((q, ctx, a))
if args.cap_src and len(pos) >= args.cap_src:
break
print(f"[tr] {TR_REPO}: answerable={len(pos)}", flush=True)
return pos, rows
def stats(sp, rows, name):
if not rows:
print(f"[{name}] 0 örnek", flush=True); return
sample = rows if len(rows) <= 3000 else random.sample(rows, 3000)
ls = sorted(tok_len(sp, r["messages"]) for r in sample)
ab = sum(1 for r in rows if is_abstain(r))
print(f"[{name}] n={len(rows)} abstain={ab} | token: med={ls[len(ls)//2]} "
f"p90={ls[int(len(ls)*0.9)]} max={ls[-1]}", flush=True)
def take_balanced(pos, neg, n, abstain_frac):
n_neg = min(len(neg), int(n * abstain_frac))
return pos[:n - n_neg] + neg[:n_neg]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--out", default="rag_v15b.jsonl")
ap.add_argument("--max_len", type=int, default=2048)
ap.add_argument("--n_en", type=int, default=15000)
ap.add_argument("--n_tr", type=int, default=15000)
ap.add_argument("--abstain_frac", type=float, default=0.15, help="abstain (bilmiyorum) örnek oranı")
ap.add_argument("--answer_mode", default="sentence", choices=["span", "sentence"],
help="span=kısa | sentence=span'in geçtiği TAM cümle (v1'de sentence 6/6 kazandı)")
ap.add_argument("--no_tr_abstain", action="store_true")
ap.add_argument("--cap_src", type=int, default=0, help="smoke test: kaynak başına ~N örnek (0=üretim)")
ap.add_argument("--seed", type=int, default=42)
args = ap.parse_args()
token = os.environ.get("HF_TOKEN")
try:
from huggingface_hub import get_token
token = token or get_token()
except Exception:
pass
sp = load_tok(token)
rng = random.Random(args.seed)
print("=== EN (squad_v2) ===", flush=True)
en_pos, en_neg = gather_en(sp, args)
rng.shuffle(en_pos); rng.shuffle(en_neg)
en = take_balanced(en_pos, en_neg, args.n_en if not args.cap_src else min(args.n_en, args.cap_src), args.abstain_frac)
print("=== TR (TR-82K) ===", flush=True)
tr_pos, tr_rows = gather_tr(sp, args)
rng.shuffle(tr_pos); rng.shuffle(tr_rows)
k_neg = int((args.n_tr if not args.cap_src else args.cap_src) * args.abstain_frac)
tr_neg = [] if args.no_tr_abstain else synth_tr_abstain(sp, tr_rows, k_neg, args.max_len, rng)
tr = take_balanced(tr_pos, tr_neg, args.n_tr if not args.cap_src else min(args.n_tr, args.cap_src), args.abstain_frac)
stats(sp, en, "EN"); stats(sp, tr, "TR")
data = en + tr; rng.shuffle(data)
with open(args.out, "w", encoding="utf-8") as f:
for r in data:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
stats(sp, data, "TOPLAM")
print(f"\n[bitti] {len(data)} örnek (EN {len(en)} + TR {len(tr)}) -> {args.out}", flush=True)
if __name__ == "__main__":
main()