File size: 3,552 Bytes
5c6c847
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""长距离精确回忆(联想检索)实验:专门测 ZeroPoint 的已知弱点。

序列格式(每个数字是一个词元):
    k1 v1 k2 v2 ... kN vN  [问]  kq   →  模型要说出 kq 对应的 v
键从 16 个里不重复地抽 N 个,值从另外 16 个里随机抽。要答对,模型必须把 N 对"键 → 值"原样记住。
注意力天生擅长这个(用 kq 当查询去找那个键);ZeroPoint 只有 M 个槽,对数一多就会混。

    python3 experiments/recall.py
训练时 N 在 2~16 之间随机,测试时分别报告 N = 2, 4, 8, 12, 16 的准确率。
结果:experiments/recall.md(最新)+ experiments/results/<时间>-recall.md(存档)
"""
import os

import numpy as np

from common import CFG, save_result
from models import TransformerLM, ZeroX
import zerocg as zc
from zerocg import functional as F

ITERS = int(os.environ.get("RECALL_ITERS", 3000))
NK, NV = 16, 16
QUERY = NK + NV          # "问" 这个词元
VOCAB = NK + NV + 1
MAX_N = 16
MAX_LEN = 2 * MAX_N + 2
TEST_N = [2, 4, 8, 12, 16]
SEEDS = [0, 1]
MODELS = {
    "Transformer": lambda: TransformerLM(VOCAB, MAX_LEN, **CFG),
    "ZeroPoint (16 槽)": lambda: ZeroX(VOCAB, n_slot=16, **CFG),
    "ZeroPoint (32 槽)": lambda: ZeroX(VOCAB, n_slot=32, **CFG),
}


def make_batch(rng, B, n):
    keys = np.stack([rng.permutation(NK)[:n] for _ in range(B)])
    vals = rng.integers(NK, NK + NV, size=(B, n))
    seq = np.empty((B, 2 * n + 2), dtype=np.int64)
    seq[:, 0:2 * n:2], seq[:, 1:2 * n:2] = keys, vals
    q = rng.integers(0, n, B)
    seq[:, -2], seq[:, -1] = QUERY, keys[np.arange(B), q]
    return seq, vals[np.arange(B), q]


def train_and_eval(make, seed):
    np.random.seed(seed)
    rng = np.random.default_rng(seed)
    model = make()
    opt = zc.optim.AdamW(model.parameters(), lr=3e-3, weight_decay=0.01)
    for it in range(ITERS):
        opt.lr = 3e-3 * min(1, (it + 1) / 100)
        x, y = make_batch(rng, 32, int(rng.integers(2, MAX_N + 1)))
        loss = F.cross_entropy(model(x)[:, -1], y)
        opt.zero_grad()
        loss.backward()
        zc.optim.clip_grad_norm(model.parameters(), 1.0)
        opt.step()
    model.eval()
    test = np.random.default_rng(1000 + seed)
    acc = {}
    with zc.no_grad():
        for n in TEST_N:
            x, y = make_batch(test, 512, n)
            acc[n] = float((model(x).data[:, -1].argmax(-1) == y).mean())
    return acc


def main():
    print(f"联想检索:{len(MODELS)} 个模型 × 种子 {SEEDS},每次 {ITERS} 步", flush=True)
    res = {}
    for name, make in MODELS.items():
        runs = [train_and_eval(make, s) for s in SEEDS]
        res[name] = {n: np.mean([r[n] for r in runs]) for n in TEST_N}
        print(f"  => {name}: " + "  ".join(f"N={n}:{res[name][n]:.0%}" for n in TEST_N), flush=True)
    lines = ["# 长距离精确回忆(联想检索)\n",
             "`k1 v1 k2 v2 … kN vN [问] kq` → 回答 kq 对应的值。16 个键、16 个值,随机猜的准确率是 1/16 ≈ 6%。",
             f"三个模型宽 {CFG['n_embd']}、{CFG['n_layer']} 层、{CFG['n_head']} 头;训练 {ITERS} 步,N 在 2~{MAX_N} 随机;"
             f"每格是 512 道测试题 × 种子 {SEEDS} 的平均准确率。\n",
             "| 模型 | " + " | ".join(f"N = {n}" for n in TEST_N) + " |", "|---|" + "---|" * len(TEST_N)]
    for name, acc in res.items():
        lines.append(f"| {name} | " + " | ".join(f"{acc[n]:.0%}" for n in TEST_N) + " |")
    save_result("recall", lines)


if __name__ == "__main__":
    main()