File size: 9,120 Bytes
1e2abdd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | """
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()
|