TreeHeap 的概率读取核:从 arr[1] 开始的 stop/left/right 坍缩

SPR-031 的结论里有一个明显问题:

我们让 root/subheap state 一次性吐出答案。

这会形成 root bottleneck。

如果答案在左子树、右子树、某个叶子,或者某个内部子堆里,那么更自然的做法不是让 root 背下所有东西,而是从 root 开始读取:

arr[1]
  -> stop
  -> left
  -> right

每一步都由一个 kernel 决定。

所以 SPR-032 的问题是:

TreeHeap read 能不能写成一个概率路径坍缩过程?

结论先说:

路径坍缩成立。
叶子读取几乎成立。
内部子堆摘要还没成立。

ARA 状态:

S1-READ-C01 -> open / mixed pilot

什么叫 read kernel?

我们先把 TreeHeap 当成一个数组堆:

arr[1] = root
arr[2] = left(root)
arr[3] = right(root)
arr[4] = left(left(root))
arr[5] = right(left(root))
...

一个读取核可以写成:

K_{read}(q, h_i, p_i) \rightarrow P(stop), P(left), P(right)

含义是:

  • q:这次要读什么,也就是 query。
  • h_i:当前节点的 TreeHeap state。
  • p_i:当前节点的路径/地址信息。
  • 输出:在当前节点停止、走左边、走右边的概率。

如果输出是硬选择,就是:

while true:
    action = K_read(query, current_node)
    if action == stop:
        return current_node_state
    if action == left:
        current_node = current_node.left
    if action == right:
        current_node = current_node.right

这就是尾递归,也可以直接写成循环。

如果输出是概率,就不是只走一条路,而是维护一个 frontier:

root mass = 1.0
每一层:
  一部分概率 stop 到当前节点
  一部分概率流向 left
  一部分概率流向 right
最后把所有 stop 的状态加权求和

这就是 soft read。

stop 不是空操作

你之前说的这个例子很关键:

root_left_right = [stop_0.5, left_0.3, right_0.2]

这里 stop 不是“什么也不做”。

它的意思是:

就在当前节点读取。

如果当前节点是叶子:

stop -> 读一个 token

如果当前节点是内部节点:

stop -> 读一个子堆摘要

所以 TreeHeap 不是 B+ 树那种“只有叶子有数据”的结构。TreeHeap 的内部节点也应该有意义。

这次实验专门把内部 stop 做成可测量目标。

实验怎么做?

数据来自真实 WMT17 英文侧文本:

/mnt/nas/datasets/wmt17/train.zh-en

切词模型:

/mnt/nas/datasets/wmt17/sp_bpe.model

样本设置:

samples = 3000
train/test/ood = 2400/300/300
token length = 4..8
vocab limit = 513

我们把一个短 BPE 序列写入 8 个叶子:

tokens = [t0, t1, t2, t3, ...]

arr[8]  = t0
arr[9]  = t1
arr[10] = t2
...

然后随机给一个 query node。

如果 query 是叶子,比如 arr[10]

目标 = 这个叶子的 token id

如果 query 是内部节点,比如 arr[2]

目标 = arr[2] 覆盖的子堆 span 的 checksum bucket

checksum 是一个玩具摘要。它不是语言语义,只是为了让内部 stop 有一个明确答案。

也就是说:

叶子 stop     -> 复制 token
内部节点 stop -> 输出子堆摘要

两个模型

第一个是 root baseline:

root_query_decoder:
  TreeEncoder(tokens) -> root state
  root state + query node id -> answer

它必须从 root state 里猜所有答案。

第二个是概率读取核:

probabilistic_read_kernel:
  TreeEncoder(tokens) -> all node states
  q + current node + path -> stop/left/right
  stop 后再 readout answer

它不是让 root 背下所有答案,而是根据 query 走到目标节点。

Run A:128 个内部摘要桶

设置:

checksum_buckets = 128
epochs = 40
labels = 641

