Alben
一个以作者名字命名的仓库,目标是复刻 TypeSafe System One(Jev)的全部功能。
先读这一段(诚实声明)
默认产物已经是「通用产物」(artifacts/alben-scorer.pt,1,882 集合 / 56,160 条)。
出厂验收(第 39 轮,走真实服务路径)
| 组 | 实测 | 出厂线 | 结果 |
|---|---|---|---|
| MASSIVE intent en-US(60 意图,随机 0.0303) | 0.6975 | 0.60 | PASS |
| MASSIVE intent 其他 9 语言 | 0.3883 | 0.30 | PASS |
| BANKING77(77 类,随机 0.05) | 0.8817 | 0.70 | PASS |
| SST-5(5 级有序,随机 0.20) | 0.3133 | 0.25 | PASS |
| 四条干净尺子(平均 / 最低) | 0.6465 / 0.6178 | 0.60 / 0.58 | PASS |
| 官方 Python SDK 5/5 · 官方 JS SDK 30/30 | 全通过 | 全通过 | PASS |
测试:常规 197 passed / 三 E2E 全开 204 passed,零跳过。
必须一起读的限定语: 「通用」现在的准确含义是【见过同类标签、换文本 / 换语言】, 不是【换全新维度】—— 后者需示例微调(
POST /admin/teach,实测 0.49 → 0.99)。 完整边界见 通用化结论.md 与 能力边界.md。
能力边界(一句话版)
| 场景 | 能否用 | 实测 |
|---|---|---|
| 契约层:任意 criteria / 255 选项 / 官方 SDK | ✅ 完整 | F1–F24 24/24 |
| 同类维度、新文本 | ✅ 可用 | 干净尺子 0.62–0.71 |
| 同类维度、新语言 | ✅ 可用 | 非英语 0.0744 → 0.3883 |
| 换全新维度 | ⚠️ 几乎不行 | 跨族 0.3150 vs 随机 0.3211 |
| 换全新维度 + 几十条示例 | ✅ 能教会 | /admin/teach:0.4917 → 0.9917 |
✅ 已有:JS SDK(sdk/js/,零依赖 fetch) ·
官方 Python SDK 原样可用(F21,5/5) · 官方 JS SDK 原样可用(F22,30/30)。
⬜ 仍没有(不在 F1–F24 范围内,是官方也有的服务端能力,非契约复刻项):
限流(429)· 计费 · 多 key 管理 · sdk/python/ 自研 Python 客户端
(A4 设计里有,未实现 —— 但 F21「官方 Python SDK 原样可用」已完成,
这意味着不需要自研客户端也能用)。
已完成的研究是一套可复现的实验和基于实测的事实基线。这些实验得出了一些与直觉相反的结论, 其中四条推翻了我们自己在更早阶段的判断 —— 详见「十轮实验」。
最重要的一条:
在【冻结的小 encoder + 监督头】架构下,无法达成 Jev 的核心能力「运行时任意 criteria」。 但【训练 encoder】可以 —— 已实测有效:未见 criteria 准确率 0.2853 → 0.8000(随机基线 0.2920,即 2.74×)。 瓶颈已定位:不是架构形状,是【训练配置】与【criteria 集合的广度】。
★★ 上面这段是早期结论,第 30–32 轮把它精确化了(请以下面为准):
- 早期那个 0.80 是在同一个维度族的留出上测的 —— 它是真的,但它不代表能处理新维度。
- 换到没训过的维度族:0.3150 vs 随机 0.3211 → 等于随机(干净跨族 4183 条)。
- 第 30 轮:合成 150 个维度训、留出没见过组合 → 0.5936(冻编码器就已经 0.5036)。
- 第 31 轮:留出整根全新语义轴 → 0-shot 0.4961(冻编码器 0.4789); 1-shot 0.6018;4-shot 0.8867。
- 第 32 轮:训练轴 2→4→7 根,新轴 0-shot 0.4898/0.4826/0.4961 —— 加轴不涨。
→ 结论:Alben 学的是【维度族内的语义匹配】,不是【任意新维度的推理】。 新维度的正确用法是【少量示例微调】(
teach_alben.py,实测 0.49 → 0.99), 不是指望它自己猜出来。
最终目标
原话(作者):
「我想做个和自己名字命名的仓库,需要复刻全部的 jev 的功能,仓库就叫 Alben」
「全部功能」的操作性定义 = F1–F24 清单(见 design/总设计-Alben.md §1.2)。
F1–F24 状态(诚实版)
| 状态 | 数量 |
|---|---|
| ✅ 已复刻 / 已裁定 | 24 / 24 |
| ⚠️ 未完成 | 0 |
★ F22 是最后一项,第 23 轮完成 —— 用【官方】
@typesafe-ai/sdk@0.6.0原样打 Alben, 30/30 断言通过,并抓出 3 个真实契约 bug(见下文「F22 完成」一节)。
| # | 功能 | 状态 |
|---|---|---|
| F7 | 运行时任意 criteria | ✅ 契约已实现(255 选项、任意判据描述都接受并作答)。⚠️ 能力限定:训练过的维度族内 模型−多数类 +0.20~+0.34;没训过的维度族 ≈ 随机(第 30–32 轮)。 |
| F8 | 255 选项不截断 | ✅ 已实现:测试断言 255 个选项全部被打分 |
| F12 | Score 置信度口径 | ✅ 已实现:用归一化峰度(与 Choice 同口径),响应含 legend + confidence |
| F22 | JS SDK | ✅ 已完成(官方 SDK 30/30,见下文) |
「已复刻」需要澄清:现有 legacy/(原 jev/)复刻的是官方 API 的形状,不是能力。
它是固定问题分类器(遇到没见过的 question → 422),而官方核心能力是运行时任意 criteria。
实现(第 10 轮)
alben 包已实现并通过端到端验收。 完整状态见 实现状态.md。
legacy(原 jev/) |
alben | |
|---|---|---|
| 复刻的是 | API 的形状 | API 的能力 |
| 未见的 question | 422 | 正常作答 |
| 选项来源 | 训练时固定集合 | 运行时请求 |
端到端验收(走 alben.Runtime + alben.server 的真实服务路径):
⚠️ 下面这一栏是第 10 轮的旧成绩,保留作历史记录。 它当时用的是有重叠的留出集(tpl 9.9% / free 11.1% / diverse 9.8% / real 36.8%), 第 28 轮修掉重叠与占位问题后,干净成绩见本页开头。
未见 criteria 准确率 = 0.8055 (旧,含 10~37% 文本重叠)
随机基线 = 0.2925
相对随机 = 2.75x
与实验脚本的 0.8020 一致 → 包没有引入额外损失(这条结论仍然成立)。
发现并修复了一个真实缺陷:置信度未标定
端到端准确率 0.8055,但平均置信度 0.8900 → 过度自信。ECE(10箱) = 0.1100。
用温度缩放修复(在留出集的一半上拟合,另一半评估):
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 准确率 | 0.8055 | 0.8055(不变 ✓) |
| 平均置信 | 0.8900 | 0.7576 |
| ECE (10 箱) | 0.1100 | 0.0604 |
| Brier | 0.1458 | 0.1243 |
★ 温度缩放只改置信度,不改 argmax —— 准确率不变这一点在脚本里显式断言。 修复后置信度是单调可信的信号:最低箱实际 0.40 → 最高箱 0.958。
契约实现:实测再次胜过文档
读官方 live 文档(docs.typesafe.ai/api.md)后发现 4 处文档与实测冲突:
| # | 文档说 | 实测 | 处理 |
|---|---|---|---|
| 1 | 缺 key → 401 | 403 | 按实测 |
| 2 | Score 无 confidence | 有 confidence + legend | 补上 |
| 3 | Noul 是 yes/no | noul 是 0..1 浮点数 |
返回浮点,不是布尔 |
| 4 | — | Score criteria ≥2 且 ≤10 级 | 加校验 |
另修正 3 处早期实现错误:概率精度 3→2 位;/v1/models 用 data→models;Noul 布尔→浮点。
tests/ 50 个测试全通过,每个对应一条实测契约事实或一个已修的真实缺陷。
★★★ 第 13 轮:换机 + 三个基础设施缺陷(其中一个是真科学发现)
第 13 轮不是实验轮,而是「换一台电脑继续」时暴露的三个缺陷。 其中第三个不是环境问题,是真正影响所有结论的尺子问题。
缺陷 ① 15 份重复的 API key 读取代码,且都在模块级
新机上 pytest 挂 6 个测试:AttributeError: 'NoneType' object has no attribute 'group'。
根因:15 个脚本各自写了同一行模块级代码读凭证。新机凭证里没有
TYPESAFE_API_KEY → re.search 返回 None → 一 import 就炸。
这是两个问题叠在一起:模块级执行(而训练/评测/服务根本不需要 key)
- 15 份拷贝(方法学陷阱 ⑤「一个问题两套策略,本身就是缺陷源」)。
修复:corpus/apikey.py 单点定义 + LazyHeaders(只在真发请求时读凭证)。
调用处一个字不改:HDR = lazy_headers()。自动迁移 15 个文件,全部成功。
缺陷 ② 仓库里没有任何依赖清单
git ls-files 确认:没有 requirements.txt / pyproject.toml。
换机只能照版本表手抄,而手抄必漏「torch 必须带 index-url」。
修复:requirements.txt + requirements-torch.txt(torch 单独)。
★ 为什么 torch 必须单独:PyPI 上的 torch 默认是 CPU 版。 当普通依赖写,pip 拉 CPU 版 →
cuda.is_available()为False→ 训练脚本自动回退 CPU,不报错,只是慢 8 倍。这是「静默降级」。
★★★ 缺陷 ③ 四个数据集之间互相泄漏 9~31%
第 12 轮已经做过「训练 vs 各自留出」的去泄漏,但四个数据集彼此之间没有做。 实测把同一批数据按同族切分后:
| 尺子的留出集 | 与哪个训练侧重叠 | 重叠率 |
|---|---|---|
tpl 留出 |
diverse 训练 |
29.6% |
free 留出 |
diverse 训练 |
31.1% |
real 留出 |
diverse 训练 |
29.2% |
| 其余组合 | — | 9~11% |
后果:在「A/B/C/D 逐步加数据」的消融里, 配置 D 训练时见过配置 A/B/C 的留出文本 —— 「加数据带来的提升」里混进了「泄漏带来的虚高」。
修复:corpus/samefam_joint.py 做联合去泄漏:
汇总所有留出集的文本作为全局禁入集,从所有训练侧删掉,并断言
任意留出 × 任意训练 = 0 重叠。删了 tpl 671 / free 645 / diverse 1279 / real 668 条。
★ 为什么必须「联合」:各自去泄漏只能保证「留出集不在自己的训练集里」, 但 A 配置的留出集仍然是 B 配置训练数据的子集 —— 配置之间的比较仍不公平。
★ 这是本轮唯一一次「先停实验、再修尺子」:第一次跑消融时我写了个泄漏检查, 结果 31.1% —— 立刻 kill 掉正在跑的消融(已跑完配置 A),修完重跑。 一个正在产出的实验,如果尺子是歪的,跑完也是废的。
★★★ 第 13 轮结果:每加一个分布,就在那个分布上大幅提升
尺子:同族留出 + 联合去泄漏(任意留出 × 任意训练 = 0 重叠)。
| 配置 | n | tpl | free | diverse | real |
|---|---|---|---|---|---|
| A) 只 tpl | 4,745 | 0.7566 | 0.3873 | 0.3641 | 0.4076 |
| B) tpl+free | 9,356 | 0.7558 | 0.6835 | 0.3944 | 0.4472 |
| C) tpl+free+diverse | 24,291 | 0.7515 | 0.6678 | 0.7787 | 0.4625 |
| D) 全部 | 29,383 | 0.7573 | 0.6861 | 0.7846 | 0.8257 |
| 随机基线 | — | 0.3625 | 0.3423 | 0.3640 | 0.3657 |
配对自助法:A→B 在 free 上 +0.2962 ★;B→C 在 diverse 上 +0.3843 ★; C→D 在 real 上 +0.3632 ★。而每次对其他分布都不显著(−0.016 ~ +0.018)。
每加一个分布的数据,就在那个分布上大幅提升(+0.30 ~ +0.38),对其他分布几乎无代价。 模型能同时装下多个分布。
★★★ 但必须同时给出【诚实基线】
「相对随机 X 倍」是被高估的对照。 真正的诚实对照是族先验基线 (只用该配置训练侧的族标签分布)。
| 配置 | 尺子 | 随机 | 多数类(作弊) | 族先验(诚实) | 模型 | 模型−随机 | 模型−先验 |
|---|---|---|---|---|---|---|---|
| A | tpl | 0.3625 | 0.7180 | 0.6746 | 0.7566 | +0.3941 | +0.0820 |
| A | diverse | 0.3638 | 0.7916 | 0.3638 | 0.3641 | +0.0003 | +0.0003 |
| B | free | 0.3425 | 0.6763 | 0.6245 | 0.6835 | +0.3410 | +0.0590 |
| C | diverse | 0.3638 | 0.7916 | 0.6332 | 0.7787 | +0.4148 | +0.1455 |
| D | real | 0.3657 | 0.7563 | 0.7417 | 0.8257 | +0.4600 | +0.0840 |
★★★ 诚实的结论:模型真正「从文本里读出来」的增量是 +0.04 ~ +0.15, 不是「2.4 倍」。
大部分表观准确率来自标签偏斜(族先验 0.62~0.74), 而「相对随机」把这个偏斜算成了模型的功劳。
最诚实的一行:配置 A 在
diverse尺子上,模型 0.3641 / 先验 0.3638 → +0.0003。 那是模型面对没见过的族时的真实水平 —— 和随机猜没有区别。 这与第 12 轮的发现一致:模型不做跨族迁移。
本仓库历史上所有「相对随机 X 倍」都要按这个口径重新理解。 以后报成绩必须两个数一起给。
★★★ 第 14 轮:标签平衡的尺子 —— 上面那个悲观解读被推翻了
第 13 轮的「诚实基线」还留着一个漏洞:族先验基线依赖「模型能估准先验」这个假设。 最干净的办法是让先验基线 = 随机:对留出集做标签平衡下采样, 把每个 criteria 集合的每个标签压到与最少标签持平。
平衡效果:多数类从 0.68~0.79 压到 0.39~0.43。
平衡尺子上的结果:
| 配置 | n | tpl | free | diverse | real |
|---|---|---|---|---|---|
| A) 只 tpl | 4,745 | 0.5988 | 0.3692 | 0.3966 | 0.4174 |
| B) tpl+free | 9,356 | 0.6047 | 0.6141 | 0.4147 | 0.4427 |
| C) +diverse | 24,291 | 0.6145 | 0.5733 | 0.5991 | 0.4862 |
| D) 全部 | 29,383 | 0.6184 | 0.5955 | 0.6215 | 0.6583 |
| 随机 | — | 0.3593 | 0.3342 | 0.3621 | 0.3646 |
| 族先验 | — | 0.4167 | 0.3860 | 0.4188 | 0.4010 |
配对自助法:B→C diverse +0.1844 ★ · C→D real +0.1720 ★ · A→B free +0.2449 ★
★★★ 两把尺子,两个结论 —— 都对,但回答不同问题
| 尺子 | 模型(tpl, 配置A) | 诚实基线 | 模型−基线 |
|---|---|---|---|
| 自然(第 13 轮) | 0.7566 | 族先验 0.6746 | +0.0820 |
| 平衡(第 14 轮) | 0.5988 | 族先验 0.4167 | +0.1822 |
第 13 轮那句「模型真正读出来的只有 +0.04~+0.15」是被自然分布【压低】的。 自然分布下族先验本身就有 0.67,先验已经很强,文本没多少可加的。 平衡之后先验被压到 0.42,模型仍有 0.60 —— 增量 +0.18。
- 自然尺子问:
在偏斜的真实分布上,模型比"猜先验"强多少?→ +0.08- 平衡尺子问:
把偏斜这条捷径堵掉后,模型还剩多少能力?→ +0.18边际增量小 ≠ 能力弱。要分清「基线太强」与「模型太弱」。
平衡尺子还修正了「跨族迁移」的结论
| 配置 | 未训练的尺子 | 模型 | 随机 | 增量 |
|---|---|---|---|---|
| A | free | 0.3692 | 0.3342 | +0.0350 |
| A | diverse | 0.3966 | 0.3621 | +0.0345 |
| A | real | 0.4174 | 0.3646 | +0.0528 |
第 12 轮「模型不做跨族迁移」应修正为:跨族有【微弱】正向迁移(+0.03 ~ +0.08), 远低于同族(+0.18 ~ +0.26),但不是零。 第 13 轮看到的「+0.0003」是自然分布下先验恰好等于随机的巧合。
★★★ 第 15 轮:编码器容量 —— 一个【否定】结果
第 14 轮证明模型确实在读文本,所以下一步是把能力做强。
最可疑的瓶颈是编码器:当前 bge-small-zh-v1.5 只有 4 层 / 512 维 / 24M 参数。
新机 HuggingFace 可达(原机不可达),所以本轮下载了
bge-base-zh-v1.5(12 层 / 768 维 / 102.5M 参数,4.25 倍大)。
| 编码器 | 参数量 | tpl | free | diverse | real | 耗时 |
|---|---|---|---|---|---|---|
| small | 24.1M | 0.6184 | 0.5955 | 0.6215 | 0.6583 | 1105s |
| base | 102.5M | 0.6086 | 0.5622 | 0.6002 | 0.6766 | 3123s |
配对自助法:四个尺子 全都不显著(Δ = −0.033 ~ +0.018,CI 全部跨 0)。
★★★ 结论:编码器大 4.25 倍,没有任何一个尺子显著改善,三个方向甚至是负的。 瓶颈不在编码器容量。
与第 8 轮一致(当时测网络形状 S2/S3/S4,差异 ≤0.02)—— 表征容量的方向已被两次独立实验排除。
这个否定结果为什么可信:train loss 已趋近 0(0.0004/0.0002)不是欠训练;
base 耗时是 small 的 2.8 倍确认换了模型;四个尺子方向不一致;
CI 宽度 ±0.030.05,而第 14 轮的真实增量是 +0.18+0.26 —— 有真实提升一定测得出来。
陷阱 ⑯(新增):换更大模型之前,先确认瓶颈在容量。 「更大模型更好」是默认假设,需要验证。验证成本(409 MB + 52 分钟)远低于盲目投入。
★★★ 第 16 轮:硬负例 —— 一个我自己造的 bug + 一次欠训练误判
动机
第 15 轮排除了编码器容量。剩下最可疑的是训练目标太容易: 当前 InfoNCE 的负例只有同一集合内的其他选项(平均 2.9 个), 模型只要「认出这是哪个集合、挑最常见的」就能把 loss 压到 0。
第一次结果:全面下降 —— 但结论是错的
| 负例池 | 池大小 | tpl | free | diverse | real | train loss |
|---|---|---|---|---|---|---|
| 组内 | 2.9 | 0.6184 | 0.5955 | 0.6215 | 0.6583 | 0.0004 |
| 族内池 | 19.0 | 0.5636 | 0.5102 | 0.5618 | 0.6078 | 0.4799 |
「模型连训练集都拟合不了」—— 这个信号必须先怀疑实验本身。
★ 我先做了一个错误的推断,被自己的验证脚本否定
我推断:族内池里混进了与正例文本完全相同的负例(任务逻辑矛盾)。
依据是 _diag_hardneg.py 的数字:正例与族内负例撞车 17.0%、纯描述撞车 79.4%。
但这是推断。 我写了 _diag_pools.py 直接数实际候选列表:
| 检查 | 结果 |
|---|---|
group / family / global 池的重复条数 |
0 / 0 / 0 |
| 正例出现在候选列表负例部分的集合 | 0 / 849 |
推断错了。
build_pools本来就有extra = [e for e in extra if e not in set(opts)], 已把撞车负例全部剔除。那 17% / 79% 是另一个口径(没施加这个过滤)的数字。★ 陷阱 ⑰:一个看起来能解释现象的数字,必须确认它描述的是【实际生效的那份数据】。 差点给出一个完全错误的原因。
真正的原因:欠训练
| epoch | train loss |
|---|---|
| 16 | 2.3073 |
| 32 | 0.9282 |
| 48 | 0.0455 |
| 64 | 0.0158 |
32 epoch 时 loss 还有 0.93,48 epoch 就掉到 0.05。 第一版在 32 epoch 停下看到 0.48 就下结论「硬负例更差」—— 那是欠训练。 第 16 轮第一版结论作废(VOID)。
★ 这是第 7 轮教训的复发:第 7 轮(数据量)与第 16 轮(硬负例) 在同一个坑里各摔了一次,形态完全相同:loss 没降下去 → 误判为「方法无效」。
防复发措施:任何训练实验,报告准确率之前先报告 loss 收敛曲线; 若 loss 仍在下降,准确率数字一律不得用于比较。
修复:epoch 数按候选池大小缩放(2.9→32ep / 19→96ep / 38→128ep), 保证每个配置都训到收敛再比。
★★★★ 独立复核(第 14–17 轮)—— 三个发现
一个独立 teammate(verifier)换【另一条实现路径】重算了第 14–17 轮的核心数字。 它不调用、不复制 round14-17 的任何实现:自己写切分、自己写平衡(确定性排序而非随机洗牌)、 自己写训练循环、自己写配对自助法,并加了 Holm–Bonferroni 多重比较校正。 报告:
corpus/_verify_report.md。
发现 ①:第 16 轮的因果结论【不成立】
verifier 复刻了负例池的构造(只构造、不训练),逐集合审计候选列表:
| 模式 | 池大小 | 额外候选带别的问题前缀 | 描述与本集合某选项逐字相同 | 碰撞的是该行正确答案 |
|---|---|---|---|---|
| group | 2.9 | — | 0 | 0 |
| family | 19.0 | 100% | 62.4% | 36.3% |
| global | 38.1 | 100% | 1.1% | 0.6% |
两个构造伪影:
- 前缀线索 ——
option_texts把「问题 + 选项描述」拼成一条文本。family/global 池把 兄弟集合的整串搬过来,所以外来候选带着别的问题前缀;而评测时每个候选都带本行自己的问题。 训练时「看到陌生问题前缀」是一个免费的、评测时不可用的判别线索。 - 描述级碰撞 —— 去重按整串做,「别的问题 + 相同描述」能存活。 family 池里 36.3% 的集合中,一个与正确答案逐字相同的文本被当作负例 —— loss 在主动排斥正确答案。
family 池相对 group 池同时改变了三件事:(a) 负例数量、(b) 前缀线索、(c) 正确答案被排斥。 三者互相混淆,−0.11~−0.18 的下降【不能】归因于「跨判据负例有害」。
所以本文档上面那句「负例必须来自同一判据空间」降级为: 「未被证实,也无法据此证伪」。
★ 一个讽刺的细节:仓库里有
round16_ablation.py(前缀统一 + 描述级去重), 它的文档串里就提到了 36.3% 这个数 —— 当时的我已经意识到了问题。 但它从未产出 JSON,也从未被运行。「写了修正版但没跑」等于没写。
发现 ②:第 14 轮「比随机高 +0.26」被【系统性高估 0.015~0.018】
口径混用:基线的 random 是按集合取 1/n 再宏平均,而模型准确率是按行平均(micro)。
| 尺子 | 随机(宏) | 随机(按行) | 模型 | 模型−随机(宏) | 模型−随机(按行) |
|---|---|---|---|---|---|
| tpl | 0.3593 | 0.3768 | 0.6184 | +0.2591 | +0.2416 |
| diverse | 0.3621 | 0.3775 | 0.6215 | +0.2595 | +0.2440 |
tpl / diverse 的「+0.26」应读作「+0.24」。 陷阱 ⑳:宏平均与微平均不能混用 —— 基线按集合算、模型按行算,就是不可比的。
发现 ③:四个轮的「对照」是【同一份预测数组】
round14['D']、round15['small']、round16['group']、round17['自然']
逐元素完全相同。这是合理复用(同一配置只训一次),但
4 个 JSON 看起来像独立实验,引用时必须说明是同一次运行;
且第 15/16/17 轮都没有重跑 baseline,第 17 轮的「自然」基线无法反映随机性。
复核确认成立的部分
| 结论 | 判定 |
|---|---|
| 第 14 轮随机基线 / 诚实族先验 | 精确复现(差 0.0000) |
| 第 15 轮 base vs small 四尺子全不显著 | 复现,Holm 校正后仍 4/4 |
| 第 16 轮数字(8/8 显著) | 复现(但解释不成立) |
| 第 17 轮 1/8 显著 | 复现;Holm 校正后 0/8 |
build_joint「任意留出 × 任意训练 = 0」 |
自己逐对验证成立(16 对全 0,双通道) |
泄漏审计(四通道 × 16 对):text 原文 0 / sha 0 / 集合名 0;text_sha 跨数据集一致;
命名空间化后跨数据集同名 0;留出族 ⊆ 训练族成立。
另一个影响解读的观察:配置之间训练数据不独立 —— tpl×diverse 有 1,504 条文本同时出现在两个数据集的训练侧(同一文本挂不同 criteria_name)。 A/B/C/D 做配置间比较时应记住这一点。
★ 一次独立复核的价值就在这里:它没有推翻任何数字,但 推翻了一个因果结论、发现了一个口径高估、澄清了「四个实验其实是同一次运行」。 这三件事,原实现者(我)都看不出来 —— 因为我在自己的框架里。
★★★★★ 两个重大发现(第 18 轮)
发现 A:「族」这个切分单位本身是坏的
nearsyn 做「同维度近义等级硬负例」(第 16 轮建议的方向),四个尺子都没有正收益。 追问原因,发现四个数据集的族内结构完全不同(Lead 独立验证):
| 数据集 | 集合 | 族数 | 同标签对 | 描述逐字相同 | 比例 |
|---|---|---|---|---|---|
| tpl | 144 | 18 | 1,246 | 119 | 9.6% |
| free | 137 | 39 | 611 | 67 | 11.0% |
| diverse | 424 | 53 | 13 | 0 | 0.0% |
| real | 144 | 18 | 1,090 | 1,090 | 100.0% |
real/reversibility_00: easy -> "Can be undone with a single click..."
real/reversibility_01: easy -> "Can be undone with a single click..." ← 逐字相同
tpl/certainty_00: likely -> "The certainty is likely."
tpl/certainty_01: likely -> "Characterized by likely certainty." ← 不同措辞
「族」在两种意义上都不是「同一判据空间」:
- real:同一套判据的重复 → 扩池只产生伪负例
- tpl/free/diverse:同名但不同判据 → 扩池 = 跨判据干扰
所以第 16 轮的正确表述应当是: 「在 Alben 的现有语料里,'族'不构成'同一判据空间', 因此无法用它构造同判据空间的硬负例。」 —— 这是数据构造的结论,不是模型性质的结论。
★ 陷阱 ㉓:切分单位本身可能是坏的 —— 它需要被验证,不能假定。
samefam.py把reversibility_00与_01归为同族,名字上成立、语义上不成立。 而基于"族"的切分是第 12–18 轮全部实验的基础。 验证成本只有一次 20 行的脚本;六轮实验之后才发现。
发现 B:生产产物的 0.998 是【记忆】,不是能力
把新旧产物放在同一把尺子(第 14 轮标签平衡同族留出集)上:
| 产物 | tpl | free | diverse | real |
|---|---|---|---|---|
| 旧(3 数据集 / 887 集合) | 0.9980 | 0.9981 | 0.9957 | 0.4472 |
| 新(4 数据集 / 849 集合) | 0.6243 | 0.6160 | 0.6599 | 0.6881 |
旧产物的 0.998 必须被怀疑 —— 验证:
| 尺子 | 留出条数 | 落在旧产物训练集里 | 比例 |
|---|---|---|---|
| tpl | 1,372 | 1,372 | 100.0% |
| free | 1,526 | 1,526 | 100.0% |
| diverse | 4,062 | 4,062 | 100.0% |
| real | 1,440 | 721 | 50.1% |
| 新产物(四个尺子) | — | — | 0.0% |
根因:
build_joint的留出侧取自*_train*.jsonl文件本身, 而旧产物正是用这三个文件训练的 —— 对旧产物来说,「留出集」就是它的训练集子集。 那 0.998 是逐条记忆。新产物在四个尺子上都是 0% 泄漏,它的 0.62 / 0.62 / 0.66 / 0.69 才是真实的、可比的水平。
★ 陷阱 ㉔:把"生产产物"当"实验模型"评测,会踩到训练集边界不同的问题。 实验模型训练集 =
build_joint的训练侧;生产产物训练集 = 整个文件(含留出侧)。 评测已发布产物的第一件事:查它的train_sources与尺子的关系。这次是被「0.998 太漂亮」抓住的。如果旧产物得的是 0.75,这个污染可能永远不会被发现。
★ 第 14–17 轮的实验数字不受影响 —— 那些实验都是用 build_joint 的训练侧重训, verifier 已逐对验证「任意留出 × 任意训练 = 0」。 被污染的只有「拿旧生产产物去比 build_joint 留出集」这一个比较。
发现 C:四把「尺子」量的根本不是同一件事
由发现 A 继续追问,先看每个数据集有多少真正不同的 criteria:
| 数据集 | 集合名 | 族数 | 唯一 criteria 内容 | 重复倍数 |
|---|---|---|---|---|
| tpl | 180 | 18 | 179 | 1.0× |
| free | 177 | 39 | 174 | 1.0× |
| diverse | 530 | 53 | 523 | 1.0× |
| real | 180 | 18 | 86 | 2.1× |
要害:留出集合的 criteria 内容是否在训练侧出现过?
| 数据集 | 留出集合 | 内容已见过 | 内容全新 | 已见比例 |
|---|---|---|---|---|
| tpl | 36 | 1 | 35 | 2.8% |
| free | 40 | 1 | 39 | 2.5% |
| diverse | 106 | 7 | 99 | 6.6% |
| real | 36 | 28 | 8 | 77.8% |
★★★ real 的留出集合里 77.8% 的 criteria 内容,模型在训练时【逐字见过】。 它只需把已学会的判据套到新文本 —— 那是最简单的任务。 而 tpl/free/diverse 的留出集合 93~97% 是全新 criteria 内容。
四把尺子的难度根本不同,跨尺子比较是苹果比橘子。
这解释了第 14 轮一个此前无法解释的数字:配置 D 在 real 上 0.6583(四把尺子最高)。 不是 real 学得最好,而是 real 那把尺子最容易(77.8% 是同判据换文本)。
| 结论 | 是否受影响 |
|---|---|
| 第 14 轮「模型确实读文本」(模型 − 族先验 +0.16~+0.29,12/12 显著) | 不受影响(尺子内部自比,口径一致) |
| 第 15/16/17 轮 | 不受影响(都在同一把尺子内比) |
| 「real 比别的分布学得好」这类跨尺子表述 | 需改 |
★ 陷阱 ㉕:留出集的难度必须被量化 —— 「同族」不等于「同难度」。
samefam保证了「留出的族 ⊆ 训练的族」,但没保证留出的 criteria 内容是新见过的。
修复:新增 corpus/content_split.py —— 按 criteria 内容签名切分,
断言「留出的内容在训练侧不存在」(+ 文本无重叠)。10 个测试。
产物:corpus/round19_contentsplit.py(用干净尺子重跑四配置消融)。
发现 D:换新尺子重跑,第 14 轮的结构性结论完整复现
用「内容切分 + 标签平衡」这把最严格的尺子(既要求留出内容是新判据、又无标签偏斜) 重跑四配置消融:
| 配置 | tpl | free | diverse | real |
|---|---|---|---|---|
| A) 只 tpl | .6169 | .4185 | .4260 | .4123 |
| B) tpl+free | .6250 | .6320 | .4336 | .3477 |
| C) tpl+free+diverse | .6593 | .6096 | .6065 | .4277 |
| D) A+B+C+real | .6492 | .5899 | .6113 | .6892 |
第 14 轮的三个关键效应,在两把完全不同的尺子上全部复现:
| 效应 | 旧尺子(samefam 平衡) | 新尺子(内容切分平衡) |
|---|---|---|
| 加 free → free 尺子大涨 | .3692 → .6141(+0.245) | .4185 → .6320(+0.214) |
| 加 diverse → diverse 尺子大涨 | .4147 → .5991(+0.184) | .4336 → .6065(+0.173) |
| 加 real → real 尺子大涨 | .4862 → .6583(+0.172) | .4277 → .6892(+0.262) |
| 加 free → tpl 基本不动 | +0.006 | +0.008 |
★★★ 「criteria 多样性是跨分布泛化的决定性杠杆」不依赖于尺子的选择。 换切分口径、换平衡子集、换留出集合(两者留出完全不同),效应量几乎不变。 这是对第 14 轮结论最强的一次稳健性检验。
诚实增量(模型 − 多数类)—— 对角线最大:
| 配置 | tpl | free | diverse | real |
|---|---|---|---|---|
| A) 只 tpl | +0.2424 | +0.0019 | +0.0019 | +0.0117 |
| B) tpl+free | +0.2505 | +0.2154 | +0.0095 | −0.0529 |
| C) tpl+free+diverse | +0.2848 | +0.1929 | +0.1824 | +0.0271 |
| D) A+B+C+real | +0.2747 | +0.1732 | +0.1872 | +0.2886 |
训练里包含哪个数据集,就在那个尺子上增量最大。 且 A 行是漂亮的负对照:只训 tpl 时 tpl +0.24,而 free/diverse/real 只有 +0.002 / +0.002 / +0.012 —— 跨判据泛化确实几乎不存在(与第 13 轮「跨维度族不可迁移」一致)。
★ 发现 C 的正确表述:跨尺子的绝对值不可比;但同一把尺子内的配置比较完全可靠。 而本项目几乎所有结论都是同一把尺子内的比较。
发现 E:模型学的是判据匹配,不是记住答案分布
方法:用已发布的生产产物直接分解(无需训练,两把尺子都标签平衡)。
| 尺子 | 定义 | 准确率 | 多数类 | 模型−多数类 |
|---|---|---|---|---|
| Ruler S | 判据内容已见 + 文本全新 | 0.6840 | 0.4629 | +0.2211 |
| Ruler F | 判据内容全新 + 文本全新 | 0.6348 | 0.4150 | +0.2197 |
逐行配对自助法(模型 vs 诚实族先验):S +0.2400 [+0.2125,+0.2676] · F +0.2054 [+0.1794,+0.2325],p<0.0001,两把都显著。
★★★ 以「模型 − 多数类」这个同口径增量看, 在【全新判据】上与【已见判据】上几乎完全相同(差 0.0014)。
模型不是靠记住判据集合的答案分布 —— 它在没见过的判据上同样有效。
那 0.0492 的裸准确率差来自两把尺子的【多数类基线不同】(0.4629 vs 0.4150), 不是模型能力的差异。★ 比较不同尺子的裸准确率是错的,必须减去各自的多数类基线。
★ 与第 19 轮 A 行不矛盾 —— 需要精确区分
| 陈述 | 含义 |
|---|---|
| 第 19 轮 A 行(只训 tpl,free/diverse/real 增量 +0.002/+0.002/+0.012) | 训练里没有那个数据集/维度族 → 跨维度族泛化≈0 |
| 第 20 轮 Ruler F(判据全新,增量 +0.22) | 训练里没有那个具体判据内容,但同属已训练的维度族 → 泛化成立 |
★★★ 合起来的精确结论: 模型的泛化能力是【维度族内】的,不是【跨维度族】的。
它能在同一维度族里对没见过的具体判据做零样本匹配(+0.22); 但换一个维度族就完全做不到(+0.002)。 这是第 13 轮「跨集合可迁移、跨维度族不可迁移」的第三种独立验证。
实际意义:
| 问题 | 答案 |
|---|---|
| 部署新 criteria 集合(同维度族)要重训吗? | 不需要(零样本 +0.22) |
| 部署全新维度族要重训吗? | 需要(跨族 ≈0) |
| 生产产物的能力是真的吗? | 是(模型−诚实族先验 +0.24/+0.21,p<0.0001) |
★★ F22 完成:官方 JS SDK 原样打 Alben,抓出 3 个真 bug
F22 的验收标准不是「我写了一个 JS 客户端」,而是「官方 SDK 能原样用」 —— 与 F21 对称。
npm install @typesafe-ai/sdk@0.6.0,用官方 TypeSafeClient + noul()/choice()/score() helper 原样打 Alben:
结果:30 / 30 断言通过
| 组 | 断言数 |
|---|---|
client.models.list() 解析 |
5 ✅ |
| 三种原语(官方 helper 构造) | 14 ✅ |
| 255 选项不截断 | 1 ✅ |
| 错误分流(403/401) | 4 ✅ |
官方 ModelMetadata 三字段 |
4 ✅ |
| 未知 model → 400 | 2 ✅ |
别名 jev-latest 被接受 |
1 ✅ |
★★★ 官方 SDK 抓出三个真实 bug
| # | bug | 后果 |
|---|---|---|
| 1 | /v1/models 返回 {id, encoder} 而非官方 {name, description, release_date} |
官方 SDK 拿到三个 undefined —— JS 动态类型不抛错,静默错 |
| 2 | /v1/models 完全没有鉴权检查 |
官方 security=[{"HTTPBearer": []}] 要求鉴权;Alben 此前任何人可列模型 |
| 3 | model 字段完全不解析 |
官方语义:未知名 → 400 Unknown model: <x>;别名 → 响应报具体版本 |
★★★ 这三个 bug 都是【官方 SDK 原样打一遍】才暴露的。 自己写的 JS 客户端(第 18 轮)没有发现它们 —— 因为它按我理解的契约写的。
这正是 F21/F22 作为「最强验收手段」的价值:它不接受你的理解,只接受官方实现。
★ 与第 8 个硬发现完全同构:A5 用官方 Python SDK 打旧
jev/抓到 2 个 bug; 现在同一招在 JS SDK 上又抓出 3 个。
修复:alben/contracts.py(新增 resolve_model/model_card)· alben/server.py(models 加鉴权 + 官方三字段 + model 解析)· sdk/js/index.d.ts · sdk/js/e2e.test.mjs · 新增 tests/test_official_js_sdk.py。
★ 陷阱 ㉗:自己写的客户端不能验证契约 —— 它验证的是「我的理解是否自洽」。 只有官方实现能验证契约。
★★ 第 27 轮:部署产物的标定温度一直是错的
触发:核对重训后的生产产物时发现三处温度不一致。
缺陷 A:拟合出来的温度【从未生效】
| 位置 | 值 |
|---|---|
alben-scorer.pt 的 meta.calibration_temperature |
5.2976 ← Scorer 读这个 |
calibration.json 的 learned_temperature |
6.2246 ← 新拟合的 |
根因:fit_calibration.py 只写 calibration.json,从不回写 .pt 的 meta。
部署的模型一直用着【为旧模型拟合的温度】。
缺陷 B:拟合目标与报告指标【不一致】
用 NLL 拟合 T,但置信度口径是 peak_confidence(非线性),对外报告的是 ECE。
| T | 来源 | NLL | ECE |
|---|---|---|---|
| 3.4 | ECE 最优 | 1.0455 | 0.1125 |
| 5.2976 | 当前生效 | 0.9985 | 0.1980 |
| 6.2246 | NLL 最优(新拟合) | 0.9959 | 0.2357 |
★★★ 两个缺陷叠加的结果是「碰巧没那么糟」:旧温度恰好比新拟合的更接近 ECE 最优。 如果缺陷 A 单独修好,ECE 反而会从 0.1980 恶化到 0.2357。
修复后(实测)
| T 的来源 | 准确率 | 平均置信 | 差 | ECE |
|---|---|---|---|---|
| 未标定 | 0.4883 | 0.7541 | +0.2658 | 0.3044 |
| NLL 最优(旧做法) | 0.4883 | 0.2486 | −0.2397 | 0.2399 |
| ECE 最优(新) | 0.4883 | 0.3823 | −0.1060 | 0.1174 |
★ ECE 0.3044 → 0.1174(降低 61.4%),准确率不变。
★★ 用交叉验证决定「按哪个目标选 T」
评估集上 Brier 最优(0.1081)比 ECE 最优(0.1174)更好 —— 但用评估集选就是泄漏。 正确做法:在拟合集内部再切 5 折来选准则:
| 准则 | 平均留出 ECE | 标准差 | 平均 T |
|---|---|---|---|
| nll | 0.2351 | 0.0192 | 6.23 |
| ece | 0.1379 | 0.0193 | 3.73 |
| brier | 0.1391 | 0.0139 | 3.27 |
1. NLL 比 ECE 差 0.0972 —— 证实缺陷 B 是真的。 2. ECE 与 Brier 的差异(0.0012)小于它们各自的标准差(±0.019)—— 统计上不可区分。 3. 所以评估集上「Brier 更好」是噪声;选 ECE 最优是安全的。
★ 陷阱 ㉝:用评估集选择超参数(哪怕"看起来更优的那个")就是泄漏。 ★ 陷阱 ㉞:两个指标的差异必须与它们的【标准差】比较 —— 差 0.0012 vs 标准差 0.019,这个"差异"比噪声小一个数量级。
防再犯:新增 tests/test_calibration_consistency.py(4 测试)——
守 .pt meta 与 calibration.json 一致、Scorer 实际生效、
准确率不变/ECE 下降、以及选择准则与候选对照必须落盘。
train_alben.py 的常量改为从 calibration.json 读取(单一数据源)。
★★ 第 28 轮:端到端评测路径的三个真实缺陷
缺陷 C:评测用了【占位问题】
evaluate_alben.py 写死 "text": "Pick one.",但训练时 option_text 拼的是该集合自己的 instructions —— 问题通道不一致。
| 留出集 | 真实问题 | 占位问题 | 差 |
|---|---|---|---|
| free_held | 0.6725 | 0.6925 | −0.0200 |
| diverse_held | 0.3175 | 0.4250 | −0.1075 |
差 0.1075,而且方向是「占位问题更好」—— 这个不一致会系统性扭曲评测数字。
缺陷 D:评测没有检查留出集与训练集的重叠
| 留出集 | 与 prod_train.jsonl 重叠 |
|---|---|
原始 tpl_held.jsonl |
9.9% |
原始 free_held.jsonl |
11.1% |
原始 diverse_held.jsonl |
9.8% |
原始 real_held.jsonl |
36.8% |
build_joint 留出侧 |
0.0% |
★
evaluate_alben.py的默认--held就是corpus/*_held.jsonl—— 默认路径一直在用有 10~37% 重叠的留出集。根因:
*_held.jsonl是第 12 轮的原始留出文件,而训练集是build_joint从*_train*.jsonl切出的同族训练侧 —— 两者不是同一划分。
★★ 修正后的干净成绩(四把尺子,重叠 0% + 标签平衡 + 真实问题)
| 尺子 | 留出 | 准确率 | 随机 | 多数类 | 模型−多数类 | 重叠 |
|---|---|---|---|---|---|---|
| tpl | 511 | 0.6243 | 0.3768 | 0.4167 | +0.2076 | 0% |
| free | 539 | 0.6160 | 0.3310 | 0.3860 | +0.2299 | 0% |
| diverse | 938 | 0.6599 | 0.3775 | 0.4250 | +0.2349 | 0% |
| real | 436 | 0.6881 | 0.3677 | 0.4010 | +0.2870 | 0% |
★★★ 这是本项目第一次在【干净尺子 + 标签平衡 + 真实问题】三个条件同时满足下报出的成绩。
★ 旧口径 vs 新口径(同一模型,free 尺子):
- 旧:0.7067,重叠 11.7%,模型−多数类 = −0.0552(不如多数类!)
- 新:0.6160,重叠 0%,模型−多数类 = +0.2299(大幅优于多数类)
这个翻转完全来自【尺子是否干净、是否平衡】,与模型无关。
★ ECE 最优的温度会【过度校正】平均置信度
| T | 平均置信 | 准确率 | gap | ECE |
|---|---|---|---|---|
| 1.00 | 0.7596 | 0.4909 | +0.2687 | 0.2986 |
| 3.05 | 0.4508 | 0.4909 | −0.0401 | 0.1121 ← ECE 最优 |
| 2.70 | 0.4883 | 0.4909 | −0.0025 | 0.1258 ← gap=0 |
★ 这是 peak 口径非线性的必然结果,不是 bug。 报 ECE 用 ECE 最优;要平均置信无偏用 gap=0。
fit_calibration.py现在同时报出 gap=0 候选并把权衡写进known_tradeoff。
★ 陷阱 ㉟:评测脚本必须走【与训练完全相同的输入构造路径】。 ★ 陷阱 ㊱:
--held指向哪个文件,必须验证它与训练集的关系。 任何评测脚本都应当自动打印重叠率并在超标时警告。
验证:pytest tests/ -q → 142 passed, 7 skipped · 四把尺子重叠 全部 0.0%
★ 第 29 轮:全量测试暴露的「测试自己骗人」陷阱
把三个端到端全开跑一遍,发现 JS 端到端失败 11/17,报的是满屏 AlbenAuthError。
看起来像 SDK 坏了 —— 但不是。
根因链条
e2e.test.mjsspawn自己起服务,但不检查端口是否被占用- 8099 上有一个我手工起的服务(
--key test-key),而 e2e 用sk-e2e-key spawn因端口占用失败,但/health仍由别人的服务响应- 循环判定「服务就绪」→ 误以为那是自己的服务
- 拿错误的 key 去打它 → 401
★★★ 这不是 SDK 的 bug,是【测试骗了人】。 它把「端口冲突」伪装成「SDK 鉴权坏了」—— 如果我不追下去,会去改一个根本没坏的 SDK。
修复(两层)
| 层 | 内容 |
|---|---|
e2e.test.mjs |
检测 address already in use / EADDRINUSE → 立刻失败;/health 就绪后再用自己的 key 探一次 /v1/models,401/403 就立刻失败 |
tests/test_js_sdk.py |
改用 --port 8199,从源头避开 8099 上的手工服务 |
★ 关键:把「还没起来」与「服务不是我们的」区分开 —— 后者必须立刻失败,不能继续等 240 秒。
★★ 验证:第一次【零跳过】跑完全部测试
| 命令 | 结果 |
|---|---|
| 常规 | 160 passed, 6 skipped |
| 三个 E2E 全开 | 166 passed, 0 skipped, 0 failed |
含官方 Python SDK(5)· 官方 JS SDK(30 项断言)· 自研 JS SDK(25)。
★ 陷阱 ㊲:测试脚本必须验证「它连上的服务确实是它自己起的」。 「起服务 → 探
/health→ 认为就绪」这个模式有静默漏洞:端口被占用时/health由别人的服务响应。 正确做法:就绪后用自己的凭据探一次受保护端点,被拒就立刻失败。★ 更一般的教训:「测试失败」的第一反应不应是「被测代码坏了」,而应是「测试真的测到了我想测的东西吗」。 这与第 21 轮(消融干预了死通道)、第 23 轮(自己写的客户端验证不了契约)是同一类错误的第三次出现: 验证者本身没有被验证。
★★★ 第 30–33 轮:能力边界的最终定位(最重要的一节)
此前所有"未见 criteria 准确率"都是【同一个维度族】的留出。 第 30–33 轮把"跨维度族"这个真正的缺口补上了。
三个数字
| 情况 | 结果 |
|---|---|
| 训练过的维度族内(干净尺子) | tpl 0.6243 / free 0.6160 / diverse 0.6599 / real 0.6881,模型−多数类 +0.21~+0.29 |
| 没训过的维度族(4183 条干净跨族) | 0.3150 vs 随机 0.3211 → 等于随机 |
| 全新语义轴 + 少量示例 | 0-shot 0.4961 → 1-shot 0.6018 → 4-shot 0.8867 |
第 30 轮的陷阱:只跟随机比是不够的
留出没见过的维度组合,得到 0.5936(随机 0.4133),看起来"跨维度可迁移"。 补测冻编码器余弦基线后:
| 准确率 | |
|---|---|
| B0 随机 | 0.4133 |
| B1 冻编码器余弦(零训练) | 0.5036 |
| M 训练塔 | 0.5936 |
| 训练塔 − B1 | +0.0900 |
★ 0.5936 里有 0.5036 是 BGE 编码器自带的语义相似度。 ★ 陷阱 ㊳:报「模型比随机好」之前,先问「冻结的编码器本身是不是就够了」。 ★ 陷阱 ㊴:合成的"新维度"必须先证明措辞与训练【零交集】—— 第 30 轮第一版就栽在这:200 个维度共用 120 个 cue 短语,留出判据逐字在训练里出现过, 得到虚假的 1.0000。现在写成自断言(训练 299 种 / 留出 176 种 / 交集 0)。
第 31 轮:留出【整根全新语义轴】(最重要的一张表)
训练 7 根轴,留出 novelty / compliance / reversibility 三根整轴:
| 条件 | 准确率 | 模型−随机 | 模型−冻编码器 |
|---|---|---|---|
| B1 冻编码器(零训练) | 0.4789 | — | — |
| C0 零样本 | 0.4961 | +0.0817 | +0.0171 |
| C1 1-shot(每轴 12 条) | 0.6018 | +0.1874 | +0.1228 |
| C4 4-shot(每轴 48 条) | 0.8867 | +0.4724 | +0.4078 |
全新语义轴零样本几乎学不到(只比冻编码器高 0.017),但 4-shot 就到 0.8867。 模型有能力,缺的是"告诉它这个维度长什么样"。
★ 陷阱 ㊵:留出组合 ≠ 留出轴。 轴见过的话,测的只是"同轴换说法"。 ★ 陷阱 ㊶:few-shot 的示例集合绝不能出现在评测集里。
第 32 轮:加轴不是杠杆
训练轴 2 → 4 → 7 根,新轴 0-shot = 0.4898 / 0.4826 / 0.4961,3.5 倍只涨 +0.0062。
★ 陷阱 ㊷:「数量不够」是一个可以否证的假设,要去测它,不要靠感觉。
第 33 轮:教新维度的收益与代价
| 产物 | 训练集 | 新维度 | 四条真实尺子平均 |
|---|---|---|---|
| v1 | prod_train(849 集合) | 0.4862 | 0.6471 |
| v2 | +150 合成维度(1749 集合) | 0.5532 | — |
| v3 | +每根新轴 4 集合教学(1789 集合) | 0.9372 | 0.6255 (−0.0216) |
新维度 +0.4510,代价 −0.0216。
落地工具:teach_alben.py
热启动微调(不是从零训),实测留出新维度 0.4917 → 0.9917(12 epoch / 约 6 分钟)。
三个防自欺机制:① 按集合切留出(不按行);② 旧数据里另留一批不重放的集合当遗忘探针; ③ 同时报"微调前 / 微调后"。
★ 陷阱 ㊸:"遗忘"必须用【没重放过的】旧数据去量 —— 用重放过的测必然 1.0,等于没测。 ★ 陷阱 ㊹:热启动微调要同时报"教的能力"与"旧能力" —— 只报一边能造出任何结论。
守门测试
tests/test_round30_33_experiments.py(17 个)把上面每一条不变量钉死,包括
"加轴不是杠杆"(delta >0.05 就报错,逼着回来改文档)。
完整说明见 能力边界.md。
★★ 安全修复:仓库历史里的密钥已彻底清除(第 25 轮)
发现:design/_ref/ 下有 5 个早期探针脚本把 TYPESAFE_API_KEY 硬编码在源码里,
且从最早的第 7 轮提交起就在 git 历史里。
| 项 | 值 |
|---|---|
| 含密钥的提交 | 64 / 66 |
远端 main 是否含 |
是 |
| 仓库可见性 | 私有 |
处置:
| 步 | 动作 | 结果 |
|---|---|---|
| ① | 5 个文件改为从 corpus/apikey.py 单点解析 |
✅ 工作区已无明文密钥 |
| ② | git filter-branch --index-filter 重写全部提交 |
✅ 全历史 0 / 65 含密钥 |
| ③ | git push --force-with-lease=main:<真实远端> |
✅ 远端已覆盖 |
★ 为什么用 index-filter 而不是 tree-filter:本仓库用 Git LFS 跟踪 91 MB 权重, tree-filter 会逐个提交 checkout 触发 LFS smudge(66 × 91 MB 不可行)。 index-filter 只操作 index/blob,不 checkout、不碰 LFS。
完整性核对:文件树路径 357/357 相同 · 内容 blob 0 个不同 · LFS 对象完好 · 提交数 66→65(少的是空提交「启用 Git LFS 跟踪」,tree 与父提交逐字节相同)· 137 passed。
防再犯:新增 tests/test_no_secrets.py(4 测试),扫描工作区 + git 跟踪文件,
并自检「扫描器本身能检出植入的密钥」。
★★ 陷阱 ㉘:清理脚本的匹配式必须与【实际形态】一致。 我的第一版按
apikey-写,实际是apikey_<hex>_<hex>(下划线)。 脚本报告「已清理 0 个文件」,我差点以为干净了。 一个不检测的检测器,比没有检测器更危险 —— 它给你虚假的安全感。★ 陷阱 ㉙:批量改写脚本时,不要顺手加「看起来有用」的东西。 我只该替换一行,却加了 3 行,其中
_hdr()是死代码。★ 陷阱 ㉚:
--force-with-lease的基准是【本地跟踪引用】, 而 filter-branch 会把它一起重写 —— 所以必然因「stale info」被拒。 正确做法是显式指定真实远端 SHA。与第 21 轮那次对称:那次我设计错消融实验(换标签键 = 干预死通道),也是因为「按自己的理解设计」。 两次的解法相同:用【官方实现】而不是【我的理解】当裁判。
发现 F:模型学的是文本 ↔ 判据描述的匹配 —— 三轮消融的完整图景
用生产产物做三个干预(只动描述这一条通道,标签键对模型完全无影响):
| 轮次 | 干预 | Ruler S | Ruler F | 说明 |
|---|---|---|---|---|
| — | 原描述 | 0.6840 | 0.6348 | — |
| 第 22 轮 | 打乱描述与选项的配对 | 0.1911 | 0.2199 | 崩到低于随机 |
| 第 22 轮 | 描述逐字倒序 | 0.3680 | 0.3978 | 掉到随机 |
| 第 21 轮 | 描述换成无意义填充 | 0.3619 | 0.3537 | 掉到随机 |
| 第 21 轮 | 描述换成标签名 | 0.5542 | 0.5736 | 标签名也带信号 |
| 第 20 轮 | 判据全新(描述完整) | 0.6840 | 0.6348 | 几乎不退化 |
★★★ 打乱配对后准确率 0.684 → 0.191 —— 【低于随机】。 低于随机说明模型是主动跟着描述走的:描述被移到哪个选项它就选哪个(于是系统性选错)。 这是「配对是模型决策依据」的最强证据 —— 比"掉到随机"更强。
描述逐字倒序 → 掉到随机,说明模型依赖词序与语义,不是字符 n-gram 表面匹配。
第 20–22 轮合起来:
| 结论 | 证据 |
|---|---|
| 模型在全新判据上几乎不退化 | Ruler F 增量 +0.2197 vs Ruler S +0.2211 |
| 决策依据是描述-选项配对 | 打乱配对 → 崩到 0.19(低于随机) |
| 依赖词序/语义 | 逐字倒序 → 崩到随机 |
| 泛化是维度族内的 | 第 19 轮 A 行跨族增量 +0.002 |
★ 方法论收获:第 21 轮我设计错了实验 —— 换的是标签键, 而
option_text根本不读标签键,等于干预了一个死通道。 「干预后结果不变」有两种可能:(a) 模型不依赖它,(b) 干预没生效。必须先排除 (b)。
★ A2 待办复核完成(第 23 轮):官方 Score confidence 的真实形态
待办:「复核『15/46 个 confidence > p_max』按 (1−p_max)/(n−1) 分层」。
分层变量来历:p_max − peak = (1 − p_max)/(n − 1) —— 即「p_max 与 peak 的间距」。
分层假设【成立】
| 间距区间 | 点数 | 超 p_max | 超 p_max + 舍入 | 最大超出 |
|---|---|---|---|---|
| [0.00, 0.02) | 233 | 0 | 0 | 0.0000 |
| [0.02, 0.05) | 94 | 0 | 0 | 0.0000 |
| [0.05, 0.10) | 71 | 1 | 0 | 0.0000 |
| [0.10, 0.20) | 78 | 2 | 0 | +0.0100 |
| [0.20, 1.01) | 3 | 0 | 0 | −0.1000 |
超 p_max 的 3 个点全部落在间距 ≥ 0.05 的两桶里,且超出量全部 ≤ 一个舍入步长。 扣掉舍入后
conf > p_max= 0 / 479。
仍不可复核:「15/46」的分母 46(46 条探针原始响应从未落盘); 479 点全部 n≡4,所以「只在 n≥5 出现」这个假设本数据无法验证。 → 「15/46」这个具体比例不应继续引用。
★★★ 但分层暴露了更重要的事实
| 候选公式 | MAE | 中位数 | 最大偏差 |
|---|---|---|---|
| p_max | 0.0142 | 0.0000 | 0.4200 |
| normalized_peak | 0.0346 | 0.0133 | 0.2267 |
p_max 的 MAE 只有 0.0142,但中位偏差 = 0.0000、最大 0.42 —— 这不是「接近 p_max」,而是「多数点上恰好等于 p_max,少数点完全不同」:分段行为。
| p_max 区间 | 点数 | 平均 conf − p_max | 中位归一熵 |
|---|---|---|---|
| [0.30, 0.50) | 16 | −0.1325 | 0.7920 |
| [0.50, 0.70) | 62 | −0.0271 | 0.5535 |
| [0.85, 0.95) | 104 | −0.0071 | 0.2182 |
| [0.95, 1.01) | 231 | −0.0021 | 0.0404 |
★
conf = p_max只在【尖锐分布】上成立(p_max ≥ 0.95 的 231 个点上偏差仅 −0.002); 分布平坦时 conf 系统性低于 p_max(p_max ∈ [0.3,0.5) 时低 0.13)。「相等组 vs 不等组」(±0.01):相等组 397 点(82.9%,归一熵中位 0.121); 不等组 82 点(17.1%,归一熵中位 0.507 —— 4.2 倍)。
公式拟合 conf = a·p_max + b·归一化熵 + c:a=+0.8882, b=−0.1749, c=+0.1244,
MAE 0.0181(比纯 p_max 的 0.0142 更差) → 官方公式不是 p_max 与熵的线性组合。
★ Alben 维持
normalized_peak不变 —— 选它的依据是**「与 Choice 同尺度、可解释」**, 不是「更接近官方」。这是已知的、已公布的偏差,不是缺陷。
修正后的完整结果:负例池越大越差,每个对比都显著
| 负例池 | 池大小 | epoch | tpl | free | diverse | real | train loss |
|---|---|---|---|---|---|---|---|
| 组内(当前做法) | 2.9 | 32 | 0.6184 | 0.5955 | 0.6215 | 0.6583 | 0.0004 |
| 族内池 | 19.0 | 96 | 0.5108 | 0.4323 | 0.4733 | 0.4885 | 0.0001 |
| 全局池 | 38.1 | 128 | 0.4736 | 0.4193 | 0.4488 | 0.4954 | 0.0003 |
配对自助法:group→family 四个尺子 −0.11 ~ −0.17,全显著; group→global −0.14 ~ −0.18,全显著。
★★★ 负例池越大越差。这不是欠训练(loss 已收敛到 0.0001~0.0003)。
原因:「组内负例」看似太容易,其实给了模型一个有结构的任务 —— 同一集合里的选项是互斥的等级(
easy / costly / impossible), 模型学的是「这条文本落在该维度哪一档」。 而把不同问题的选项混进来(urgency 的选项和 sentiment 的选项同时当候选), 模型被迫分辨「这个选项属于哪个问题」—— 那是另一个任务,且会干扰原本要学的分档。正确的表述不是「硬负例没用」,而是:负例必须来自【同一个判据空间】才有意义。 想让它变难,应当加深【同维度内】的难度(
easily_reversedvsreversible_with_effort这类近义等级),而不是把别的维度拉进来。
⚠️ 第 11 轮的结论【暂时撤回】,等待第 12 轮重测
第 12 轮在扩大多样性时发现三个数据构造缺陷,它们波及第 11 轮用到的数据:
- criteria 的每个选项描述完全相同 —— 我把「问题」写进了描述位置, 模型无法区分选项(train loss 1.0169,正常 0.02 量级)
- 留出集 100% 泄漏进训练(集合名重叠 100/100)
instructions被完全忽略 —— 实测 Jev 换问题会换答案,Alben 却不会
所以下面第 11 轮的数字建立在有缺陷的数据上,必须重测后才能引用。
修复已在进行:新建 corpus/criteria_build.py 作为 criteria 的单点定义,
旧数据用 fix_free.py 原地修复(标签未变,纯结构修复)。
★ 教训(已写入方法论): 一个退化得太彻底的数字,先怀疑实验,不要接受它。 多样留出只有 0.35、恰好等于随机基线 0.3758 —— 正是这个信号引出了上面三个缺陷。
★★★ 第 11 轮(数字待重测):criteria 多样性 —— 跨分布泛化的杠杆
第 10 轮暴露了真实瓶颈:同分布 0.8055,跨分布只有 0.3715; 5 个临时真实任务期望首选命中仅 2/5。
中途我曾假设「中文 criteria 是短板」(训练数据是英文 criteria)—— 这个假设被消融实验【推翻】:EN = 2/5,ZH = 2/5,完全相同。 真正的原因是【criteria 分布不匹配】。
于是加入 177 个【自由撰写】的 criteria 集合。结果:
| 配置 | n | 模板留出 | 自由留出 |
|---|---|---|---|
| A) 360 模板(原状) | 13,129 | 0.8020 | 0.3933 |
| B) 360 模板 + 177 自由 | 20,209 | 0.7980 | 0.6956 |
| C) 只 177 自由 | 7,080 | 0.3155 | 0.7094 |
| D) 180 模板 + 177 自由 | 13,643 | 0.5940 | 0.6872 |
配对自助法:
- A→B 自由留出 Δ=+0.3022 CI [+0.2728, +0.3328] ★ 显著
- A→B 模板留出 Δ=−0.0040 CI [−0.0160, +0.0085] 不显著 ← 几乎零代价
- C→D 模板留出 Δ=+0.2785 ★ 显著,自由留出 −0.0222 不显著
★★★ 「多样性」与「数量」是两个独立杠杆,各自作用于自己那个分布。 模型能同时装下两个分布,不必二选一。
端到端(走真实服务路径):
| 留出集 | 准确率 | 相对随机 |
|---|---|---|
| 模板分布 | 0.8025 | 2.74× |
| 自由分布 | 0.7144 | 2.00× |
跨分布从 0.3933(1.27×)提升到 0.7144(2.00×)。
本轮修掉一个真实 bug:结构化 state(object/array)导致 400 —— 官方明确支持,早期实现没处理。
本轮一个失败的方向(留痕):按分布分别标定温度,加权 ECE 0.1166 比单一温度的 0.1112 更差。 原因是跨分布时模型「既不准又自信」,温度缩放修不了这个结构性问题。
十二轮实验(本仓库最有价值的部分)
每一轮都推翻了上一轮的一个假设。这些否定结论本身就是产出。
| 轮次 | 假设 | 结果 |
|---|---|---|
| 1 | 10 类固定任务能区分架构形状 | ❌ 全部 1.0000,无判别力 |
| 2 | 形状(S2/S3/S4)是瓶颈 | ❌ 换双线性/交叉仍 ≈ 随机 |
| 3 | 拼接特征够用 | ❌ 线性打分器结构上无法泛化到新选项 |
| 4 | 「同义改写 0.7242」说明学会匹配 | ❌ 那是选项向量自带的类别信息 |
| 5 | 选项多样性是瓶颈 | ❌ 40 个 criteria 集合仍 ≈ 随机(0.2650 vs 0.2552) |
| 6 | encoder 没被训练是瓶颈 | ✅ 成立(0.2853 → 0.3940) |
| 7 | 数据量是瓶颈 | ❌ 结论作废 —— 该曲线在 epochs=2 下测得,是欠训练伪影 |
| 8 | 训练配置 / 数据广度是瓶颈 | ✅ 成立(0.6337 → 0.8000) |
| 9 | 广度仍显著、深度已饱和 | ✅ 成立(同分布配对证据) |
第 8 轮:训练配置才是主因(★ 推翻第 7 轮)
第 7 轮的曲线是在 epochs=2, lr=5e-6 下测的 —— 严重欠训练。修正配置后:
| #criteria | #samples | 第 7 轮(欠训练) | 第 8 轮(修正) | 差距 |
|---|---|---|---|---|
| 20 | 1,305 | 0.4174 | 0.5413 | +0.124 |
| 80 | 5,227 | 0.5935 | 0.7207 | +0.127 |
| 180 | 11,793 | 0.6337 | 0.8000 | +0.166 |
配置:freeze=3, lr=1e-4, OneCycle, epochs=32 | 随机基线 0.2920
★ 关键:所有配置的 train loss 都降到 0.0007–0.0013(几乎完美记忆),而 acc 仍随数据上升 → 瓶颈在泛化,不在拟合能力。
第 8 轮的决定性发现:广度 > 深度
固定 180 个 criteria 集合,只改变每集合用多少条样本:
| 每集合条数 | 未见 acc | 对比 | Δ | 95% CI | 判定 |
|---|---|---|---|---|---|
| 10 | 0.7255 | — | — | — | — |
| 20 | 0.7603 | 10→20 | +0.0348 | [+0.0136, +0.0554] | ★ 显著 |
| 40 | 0.7859 | 20→40 | +0.0255 | [+0.0092, +0.0418] | ★ 显著 |
| 65 | 0.7870 | 40→65 | +0.0011 | [−0.0120, +0.0141] | 不显著 |
每集合样本数超过约 40 条后收益归零。 而「更多 criteria 集合」的边际增益在 180 处仍有 +0.0223。 → 【广度】比【深度】更值钱。
第 9 轮:广度实验(含一次设计缺陷自查)
第一版有缺陷:新增的 180 个集合来自全新的 20 个维度,而留出集是原 20 个维度的变体 → 分布不匹配。实测 new180 只有 0.3715,说明第一版看到的差距主要来自分布不匹配。
第二版(修正:训练与留出同维度,只改集合数):
| #criteria | 未见 acc | 相对随机 | 边际增益 | 配对自助法 |
|---|---|---|---|---|
| 20 | 0.5055 | 1.73× | — | — |
| 40 | 0.6255 | 2.14× | +0.1200 | ★ 显著 |
| 80 | 0.7220 | 2.47× | +0.0965 | ★ 显著 |
| 120 | 0.7585 | 2.59× | +0.0365 | ★ 显著 |
| 180 | 0.7850 | 2.68× | +0.0265 | ★ 显著 [+0.0125, +0.0405] |
每个翻倍点都配对显著,包括最后一个。边际增益在衰减,但没有饱和。
第三版:把外推变成实测(180 → 360,同分布)
用同样的 20 个维度再生成 180 个新集合(_10.._18)× 40 条 = 7,200 条($0.117 / 0 失败)
| 配置 | 未见 acc | 相对随机 | n |
|---|---|---|---|
| A) 180 集合 × 40 | 0.7850 | 2.68× | 7,200 |
| B) 360 集合 × 40 | 0.8020 | 2.74× | 13,129 |
配对自助法:Δ=+0.0170,CI [+0.0025, +0.0315] → ★ 显著
同分布下 180→360 仍显著 → 120→180 的观察得到独立实测确认。 完整衰减序列:+0.120 → +0.0965 → +0.0365 → +0.0265 → +0.0170
★★ 合并第 8 轮的完整资源分配结论:
| 维度 | 边际收益 | 判定 |
|---|---|---|
| 每集合样本数(>40 条) | Δ=+0.0011,CI 跨 0 | 已饱和 |
| criteria 集合数(120→180) | Δ=+0.0265,CI 全正 | 仍显著 |
三个反复出现的陷阱(本项目踩了四次)
① 恰好等于基线的数字,必须被怀疑,而不是被接受。
三次实例:形状实验首版 0.1000(恰好 1/10)· TF-IDF 版 0.3333(恰好多数类)· 第 5 轮 0.2650(恰好随机)。 前两次经诊断都是退化伪影(模型恒预测同一个选项)。
② 每个「看起来变好了」的数字,先问:测试条件是否仍在训练分布内?
第 4 轮的 0.7242 就是这么来的 —— 选项只换措辞,集合本身仍是训练时见过的 10 个。
③ 训练/测试划分必须自查重叠,不能假定。
第 5 轮留出集有 86.9% 的文本在训练集出现过(criteria 不同但 state 文本相同)→ 高估泛化。已修正为重叠 = 0。
④ 训练集与测试集必须【同分布】—— 否则「广度有效」这类结论会被伪影冒充。
第 9 轮首版用全新维度做广度实验,而留出集是原维度的变体 → 分布不匹配,new180 只有 0.3715 而 old180 是 0.7850。 如果没追问「增益来自哪里」,就会把这个伪影当成「广度显著」写进文档。 修正为同维度后重做,才拿到可信的 +0.0265 和 +0.0170。
⑤(实现层)交付的数字必须做【标定】检查,不能只看准确率。
端到端准确率 0.8055 看起来很干净,但置信度 ECE = 0.1100(严重未标定)。 准确率合格 ≠ 概率可用。 已用温度缩放修复到 0.0604。
已完成的可复现产出
1. corpus-v2(去泄漏语料)
- 2,400 条,
sha256 = 6244baecc1510ce73d7e52d555a56abc7da422ab583f57b8cdba1d1701672949(600,581 B)- ★ 所有产物的哈希与规模统一记录在
corpus/MANIFEST.json(由corpus/make_manifest.py计算生成,不手工抄录) - 立此规则的原因:此前文档里记录的
6c336907...与实测6244baec...不符,而文件本身未变(600,581 B / 2,400 行)—— 手工抄录的哈希会漂移
- ★ 所有产物的哈希与规模统一记录在
- spec 在任何生成之前冻结(
corpus/corpus_spec.yaml) - C1a PASS:17 个条件,0 违规 —— 10 个部门
P(refund|dept) = 0.3500,极差 0.0000(v1 是 0.936) - 最佳 oracle 规则 0.7433,多数类基线 0.6500 → 比 v1 更难(「请不要给我退款」含「退款」,必须理解否定)
2. 官方基线(479 次真实调用)
| 指标 | ZH (n=398) | EN (n=81) | ALL (n=479) |
|---|---|---|---|
| dept | 0.8417 | 0.7654 | 0.8288 |
| urgency | 0.6256 | 0.6296 | 0.6263 |
| refund | 0.8442 | 0.8519 | 0.8455 |
ZH−EN = +0.0763(z=1.66,不显著) —— 官方文档「CJK 表现不等」未复现。 但 80% 功效的检出下限约 0.13 → 「未复现」≠「已证伪」。
3. 契约事实(10 处文档与实测不符,全部以实测为准)
- 仅 2 个端点;
/v1/models必须返回{"models":[...]}(现有实现返回{"data":[...]},是错的) - 缺失 key → 403,无效 key → 401(不是常规顺序)
- 概率四舍五入到 2 位小数(现有实现用
round(p,3)) ScoreQuestion.criteria是minItems: 1(散文说「至少两个」,是错的)- 错误响应体有 4 种形状(按
detail类型区分) - 422 的
input会回显整个请求体(含 state) → 日志必须脱敏
4. 一个可验证的数学事实
P4(20,000 次随机分布验证,最大残差 1.110e-16):
p_max − peak = (1 − p_max) / (n − 1) 0 ≤ p_max − peak ≤ 1/n
peak = max(0, min(1, (n·p_max − 1)/(n − 1)))。
→ 这解释了官方文档 8 个 Score 例子中,6 个符合一种口径、2 个符合另一种。
目录结构
Alben/
├── design/ 设计文档(总设计 + 5 份分层设计)
│ ├── 总设计-Alben.md ★ 主知识库(含 F1–F24 权威定义)
│ ├── A1-模型与训练层.md
│ ├── A2-API契约与类型系统.md
│ ├── A3-标定与评测层.md
│ ├── A4-SDK与开发者工具.md
│ ├── A5-运行时与工程化.md
│ └── _ref/ 调研事实库(RECON-FINDINGS.md 52 KB)
├── corpus/ ★ 语料与实验(本仓库最有价值的部分)
│ ├── MANIFEST.json ★ 单一权威哈希/规模记录(生成而非手抄)
│ ├── corpus_spec.yaml 生成前冻结的规格
│ ├── corpus_v2.jsonl 2,400 条去泄漏语料
│ ├── shape_*.py S1–S4 形状对照实验(四轮)
│ ├── distill.py 官方 API 蒸馏(2,400 次调用 / $0.036)
│ ├── contrastive2.py ★ 第六轮:训练 encoder(有效)
│ └── scale_gen.py ★ 扩数据验证
├── 调研结论与实施计划.md 面向实施的调研总结
└── 迁移交接书.md 换机交接(含已知坑 9 类)
复现全部实验
$env:PYTHONIOENCODING='utf-8' # Windows 中文环境必须
cd Alben
# 语料质量门
python corpus\gates.py
# 形状实验(四轮)
python corpus\shape_experiment_real.py corpus\corpus_v2.jsonl # 轮 1
python corpus\shape_unseen.py corpus\corpus_v2.jsonl # 轮 2
python corpus\_diag_bilinear.py # 轮 3
python corpus\_diag_para.py # 轮 4
# 蒸馏与验证(需要 TYPESAFE_API_KEY)
python corpus\distill.py pilot
python corpus\distill.py generate 2000 400
python corpus\distill.py generate_clean
python corpus\distill_eval.py # 轮 5(≈随机)
python corpus\contrastive2.py # 轮 6(有效)
python corpus\scale_gen.py # 扩到 200 个 criteria(20min / $0.22)
python corpus\scale_curve.py # 轮 7(★ 结论已作废)
python corpus\capacity_sweep2.py # 轮 8:容量/超参扫描
python corpus\best_config.py # 轮 8:最优配置长训练
python corpus\scale_curve2.py # 轮 8:修正后的数据曲线
python corpus\per_criteria.py # 轮 8:广度 vs 深度
python corpus\breadth_gen.py # 轮 9:新增 180 集合($0.11)
python corpus\breadth_matched.py # 轮 9:修正版(同分布)
python corpus\breadth2_gen.py # 轮 9:180 个同维度新集合($0.12)
python corpus\breadth2_eval.py # 轮 9:180 vs 360 决定性验证
环境(实测):Python 3.12.11 · torch 2.11.0+cu128 · transformers 4.57.6 · numpy 2.3.3 · fastapi 0.109.2 · pydantic 2.13.4 · httpx 0.28.1 · pytest 9.1.1 · Windows 11 · RTX 5090
注意:numpy 存在双安装(2.4.4 与 2.3.3),实验统一用 2.3.3。
立场
证据优先级:S3 实测 > S0 SDK 源码 > S1 openapi.json > S2 散文/docstring
本项目发现 10 处文档与实测不符,全部以实测为准。
方法论上最重要的一条(单点定义原则):
公式 / 指标 / 口径 / 证据状态,在仓库里只能有一处定义,其他位置只引用
claim_id。违反它的后果:两份实现各自漂移 · AUROC 出现 0.085 分歧 · 把不可比的量合并 · 同一条断言在不同位置漂移成不同状态。
每个位置单独看都自洽,只有把同一条断言在多处复述时才会暴露。
下一步(按证据排序)
F1–F24 全部完成。 剩余的是已排除方向之外的新方向与明确的未测项。
已排除(不要重试)
| 方向 | 结论 |
|---|---|
| 改网络形状(S2/S3/S4) | 差异 ≤0.02 |
| 全参数微调 | 冻结更多反而更好 |
| 单纯训练更久 | ep24 后收敛 |
| 每集合更多样本(>40) | Δ=+0.0011,CI 跨 0 |
| 语言特定训练 | EN = ZH |
| 按分布分别标定温度 | 比单一温度更差 |
| 编码器容量(第 15 轮) | 4.25 倍大无显著改善 |
| 跨判据空间负例(第 16/18 轮) | 等大对照证明是「池变大」而非「更难」 |
| 换训练目标(第 19 轮) | BCE 四尺度 × 四尺子 16 格方向全负;交叉编码器成本高 14~29 倍且不优于双塔 |
仍值得做
- 扩大 criteria 集合广度 —— 已实测 20→360 集合每个翻倍点均配对显著;$210 预算按此投入
- ★ 造等级序标注 —— 第 19 轮确认语料里没有序数语义(choice 的 criteria 是 dict)。 造出标注后才能做 ordinal 标定指标 / smooth_ece / TOST(§10.1 剩余两项之一)
- ★ 解决权重分发 —— 单文件上限 100 MB,产物 91.9 MB + encoder 91.4 MB 不能放同一个 wheel(第 24 轮)
- 轮换
TYPESAFE_API_KEY—— 唯一未做的运维项 - 修复
round16_ablation.py—— 它从未运行过;第 18 轮已用 nearsyn 的实现覆盖了这个方向,可考虑删除
★ 已排除的方向(不要再试):架构形状(S2/S3/S4 差异 ≤0.02)· 全参数微调(冻结更多反而更好)· 单纯加长训练(ep24 后收敛)· 每集合加样本(>40 条收益归零)
★ 已写好但【尚未运行】的脚本(诚实标注):
第 26 轮已把两个「写了但从未运行」的脚本裁定为【退役】, 并把理由写进文件头 + 加了运行守卫(跑它会直接报错退出):
| 脚本 | 裁定 | 理由 |
|---|---|---|
corpus/hardneg.py |
退役 | ① 数据源 scale_train.jsonl 缺 instructions 的行 = 100%(第 12 轮缺陷 #3)→ 训出的模型不读问题;② 扩负例池的方向已被第 16/18 轮覆盖,结论是池变大本身有害 |
corpus/round16_ablation.py |
退役 | 它要回答的问题已被 round18_nearsyn.py 用更干净的实现回答,且 round18 多了 hard/far 逐集合等大对照(证明「难度无关」) |
corpus/capacity_sweep.py |
保留作留痕 | 首版容量扫描有两个混淆(freeze 与 epochs 未分离;bge-small 只有 4 层,freeze=4 会冻住全部层),结论已被 capacity_sweep2.py 取代 |
审计脚本:corpus/audit_never_run.py · 结果:corpus/_never_run_audit.json
★ 教训:「写了但没跑」等于没写(verifier 第 18 轮的原话)。 但「跑了但数据有缺陷」比「没跑」更糟 —— 它会产生一个看起来可信的错结论。 所以退役时必须写清【为什么不需要跑】,而不只是删掉。
关于 $210 蒸馏预算(最终裁定):
✅ 应该花,全部花在【更多 criteria 集合】上。
- 每个 criteria 集合配 约 40 条样本即可(超过 40 收益归零,是浪费)
- $210 ≈ 5M input token ≈ 约 50 万个 (state, criteria) 对 → 约 12,500 个 criteria 集合(当前 180)
- 已实测区间:集合数 20 → 360,准确率 0.5055 → 0.8020(+0.30),每个翻倍点都配对显著
★ 诚实边界:360 之后仍是外推。边际增益衰减到 +0.017/翻倍, 若按此速率继续衰减,集合数再翻 5 倍(360 → ~11,500)的累计增益量级约 +0.05。
许可
未定。本仓库是个人学习性质的复刻项目。
legacy/(原 jev/)保留不删 —— README 首段标注 known_leak: true 与 question_channel_closed: true。
它是本项目最重要的反面教材:复刻了官方 API 的形状,但没有复刻能力。
Metadata
Release files for alben 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| alben-0.1.1.tar.gz | 188.5 kB | Without provenance | Details for alben-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| alben-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for alben-0.1.1-py3-none-any.whl |
Total release size: 264.7 kB
Release files / alben-0.1.1.tar.gz
| Download URL | alben-0.1.1.tar.gz |
|---|---|
| Size | 188.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
10c9c7924394a6c28808de33ed84d69579f8f4deba69795bc306cbb437948a15
|
|
BLAKE2b-256 checksum How to use checksums |
12e971b0c6074919d85076bf16400217adb75399c1b85d8920b004079b7e81f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / alben-0.1.1-py3-none-any.whl
| Download URL | alben-0.1.1-py3-none-any.whl |
|---|---|
| Size | 76.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
828292cdd67f74e0b49066a81274a7a03f891b924ef62368a54e831d87d939a6
|
|
BLAKE2b-256 checksum How to use checksums |
4785248ba5a80c5963818ccead410a49a23b666005768dd3484dac04b3d2fb14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Security for alben 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.