讲义定位
表格输入同时有 row 轴和 column 轴。第 2 课的 attention 只告诉我们如何读取 token;这一课要回答: 当一列有很多 row 时,怎样让每个 cell 获得列内上下文,又不直接承担完整的 \(N\times N\) 成本?
核心直觉:用 \(K\) 个瓶颈读写整列,保留每个 cell 的输出,把主交互从 \(N^2\) 降到 \(NK\) 量级。
我们会先定义 cell token,再沿 column 方向组织 tensor,最后推导两段 inducing attention: \(N\to K\to N\)。这里的 \(K\) 是 inducing points 数,不是类别数,也不是永久列 embedding。
与 TabPFN-3 Stage 1 对齐
官方 Stage 1(Feature distribution embedding, column-wise):每一列独立用带 inducing-point attention 的 transformer 嵌入,避免全量跨行二次代价,同时在任意数据规模上捕捉列级统计 (02_tabpfn3.tex §Architecture)。课程本课实现的正是这一阶段的 Mini 教学核心。
本课符号
| 符号 | 含义 |
|---|---|
| \(N\) | 一列内的 row 数(本课沿列处理时) |
| \(P\) | feature / column 数 |
| \(K\) | inducing points 数(官方默认约 128;课程用小 \(K\)) |
| \(D\) | cell / 中间表示宽度 |
| \(I, I'\) | inducing states 及其更新 |
| \(X, X'\) | 列内 cell states 及其更新 |
1. 学习目标与先修知识
学完后你应该能够:
- 区分 scalar cell、cell token、column-contextualized state 和 row token;
- 说明 missingness 为什么不能只用一个数值编码;
- 写出 \(N\to K\to N\) 两段 cross-attention 的权威 shape;
- 解释普通 column self-attention 与 inducing column encoder 的复杂度差异;
- 处理有效 row、padding row 和 missing cell 的不同 mask;
- 用 scaling、shape 和 missingness audit 检查 column encoder;
- 用一句话说明 Mini 相对官方 Stage 1 省略了什么。
2. 一张表的三种坐标
设 episode 的 feature tensor:
一个 scalar \(x_{bnp}\) 同时属于:第 \(n\) 行(与同行其他 feature 描述同一对象)、第 \(p\) 列 (与同列其他 row 呈现该 feature 的 task-level 分布)、以及当前 episode 的 context/query 边界。
因此 cell 是最细原子,但不是最终预测对象。
3. Cell token:值与缺失是两个变量
先把 scalar 映射到表示空间。真实数据中的「缺失」不是普通数值:\(x=0\) 可能是真实观测,也可能是 把 NaN 填成 0 的结果。因此至少需要两个对象:
Mini 简化:独立 missing embedding,再与 value representation 相加:
value_state = value_mlp(value.unsqueeze(-1))
missing_state = missing_embedding(observed.long())
cell_state = value_state + missing_state
官方 TabPFN-3:对每个 NaN cell 计算 binary indicator,与 cell value concat 后再 embed;另有 Inf 指示。Mini 用加法分支显式化边界即可,须在报告中注明并非官方 concat 路径。
这不意味着 missing embedding 就是「缺失机制」的完美建模;它只是把缺失与真实零值分开。
4. 为什么列上下文有用
单个 cell 通常不足以判断它在当前 task 中的含义。列内其他 row 可提供:尺度与分布、context/query 相对位置、该列与标签的局部线索、缺失模式与有效 row 边界。
Column Encoder 的目标是 更新每个 cell state,使它带有同列上下文——不是把整列永久压成一个向量。 若直接平均成 \([B,P,D]\),后续 row token 可能失去不同 row 的局部差异。
5. 普通 column self-attention 的成本
对每一列沿 row 轴做 self-attention,每列 score 属于 \(\mathbb{R}^{N\times N}\),主项近似:
当 \(N\) 增长时,\(N^2\) 是主要压力。我们希望保留每个位置的输出,但降低直接两两读取的成本。
6. Inducing points:权威 \(N\to K\to N\)
设 \(K\ll N\),learned inducing states \(I\) 与 cell states \(X\) 在「列在前」布局下为:
第一段:inducing 从所有 cell 读取(\(N\to K\)):
第二段:每个 cell 从 inducing 读回(\(K\to N\)):
这就是 \(N\to K\to N\)。第一段把长列汇聚到短 bottleneck;第二段把混合后的上下文写回每个 cell。 输出仍有 \(N\) 个 cell states,不是把 \(N\) 行删到 \(K\) 行。
6.1 复杂度直觉
两段 score 大小分别为 \(K\times N\) 与 \(N\times K\),元素数 \(2NK\),主项近似:
相对 \(\mathcal{O}(P N^2 D)\) 这是折衷,不是无损等价于全量 \(N^2\) attention。\(K\) 太小会瓶颈, \(K\) 太大则节省变少。课程必须测量这个折衷。
7. 轴操作比模块名字更重要
外层约定常是 \(\mathbb{R}^{B\times N\times P\times D}\)。沿列内 row 轴 attention 时:
最危险的错误是把 \([N,P]\) 转错,得到另一个同样「能跑」、却沿 feature 轴做 attention 的模型。
8. Mask 的三种边界
- missing cell mask:该位置有无真实观测(影响 embedding,也可阻止作为 key 被读);
- padding row mask:该 row 是否只是 batch 对齐占位(通常必须屏蔽为 key);
- episode boundary:不同 episode 绝不能互相读取。
两种策略对 missing 的处理意义不同,必须在实现说明中写清。padding 与 missing 不能用同一个 mask 语义草率替代。
9. 最小模块契约
class InducedColumnEncoder(nn.Module):
def forward(
self,
cell_states: Tensor, # [B, N, P, D]
row_mask: Tensor, # [B, N], True = valid
observed_mask: Tensor, # [B, N, P], True = observed
) -> Tensor:
"""Return column-contextualized states with the same outer shape [B,N,P,D]."""
默认输出仍保留 \([B,N,P,D]\):后续 Stage 2(第 4 课 row aggregation)需要每个 row 的 feature states。
10. Mini 相对官方的省略项(须在报告写一句)
官方(02_tabpfn3 / App.C) | Mini 本课 |
|---|---|
| feature group size=3(循环邻域 triplet → linear cell embed) | 单 scalar → embed |
| training rows 的 target-aware embeddings | 通常延后到 ICL / label injection |
| QASSMax | 标准 scaled softmax(第 2 课模块) |
| \(K\approx 128\);row-chunking 先算全 train inducing 再 chunk 复用 | 小 \(K\);整表一次性 forward |
Mini 简化:……。官方 TabPFN-3:……。——按上表填空即可。
Inducing points 提供可学习信息通道,不自动保证:严格 raw-column 置换不变、任意 missingness 机制鲁棒、完整保留全部邻居信息、或 attention weight = 列内因果贡献。
11. 课堂推导与实验
- 在 \(N=4,K=2\) 上手算两段矩阵大小;
- 打印每次 \(\mathbb{R}^{B\times N\times P\times D}\leftrightarrow\mathbb{R}^{B\times P\times N\times D}\) 变换;
- 比较普通 self-attention 与 induced 的运行时间趋势,并报告 score 元素数 \(N^2\) vs \(2NK\);
- 真实零值 cell vs missing cell,检查表示是否不同;
- 增加 padding row,确认不污染有效输出;
- 比较不同 \(K\) 对 proxy 指标与耗时的影响。
12. 知识检查
- 为什么 \(K\) 不是类别数?
- 为什么 column encoder 不能只输出每列一个永久向量?
- \(N\to K\to N\) 中第一段和第二段分别由谁发起读取?
- missing cell 与 padding row 为什么不能共用同一 mask 语义?
- 如果
transpose写错,哪些 shape 仍可能看起来正确? - \(\mathcal{O}(NK)\) 相对 \(\mathcal{O}(N^2)\) 的节省以什么表达能力折衷为代价?
13. 本课的硬结论
Column Encoder 的核心不是「用了 inducing points」这一名称,而是能在明确轴语义和 mask 边界下,把 每个 cell 更新为带有列上下文的 state,并用 scaling audit 证明 \(\mathcal{O}(PNKD)\) 与表达效果之间的 折衷。它对应 TabPFN-3 的 Stage 1;Stage 2/3 留给后续课。
第 3 课:Inducing Points 与 Column Encoder
本课交付
本课把一个 scalar cell 变成带列内上下文的 cell state,并明确证明「inducing points 是低秩路由」, 而不是「把 \(N\) 行删到 \(K\) 行」。本课交付:
models/cell_embedder.py:数值 cell、missing value、validity mask;models/induced_attention.py:\(N\to K\to N\) block;models/column_encoder.py:沿 rows 独立处理每一列;experiments/attention_scaling.py:普通 attention vs induced attention;- 4 行、2 个 inducing points 的可视化和 mask audit。
核心直觉:用 \(K\) 个瓶颈读写整列,保每格输出,降 \(N^2\to NK\)。
与 TabPFN-3 Stage 1 对齐
官方 Stage 1:Feature distribution embedding(column-wise),每列独立用 inducing-point attention 捕捉列级统计,避免全量跨行二次代价。本课实现其 Mini 核心;官方额外项见文末对照表。
本课符号
| 符号 | 含义 |
|---|---|
| \(N\) | 一列内 row 数 |
| \(P\) | column / feature 数 |
| \(K\) | inducing points(不是类别数) |
| \(D\) | 表示宽度 |
项目问题
对一列的 \(N\) 个 cell states 做普通 self-attention,score 是 \(\mathbb{R}^{N\times N}\),成本随 \(N^2\) 增长。我们希望每个 cell 仍能读到列内上下文,但信息先经过少量 learned inducing states。
权威两段式(canon §3.3):
课程中的 \(K\) 是教学可控的压缩路由,不代表官方 TabPFN-3 的全部内部实现(官方默认 \(K\approx 128\))。
3 小时课堂流程
| 时间 | 内容 | 证据 |
|---|---|---|
| 00:00–00:25 | 普通列 attention 的成本 | 画出 [N,N] score matrix |
| 00:25–00:55 | inducing 两段 attention | 写出两次 Q/K/V shape |
| 00:55–01:35 | cell embedding 与 missing mask | [B,N,P,d] smoke test |
| 01:35–01:50 | 休息与 mask 错误样例 | 解释 padding/真实零值 |
| 01:50–02:25 | column encoder 实现 | 每列返回 N 个 updated states |
| 02:25–02:50 | scaling 与可视化 | N 趋势、mask heatmap |
| 02:50–03:00 | 退出评审 | 交付复杂度和边界说明 |
1. Cell token:先保留「值」和「缺失」两个对象
输入 \(X\in\mathbb{R}^{B\times N\times P}\),目标 \(E\in\mathbb{R}^{B\times N\times P\times D}\)。
Mini 简化(加法 missing branch):
class NumericCellEmbedder(nn.Module):
def __init__(self, d_model: int):
super().__init__()
self.value = nn.Linear(1, d_model)
self.missing = nn.Parameter(torch.zeros(d_model))
def forward(self, x: Tensor, missing: Tensor) -> Tensor:
assert x.ndim == 3 and missing.shape == x.shape
safe_x = torch.nan_to_num(x, nan=0.0).unsqueeze(-1)
e = self.value(safe_x)
return e + missing.unsqueeze(-1).to(e.dtype) * self.missing
safe_x=0 只是数值安全处理;真正区分 missing 与真实 zero 的是显式 missing branch。
官方:NaN/Inf binary indicator 与 cell value concat 后再 embed。Mini 不强制 concat,但报告须 标注差异。padding row/column 还需要另一个 validity mask,不能把 missing mask 复用成 padding mask。
2. InducedAttentionBlock 的 exact contract
class InducedAttentionBlock(nn.Module):
def __init__(self, d_model: int, n_inducing: int): ...
def forward(
self,
x: Tensor, # [B, N, D]
valid_mask: Tensor, # [B, N], True = 可读
) -> tuple[Tensor, dict[str, Tensor]]:
# return x_out [B,N,D]
# diagnostics: inducing_weights [B,K,N], reconstruction_weights [B,N,K]
内部第一段:learned I:[B,K,D] 作 Q,x 作 K/V;第二段:x 作 Q,第一段输出作 K/V。 对 invalid key,第一段 score 必须加 -inf;第二段 inducing slots 固定有效。
如果 attention 实现是 batch-first [B,N,D],必须在进入第 2 课模块前加 head 轴,或由多头包装负责。 不要在本课复制第二份 softmax。
3. Column Encoder 的轴操作
cell states 是 [B,N,P,D]。每一列沿 N 处理:
def forward(self, cell_states, row_valid_mask, column_valid_mask=None):
# cell_states: [B, N, P, D]
states_by_column = cell_states.permute(0, 2, 1, 3) # [B, P, N, D]
encoded = []
for p in range(P):
encoded.append(self.block(states_by_column[:, p], row_valid_mask)[0])
return torch.stack(encoded, dim=2) # [B, N, P, D]
第一版可用 Python loop 换清晰度;第二版再合并 B*P 做向量化。课程要测量差异,但不能为了优化而 隐藏「沿 rows、逐 column」的对象。
4. 课堂实验
实验 A:输出形状与信息保留
用 \(B=2,N=4,P=3,D=8,K=2\):
如果最后只返回 [B,P,D],说明把 column contextualization 错做成永久列 summary,必须退回。
实验 B:N-scaling audit
对 \(N\in\{32,64,128,256,512\}\),分别测量普通 self-attention 与 induced block 的 wall time、峰值 显存(能测则测)和 score matrix 元素数:
复杂度主项:全列 \(\mathcal{O}(P N^2 D)\);induced \(\mathcal{O}(P N K D)\)(折衷,非等价)。常数、 kernel、Python loop 和 GPU warm-up 会影响实测时间;趋势与数字分开记录。
实验 C:missing/padding audit
把一个真实 cell 的值设为 0 且 missing=False;再标成 missing。两次 embedding 不得相同。另给一整行 padding,确认它不会被 inducing query 读取,也不会制造有效 output。
5. Mini vs 官方(退出报告必写一句)
| 官方 | Mini |
|---|---|
| triplet feature group → linear cell embed | 单值 + missing branch |
| target-aware embeddings on training rows | 延后到后续课 |
| QASSMax | 第 2 课标准 softmax |
| \(K\approx 128\);row-chunking 复用 inducing KV | 小 \(K\);整表 forward |
常见失败
- 在列轴上做了 attention,却把 \(P\) 当成 \(N\);
- 第二段 Query 用了 inducing states,导致只返回 \(K\) 个 token;
- mask polarity 反了,padding 获得最大权重;
- \(K\ge N\) 时仍宣称获得了低成本;
- 只报告运行时间,不报告 score matrix 和实际 \(K/N\);
- 把 inducing weight 当成 feature causal contribution。
退出条件与评分
- \(N\to K\to N\) 的所有 shape assertions 通过;
- 任意 \(N\) 都返回 \(N\) 个 cell states;
- invalid row 在两段 attention 中都不可读;
- 复杂度 audit 明确列出 \(N^2\) 与 \(2NK\);
- missing zero 与 real zero 的 embedding 可区分;
- 课程报告清楚写出这只是 inspired engineering block,并对照 Stage 1 / Mini 省略项。
评分建议:cell/mask 20%,induced block 30%,axis contract 20%,scaling/memory evidence 20%,边界 10%。
课后作业
把列循环改成 B*P 合并的向量化实现,保持随机权重下输出与 loop 版本误差在 tolerance 内,并提交 一张 profiling 对照表。不要在作业中引入第二套 attention 公式。