结果:

Model Params OOD acc OOD internal OOD leaf Route acc
root_query_decoder 398,209 0.0638 0.0205 0.1066 n/a
probabilistic_read_kernel 499,588 0.6124 0.2214 0.9989 1.0000

这说明:

read kernel 比 root bottleneck 强很多。
路径路由完全学会了。
叶子 token 几乎完全读对。
内部摘要仍然很弱。

Run B:32 个内部摘要桶

为了判断是不是摘要桶太难,我们把 checksum 从 128 桶降到 32 桶:

checksum_buckets = 32
epochs = 80
labels = 545

结果:

Model Params OOD acc OOD internal OOD leaf Route acc
root_query_decoder 373,537 0.1184 0.0765 0.1598 n/a
probabilistic_read_kernel 474,916 0.7177 0.4332 0.9989 1.0000

内部摘要有提升:

0.2214 -> 0.4332

但仍然没有达到“解决”的程度。

所以这不是单纯训练不够的问题。更可能是:

当前 compose state 很适合保留叶子地址读取,
但还没有被设计成稳定的内部子堆摘要。

为什么 hard read 和 soft read 一样?

这次 hard read 是一条确定路径:

root -> left -> right -> stop

soft read 是概率流:

root mass = 1.0
mass 分到 stop/left/right
下一层继续分

因为路由已经学到接近确定:

route_acc = 1.0000

所以 soft frontier 最后也几乎坍缩成同一条路径。

这很好理解:

概率容器在信息不足时保留多条路。
当模型非常确定时,它自然坍缩成一条路。

这次证明了什么?

证明一:

从 arr[1] 开始的 stop/left/right 读取过程是可学习的。

证明二:

这个读取过程可以写成尾递归,也可以写成 soft frontier 概率传播。

证明三:

对于叶子 token copy,它明显优于 root-only decoding。

Run A 的 OOD 对比:

root OOD acc = 0.0638
read OOD acc = 0.6124
提升 = 0.5486

Run B 的 OOD 对比:

root OOD acc = 0.1184
read OOD acc = 0.7177
提升 = 0.5992

这说明 TreeHeap 的地址、路径、局部节点状态确实被利用了。

这次没有证明什么?

没有证明翻译。

没有证明世界模型。

没有证明无监督路由。

没有证明长句法。

也没有证明内部子堆摘要已经解决。

最关键的负结果是:

internal OOD acc 最高只有 0.4332

这说明:

stop at internal node 的机制是对的,
但 internal node state 里面应该保存什么,还没设计好。

下一步怎么走?

SPR-032 把问题拆开了。

之前我们以为问题是:

怎么读 TreeHeap?

现在更准确:

路径坍缩怎么读:基本解决。
内部节点读什么:还没解决。

下一步应该把内部摘要目标换成更符合 TreeHeap 组合性的东西。

比如:

subheap length
first token
last token
bag checksum
ordered checksum
learned phrase vector

不要一开始就用任意 checksum 桶硬压。checksum 可以当压力测试,但不一定符合 TreeHeap compose 的自然代数。

还需要加 baseline:

small Transformer read baseline
pointer/copy baseline
matched flat MLP

并且要测试:

如果不给 teacher route,
只给最终 answer loss,
read kernel 能不能自己学会走路?

这才是从监督路径读取走向真正结构学习的下一步。

总结

SPR-032 的结论很清楚:

TreeHeap read 不应该再压在 root bottleneck 上。
它应该是从 arr[1] 开始的概率路径坍缩。

这条路已经被实验支持:

route acc = 1.0000
leaf OOD acc = 0.9989

但内部节点的世界还没打开:

internal OOD acc = 0.2214 / 0.4332

所以 C32 不是终点,而是把下一个问题照亮了:

TreeHeap 的 internal node state,应该如何学习成为一个真正的子堆摘要?

这会是 S1 接下来最重要的问题之一。