File size: 6,854 Bytes
9bc99d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""AUBIN-Learn Beceri-2: kaynakların AÇIK eğitim bölümünden (Kev train + kev_augment ek verisi, Kev'in hiçbir kümesiyle
çakışmaz) öğrenilen ince-ayarlı kodlayıcı becerisi. Tek kodlayıcı + (kaynak, soru) başına çıkış dilimi; girdi = soru + seçenek
metinleri + durum. Eğitim ayarları ÖNCEDEN sabit (dev/test'e bakılmaz); kev_dev yalnız sonra birleştirme ağırlığı seçiminde,
kev_test / kev_transfer_test yalnız rapor. Çıktı: her küme için soru sırasına hizalı olasılıklar (seçenek ADIyla eşlenmiş).

    KEV_EXTRA_TRAIN=kev_extra.jsonl python learn_skill2.py --work /tmp/kev --model microsoft/deberta-v3-base --out skill2.json
"""
import argparse, json, math, os, random, sys, time
import numpy as np
import torch

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import kevdata
from kev_llm import items

SUITES = ("kev_dev", "kev_test", "kev_transfer_test", "kev_cal")


def gkey(it):
    return it["src"] + "|" + it["key"].rsplit("/", 1)[1]


def text(it):
    # soru (kısa; MNLI hipotezi / BoolQ sorusu burada) → durum → seçenekler: kesilme yalnız seçenek listesinden olur
    # (ilk sürümde seçenekler öndeydi; banking77'de 77 seçenek durumu tamamen kesiyordu)
    return f"{it['q']}\n\n{it['state']}\n\nOptions: " + " ; ".join(it["texts"][:26])


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--work", default="/tmp/kev"); ap.add_argument("--model", default="microsoft/deberta-v3-base")
    ap.add_argument("--epochs", type=float, default=3); ap.add_argument("--bs", type=int, default=32)
    ap.add_argument("--lr", type=float, default=3e-5); ap.add_argument("--max_len", type=int, default=256)
    ap.add_argument("--seed", type=int, default=0); ap.add_argument("--out", default="skill2.json")
    ap.add_argument("--micro", type=int, default=0)    # >0: mikro-yığın boyu (gradyan biriktirme + checkpointing)
    a = ap.parse_args()
    random.seed(a.seed); torch.manual_seed(a.seed)
    K = kevdata.load(a.work)
    tr = items(K["kev_train"])
    G = {}
    for it in tr:
        G.setdefault(gkey(it), set()).add(str(it["keys"][it["y"]]))
    cnt = {}
    for it in tr:
        cnt[gkey(it)] = cnt.get(gkey(it), 0) + 1
    G = {g: sorted(v) for g, v in G.items() if len(v) >= 2 and cnt[g] >= 50}
    off, tot = {}, 0
    for g in sorted(G):
        off[g] = tot; tot += len(G[g])
    tr = [it for it in tr if gkey(it) in G]
    print({"train_items": len(tr), "groups": len(G), "slots": tot}, flush=True)

    from transformers import AutoTokenizer, AutoModel
    tok = AutoTokenizer.from_pretrained(a.model)
    enc = AutoModel.from_pretrained(a.model, dtype=torch.float32).cuda().float()   # ana ağırlıklar fp32 (AMP fp16 yalnız hesapta)
    if a.micro:
        enc.gradient_checkpointing_enable()
    head = torch.nn.Linear(enc.config.hidden_size, tot).cuda()
    params = list(enc.parameters()) + list(head.parameters())
    opt = torch.optim.AdamW([{"params": enc.parameters(), "lr": a.lr}, {"params": head.parameters(), "lr": a.lr * 10}], weight_decay=0.01)
    steps = int(math.ceil(len(tr) / a.bs) * a.epochs)
    sched = torch.optim.lr_scheduler.LambdaLR(opt, lambda s: min(1.0, s / max(1, int(0.06 * steps))) * max(0.0, (steps - s) / steps))
    scaler = torch.cuda.amp.GradScaler()

    def batch(its):
        b = tok([text(x) for x in its], truncation=True, max_length=a.max_len, padding=True, return_tensors="pt")
        return {k: v.cuda() for k, v in b.items()}

    def logits(b, its):
        h = enc(**b).last_hidden_state[:, 0]
        z = head(h).float()
        m = torch.full_like(z, -1e4)
        for i, x in enumerate(its):
            g = gkey(x); m[i, off[g]: off[g] + len(G[g])] = 0
        return z + m

    t0 = time.time(); s = 0; enc.train()
    while s < steps:
        order = list(range(len(tr))); random.shuffle(order)
        for i in range(0, len(order), a.bs):
            if s >= steps:
                break
            its = [tr[j] for j in order[i: i + a.bs]]
            opt.zero_grad(set_to_none=True)
            for j in range(0, len(its), a.micro or len(its)):         # gradyan biriktirme (büyük kodlayıcılar T4'e sığsın)
                mb = its[j: j + (a.micro or len(its))]
                y = torch.tensor([off[gkey(x)] + G[gkey(x)].index(str(x["keys"][x["y"]])) for x in mb]).cuda()
                with torch.autocast("cuda", dtype=torch.float16):
                    z = logits(batch(mb), mb)
                loss = torch.nn.functional.cross_entropy(z, y) * len(mb) / len(its)
                scaler.scale(loss).backward()
            scaler.unscale_(opt); torch.nn.utils.clip_grad_norm_(params, 1.0)
            scaler.step(opt); scaler.update(); sched.step(); s += 1
            if s % 200 == 0:
                print(f"adım {s}/{steps} kayıp {loss.item():.4f} {time.time() - t0:.0f}s", flush=True)

    enc.eval(); R = {"protocol": __doc__.split("    KEV")[0].strip(), "model": a.model, "epochs": a.epochs, "lr": a.lr,
                     "max_len": a.max_len, "train_items": len(tr), "train_minutes": round((time.time() - t0) / 60, 1), "suites": {}}
    for sname in SUITES:
        if sname not in K:
            continue
        ev = items(K[sname]); P = []
        with torch.no_grad():
            for i in range(0, len(ev), 64):
                its = ev[i: i + 64]
                ok = [x for x in its if gkey(x) in G]
                pr = {}
                if ok:
                    with torch.autocast("cuda", dtype=torch.float16):
                        z = logits(batch(ok), ok)
                    for x, row in zip(ok, torch.log_softmax(z, -1).exp().cpu().numpy()):
                        g = gkey(x); pos = {c: j for j, c in enumerate(G[g])}
                        p = np.array([row[off[g] + pos[str(k)]] if str(k) in pos else 0.0 for k in x["keys"]], dtype=np.float64)
                        pr[x["key"]] = (p / p.sum()).round(5).tolist() if p.sum() > 0 else None
                P += [pr.get(x["key"]) for x in its]
        acc = [int(np.argmax(p)) == x["y"] for p, x in zip(P, ev) if p is not None]
        bys = {}
        for p, x in zip(P, ev):
            if p is not None:
                bys.setdefault(x["src"], []).append(int(np.argmax(p)) == x["y"])
        R["suites"][sname] = {"keys": [x["key"] for x in ev], "p": P, "covered": len(acc), "n": len(ev),
                              "skill_acc_covered": round(float(np.mean(acc)), 4) if acc else None,
                              "by_source": {k: [len(v), round(float(np.mean(v)), 4)] for k, v in sorted(bys.items())}}
        print(sname, R["suites"][sname]["covered"], R["suites"][sname]["skill_acc_covered"], R["suites"][sname]["by_source"], flush=True)
        json.dump(R, open(a.out, "w"))
    print("BITTI", a.out, flush=True)


if __name__ == "__main__":
    main()