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()
|