primitive plus 实验:把 proof 变成可测的 TreeHeap toy
这篇文章解释刚跑完的实验:
primitive_plus_probe.py
它对应 ARA 里的:
P-MATH02: Semantic primitive and plus operator
这次实验不是语言实验。
不是翻译实验。
不是 WMT。
它是一个纯数学 toy,用来验证一个非常小的想法:
如果 TreeHeap 是一个可寻址的堆结构,
那么 plus 能不能同时承担三件事:
1. successor:走到下一个地址
2. information gain:增加信息量
3. mod fold:超过 base 后折回前面的地址
如果这个 toy 都跑不通,就没必要急着谈真实语言里的 TreeHeap 卷积。
先说结论
这轮 toy 实验通过了:
pilot_pass = true
successor_ok = true
info_gain_pre_base = true
info_saturates_after_base = true
mod_fold_targets = [0, 1]
mod_fold_ok = true
kernel_hit_at_1 = 1.0
wrap_breaks_old_kernel = true
用人话说:
plus 可以从 arr[0] 写到 arr[7]。
base=8 满了以后,再 plus 会折回 arr[0]、arr[1]。
base 没满前,信息量每次 +1。
base 满了以后,信息量不再增加,而是覆盖旧位置。
循环窗口 kernel 能找到 [p0, p1, p2]。
覆盖 arr[0] 后,旧 kernel 分数下降。
这说明:
plus = successor + information gain + mod fold
至少在这个合成 toy 里是成立的。
为什么要做这个实验
前面我们讨论 TreeHeap 卷积。
卷积需要一个有序空间。
一维卷积里,这个空间是:
x[0], x[1], x[2], ..., x[n-1]
如果加上取模:
x[(i + 1) mod n]
它就变成一个循环空间。
但是 TreeHeap 不是普通数组。
所以问题变成:
TreeHeap 的有序空间从哪里来?
如果只是人工编号:
0, 1, 2, 3, ...
那只是工程索引。
我们真正想找的是:
这个顺序能不能由 plus 生成?
也就是:
H_{n+1} = plus(H_n, primitive)
这个实验就是把这个 proof 变成可测程序。
TreeHeap 对象怎么定义
这次不用单个向量表示 TreeHeap。
我们明确使用可寻址数组:
TreeHeapState = {
arr: Node[]
base: int
cursor: int
summary: vector
step: int
}
其中:
arr[0] = root
这是你指出的关键点。
如果 TreeHeap 是数组式堆,那么 root 本来就在:
arr[0]
所以 root 不需要额外幻想出来。
真正重要的是:
arr 是主状态。
summary 只是投影。
不要反过来把 summary 当成整棵树。
数组堆的地址规律
这次使用二叉堆的本科数据结构公式:
root = 0
left(i) = 2i + 1
right(i) = 2i + 2
parent(i) = floor((i - 1) / 2)
例如 base = 8:
index: 0 1 2 3 4 5 6 7
树关系是:
0
├─ 1
│ ├─ 3
│ └─ 4
└─ 2
├─ 5
└─ 6
7 是下一层的第一个节点。
这说明 TreeHeap 不是无序集合。
它有地址,有父子关系,也可以有线性顺序。
取模 base
这次设置:
base = 8
地址只有:
0, 1, 2, 3, 4, 5, 6, 7
如果再往后写,就用取模:
target = (cursor + 1) mod base
这就是整数模群里最基础的:
Z / 8Z
也就是:
0,1,2,3,4,5,6,7,0,1,2,...
群论里可以把它看成一个循环群。
但这里只需要理解:
超过 7,就回到 0。
plus 怎么定义
这次的 plus 很简单:
plus(H, primitive):
target = (cursor + 1) mod base
arr[target] = primitive
summary = summarize(arr)
也就是说:
plus 不是向量加法。
plus 是对 TreeHeap 地址空间的一次写入。
它做三件事:
- 找下一个地址。
- 把新 primitive 写进去。
- 更新 summary。
如果用 [ ] 表示空 TreeHeap,那么:
H0 = []
H1 = plus(H0, p0)
H2 = plus(H1, p1)
...
实际写入顺序是:
step 1: p0 -> arr[0]
step 2: p1 -> arr[1]
step 3: p2 -> arr[2]
...
step 8: p7 -> arr[7]
step 9: p8 -> arr[0]
step 10: p9 -> arr[1]
这就是 successor:
n -> n + 1
也是 mod fold:
n + base -> n
信息量怎么测
你说:
[] 信息量是 0
[cat] 信息量是 1
[cat, run] 信息量是另一个值
这次 toy 先用最简单的信息量:
I(H) = 非空节点数量
也就是:
I([]) = 0
I([p0]) = 1
I([p0, p1]) = 2
这个指标很粗。
但适合第一版 toy。
因为我们只是想验证:
base 没满之前,plus 是否增加信息量?
实验结果:
step 1: info 0 -> 1
step 2: info 1 -> 2
step 3: info 2 -> 3
...
step 8: info 7 -> 8
所以:
info_gain_pre_base = true
到了 base = 8 以后,数组满了。
这时再 plus:
step 9: p8 -> arr[0]
它不是新增第 9 个格子。
它覆盖 arr[0]。
所以信息量保持:
8 -> 8
实验结果:
info_saturates_after_base = true
这说明 plus 有两个阶段:
base 未满:信息增长
base 已满:模折叠 / 覆盖 / 循环更新
summary 是什么
实验里每个 primitive 是一个 64 维向量:
p0, p1, ..., p9 ∈ R^64
TreeHeap 的 summary 也是一个向量。
但它不是主状态。
主状态是:
arr
summary 是从 arr 算出来的投影:
summary = summarize(arr)
代码里用了一个简单的地址敏感求和:
summary = normalize(Σ roll(node.value, shift(index)))
这里用到了线性代数:
向量求和
归一化
向量距离
余弦相似度
roll 的意思是按地址改变向量坐标,让同一个 primitive 放在不同地址时,对 summary 的影响不同。
这相当于给地址一个位置编码。
实验检查:
summary_consistency_ok = true
意思是:
summary 每次都确实等于 summarize(arr),没有和 arr 脱节。
还有:
summary_min_delta = 0.4124
意思是每次 plus 后,summary 都发生了明显变化。
kernel 是怎么测的
我们定义一个循环窗口:
window(i) = [
arr[(i - 1) mod base],
arr[i],
arr[(i + 1) mod base]
]
这就是一维卷积最小窗口:
[-1, 0, +1]
然后设置 kernel:
K = [p0, p1, p2]
在 base 填满后:
arr[0] = p0
arr[1] = p1
arr[2] = p2
所以以 i = 1 为中心:
window(1) = [arr[0], arr[1], arr[2]]
= [p0, p1, p2]
应该完全匹配。
实验结果:
kernel_hit_at_1 = 1.0
kernel_top_score = 1.0
这说明循环 kernel 能在地址环上找到正确窗口。
覆盖以后为什么分数下降
第 9 步:
p8 -> arr[0]
于是原来的:
[p0, p1, p2]
变成:
[p8, p1, p2]
这时再用旧 kernel:
K = [p0, p1, p2]
去匹配,分数应该下降。
实验结果:
kernel_top_score = 1.0
kernel_after_wrap_top_score = 0.6731
wrap_breaks_old_kernel = true
这说明:
mod fold 不是空操作。
它真的改变了局部结构。
这点重要。
因为如果折回以后 kernel 分数不变,说明地址环没有真正影响结构。
概率论在这里怎么出现
这次没有做复杂概率模型。
但有一个概率容器思想的前置形式:
对每个 center 计算 kernel score
然后按分数排序
这还不是 softmax 概率。
但它已经是概率容器之前的一步:
候选位置集合 + 分数
下一步可以把分数变成概率:
P(i) = exp(score(i)) / Σ exp(score(j))
这就是本科概率论里常见的归一化思想。
先有 score。
再有 probability。
最后才有 collapse。
这次到底 proof 了什么
这次不是证明 TreeHeap 会推理。
它 proof 的是一个很窄的数学命题:
在一个可寻址 TreeHeap toy 里,
plus 可以被定义为 successor 写入;
在 base 未满时增加信息量;
在 base 满后按 mod 折回;
循环窗口 kernel 可以在这个地址环上匹配局部模式。
对应实验结果全部通过:
closure_ok = true
root_ok = true
successor_ok = true
info_gain_pre_base = true
info_saturates_after_base = true
mod_fold_ok = true
kernel_hit_at_1 = 1.0
wrap_breaks_old_kernel = true
这说明:
P-MATH02 作为 M0 toy 成立。
但这还不是语言 claim。
这次没有证明什么
它没有证明:
真实语义空间里一定有 primitive
真实 TreeHeap checkpoint 已经学到 plus
TreeHeap 会翻译
TreeHeap 会推理
kernel 能处理真实句子
这些都还要后续实验。
这次只是把地基往前铺了一格。
下一步怎么走
下一步应该做两个方向。
第一,把信息量从:
非空节点数量
升级为:
rank
entropy
description length
reconstruction bits
distinguishability
第二,把 primitive 从人工 p0,p1,... 换成可学习的 basis:
learned primitive basis
也就是问:
模型能不能自己找到那个类似“1”的基元?
然后才进入:
TreeHeap-object echo
structure invariant
S2 translation
一句话
这次实验把一句抽象话:
TreeHeap 需要 primitive 和 plus 来生成有序性。
变成了可测 toy:
arr[0] 是 root。
plus 写入下一个地址。
base 前信息量增长。
base 后按 mod 折回。
kernel 能在循环地址环上滑动。
这就是进入 Echo 之前,TreeHeap 工具箱需要补上的数学基础。
License: GPLv3