MINI-TABPFN COURSE · LESSON 03

第 3 课讲义:Inducing Points 与 Column Encoder——怎样让列上下文可计算?

这是一节可直接阅读、实现和验收的课程单元。页面正文由 canonical Markdown lesson source 投影生成。

第 03 / 08 课course-v0.5 / owner-review完整讲义 + 动手验收包
课程讲义 · 基础知识层canonical source: course/lectures/03-induced-column-encoder.md · 先建立概念、符号、公式与边界,再进入工程实现。

讲义定位

表格输入同时有 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. 学习目标与先修知识

学完后你应该能够:

  1. 区分 scalar cell、cell token、column-contextualized state 和 row token;
  2. 说明 missingness 为什么不能只用一个数值编码;
  3. 写出 \(N\to K\to N\) 两段 cross-attention 的权威 shape;
  4. 解释普通 column self-attention 与 inducing column encoder 的复杂度差异;
  5. 处理有效 row、padding row 和 missing cell 的不同 mask;
  6. 用 scaling、shape 和 missingness audit 检查 column encoder;
  7. 用一句话说明 Mini 相对官方 Stage 1 省略了什么。

2. 一张表的三种坐标

设 episode 的 feature tensor:

\[X\in\mathbb{R}^{B\times N\times P},\qquad B=\text{episode batch},\; N=\text{row 数},\; P=\text{column 数}\]

一个 scalar \(x_{bnp}\) 同时属于:第 \(n\) 行(与同行其他 feature 描述同一对象)、第 \(p\) 列 (与同列其他 row 呈现该 feature 的 task-level 分布)、以及当前 episode 的 context/query 边界。

因此 cell 是最细原子,但不是最终预测对象。

3. Cell token:值与缺失是两个变量

先把 scalar 映射到表示空间。真实数据中的「缺失」不是普通数值:\(x=0\) 可能是真实观测,也可能是 把 NaN 填成 0 的结果。因此至少需要两个对象:

\[v_{bnp}=\text{填充或标准化后的数值},\qquad m_{bnp}=\begin{cases}1,&\text{原始值存在},\\0,&\text{原始值缺失}.\end{cases}\]

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}\),主项近似:

\[\mathcal{O}(P N^2 D)\]

当 \(N\) 增长时,\(N^2\) 是主要压力。我们希望保留每个位置的输出,但降低直接两两读取的成本。

6. Inducing points:权威 \(N\to K\to N\)

设 \(K\ll N\),learned inducing states \(I\) 与 cell states \(X\) 在「列在前」布局下为:

\[I\in\mathbb{R}^{B\times P\times K\times D},\qquad X\in\mathbb{R}^{B\times P\times N\times D}\]

第一段:inducing 从所有 cell 读取(\(N\to K\)):

\[I'=\operatorname{Attention}(Q=I,\,K=X,\,V=X)\in\mathbb{R}^{B\times P\times K\times D}\]

第二段:每个 cell 从 inducing 读回(\(K\to N\)):

\[X'=\operatorname{Attention}(Q=X,\,K=I',\,V=I')\in\mathbb{R}^{B\times P\times N\times D}\]

这就是 \(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 K D)\]

相对 \(\mathcal{O}(P N^2 D)\) 这是折衷,不是无损等价于全量 \(N^2\) attention。\(K\) 太小会瓶颈, \(K\) 太大则节省变少。课程必须测量这个折衷。

7. 轴操作比模块名字更重要

外层约定常是 \(\mathbb{R}^{B\times N\times P\times D}\)。沿列内 row 轴 attention 时:

\[\mathbb{R}^{B\times N\times P\times D} \xrightarrow{\operatorname{transpose}(1,2)} \mathbb{R}^{B\times P\times N\times D} \to\ldots\to \mathbb{R}^{B\times N\times P\times D}\]

最危险的错误是把 \([N,P]\) 转错,得到另一个同样「能跑」、却沿 feature 轴做 attention 的模型。

8. Mask 的三种边界

  1. missing cell mask:该位置有无真实观测(影响 embedding,也可阻止作为 key 被读);
  2. padding row mask:该 row 是否只是 batch 对齐占位(通常必须屏蔽为 key);
  3. 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. 课堂推导与实验

  1. 在 \(N=4,K=2\) 上手算两段矩阵大小;
  2. 打印每次 \(\mathbb{R}^{B\times N\times P\times D}\leftrightarrow\mathbb{R}^{B\times P\times N\times D}\) 变换;
  3. 比较普通 self-attention 与 induced 的运行时间趋势,并报告 score 元素数 \(N^2\) vs \(2NK\);
  4. 真实零值 cell vs missing cell,检查表示是否不同;
  5. 增加 padding row,确认不污染有效输出;
  6. 比较不同 \(K\) 对 proxy 指标与耗时的影响。

12. 知识检查

  1. 为什么 \(K\) 不是类别数?
  2. 为什么 column encoder 不能只输出每列一个永久向量?
  3. \(N\to K\to N\) 中第一段和第二段分别由谁发起读取?
  4. missing cell 与 padding row 为什么不能共用同一 mask 语义?
  5. 如果 transpose 写错,哪些 shape 仍可能看起来正确?
  6. \(\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 留给后续课。

HAND-ON LAB · 工程实现与验收包

第 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):

\[I'=\operatorname{Attention}(Q=I,\,K=X,\,V=X)\in\mathbb{R}^{B\times P\times K\times D}\]
\[X'=\operatorname{Attention}(Q=X,\,K=I',\,V=I')\in\mathbb{R}^{B\times P\times N\times D}\]

课程中的 \(K\) 是教学可控的压缩路由,不代表官方 TabPFN-3 的全部内部实现(官方默认 \(K\approx 128\))。

3 小时课堂流程

时间内容证据
00:00–00:25普通列 attention 的成本画出 [N,N] score matrix
00:25–00:55inducing 两段 attention写出两次 Q/K/V shape
00:55–01:35cell embedding 与 missing mask[B,N,P,d] smoke test
01:35–01:50休息与 mask 错误样例解释 padding/真实零值
01:50–02:25column encoder 实现每列返回 N 个 updated states
02:25–02:50scaling 与可视化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 处理:

\[\mathbb{R}^{B\times N\times P\times D} \xrightarrow{\operatorname{transpose}(1,2)} \mathbb{R}^{B\times P\times N\times D} \to\ldots\to \mathbb{R}^{B\times N\times P\times D}\]
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\):

\[\begin{aligned} \operatorname{cell\_states}&\in\mathbb{R}^{2\times4\times3\times8},& \operatorname{one\ column\ input}&\in\mathbb{R}^{2\times4\times8},\\ \operatorname{inducing\ state}&\in\mathbb{R}^{2\times2\times8},& \operatorname{one\ column\ output}&\in\mathbb{R}^{2\times4\times8},\\ \operatorname{encoded\ output}&\in\mathbb{R}^{2\times4\times3\times8}. \end{aligned}\]

如果最后只返回 [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 元素数:

\[\begin{aligned} \text{ordinary score elements}&=N^2,\\ \text{induced score elements}&=KN+NK=2NK. \end{aligned}\]

复杂度主项:全列 \(\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。

退出条件与评分

  1. \(N\to K\to N\) 的所有 shape assertions 通过;
  2. 任意 \(N\) 都返回 \(N\) 个 cell states;
  3. invalid row 在两段 attention 中都不可读;
  4. 复杂度 audit 明确列出 \(N^2\) 与 \(2NK\);
  5. missing zero 与 real zero 的 embedding 可区分;
  6. 课程报告清楚写出这只是 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 公式。