File size: 4,251 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
"""ZeroPoint 消融实验,仿《Attention Is All You Need》表 3:每次只改一处,看那个部件到底有没有用。

    python3 experiments/ablation.py

- 每组 3 个种子,报告 均值 ± 标准差
- 参数量对齐:某个改动让参数量和 base 差超过 1% 时(比如改头数、槽数),自动调整 MLP 宽度把参数量拉回来,
  这样变好变差就不是"参数多了/少了"造成的
- 学习率固定 3e-3(所有变体相同)
结果:experiments/ablation.md(最新)+ experiments/results/<时间>-ablation.md(存档)
"""
import numpy as np

from common import CFG, ITERS, prepare_data, save_result, train
from models import ZeroX

SEEDS = [0, 1, 2]
LR = 3e-3
BASE = dict(n_slot=16, **CFG)

# (行号, 说明, 相对 base 的改动)
VARIANTS = [
    ("base", "ZeroPoint:4 头 × 16 槽,写强度门,写入竞争,分层初始化,λ 可学,可学初始状态,无位置编码", {}),
    ("(A)", "1 个头(每头 64 维)", dict(n_head=1)),
    ("(A)", "8 个头(每头 8 维)", dict(n_head=8)),
    ("(B)", "每头 4 个槽", dict(n_slot=4)),
    ("(B)", "每头 32 个槽", dict(n_slot=32)),
    ("(C)", "去掉写强度门", dict(gate=False)),
    ("(D)", "写入不竞争:softmax 换成每槽独立的 sigmoid", dict(write="sigmoid")),
    ("(E)", "不分层:所有槽初始半衰期相同(约 9.8 个字)", dict(init="uniform")),
    ("(F)", "λ 固定在初始值,不学习", dict(learn_lambda=False)),
    ("(G)", "加上论文 §3.5 的正弦位置编码", dict(pos="sin")),
]


def matched(V, change, target):
    """返回 (参数量对齐后的配置, 是否调整过 MLP)。"""
    cfg = {**BASE, **change}
    n = ZeroX(V, **cfg).num_parameters()
    if abs(n - target) / target <= 0.01:
        return cfg, False
    C, L = cfg["n_embd"], cfg["n_layer"]
    hidden = 4 * C + round((target - n) / (L * (2 * C + 1)))   # 每个隐藏单元每层 2C+1 个参数
    return {**cfg, "mlp_hidden": hidden}, True


def main():
    text, V, train_data, val_data = prepare_data()
    target = ZeroX(V, **BASE).num_parameters()
    print(f"消融实验:{len(VARIANTS)} 组 × 种子 {SEEDS},每次 {ITERS} 步,lr {LR}", flush=True)
    results = []
    for row, desc, change in VARIANTS:
        cfg, adjusted = matched(V, change, target)
        vals = []
        for seed in SEEDS:
            np.random.seed(seed)
            vals.append(train(f"{row} {desc[:10]}", ZeroX(V, **cfg), train_data, val_data, seed, LR))
        n = ZeroX(V, **cfg).num_parameters()
        results.append((row, desc + (f"(MLP 宽度调为 {cfg['mlp_hidden']} 以对齐参数)" if adjusted else ""),
                        n, np.mean(vals), np.std(vals), vals))
        print(f"  => {row} {desc}: {np.mean(vals):.4f} ± {np.std(vals):.4f}", flush=True)

    base, base_std = results[0][3], results[0][4]
    lines = ["# ZeroPoint 消融实验(仿论文表 3)\n",
             f"每行只改一处,其余和 base 相同。{ITERS} 步,lr {LR},种子 {SEEDS},其余训练设置同 [`compare.md`](compare.md)。",
             "参数量和 base 差超过 1% 的变体,自动调整 MLP 宽度对齐参数量。",
             "Δ = 该行验证 loss 均值 − base 均值(**正数表示变差**)。\n",
             f"> 判断标准:base 的种子间标准差是 {base_std:.4f}。|Δ| 小于约 2 倍标准差({2 * base_std:.4f})的差别**不能算数**。\n",
             "| | 改动 | 参数量 | 验证 loss | Δ | 结论 |", "|---|---|---|---|---|---|"]
    for row, desc, n, mean, std, _ in results:
        if row == "base":
            lines.append(f"| {row} | {desc} | {n:,} | {mean:.4f} ± {std:.4f} | — | — |")
            continue
        d = mean - base
        noise = 2 * max(base_std, std)
        if noise == 0:
            verdict = "只有 1 个种子,无法判断"
        elif abs(d) < noise:
            verdict = "差别不显著"
        else:
            verdict = "变差:这个部件有用" if d > 0 else "变好:这个部件可能多余"
        lines.append(f"| {row} | {desc} | {n:,} | {mean:.4f} ± {std:.4f} | {d:+.4f} | {verdict} |")
    save_result("ablation", lines)


if __name__ == "__main__":
    main()