CH 15 / SOURCE EXAMPLE

autoint.py

ch15 特征工程/code/ch04_feature_interaction/autoint.py

这个示例讲什么

│ ├── autoint.py §15.5.3 AutoInt:多头自注意力 + 注意力图

AutoInt:基于 Self-Attention 的自动特征交互(第15章 §15.5.3) 把每个 field 的 Embedding 当成一个 token: Q = E W_Q, K = E W_K, V = E W_V A = softmax(Q K^T / sqrt(d_k)), H = A V 堆叠多头注意力,最后把各 field 的表示展平送入 MLP。相比 DCN 的固定交叉, Attention 的权重随样本变化,是"动态特征交互"的代表。 运行:python3 ch04_feature_interaction/autoint.py

上面来自章节 README 或源码自身的说明,不是自动验证的运行结果,也不能代替 PDF 的书中论断。讲解时请结合实际代码、输入输出与原书页码。

源码结构与阅读入口

  1. MultiHeadSelfAttention · 第 30 行
  2. AutoInt · 第 52 行
  3. main · 第 78 行

完整源码 · 103 行

1"""2AutoInt:基于 Self-Attention 的自动特征交互(第15章 §15.5.3)34把每个 field 的 Embedding 当成一个 token:5    Q = E W_Q, K = E W_K, V = E W_V6    A = softmax(Q K^T / sqrt(d_k)),  H = A V7堆叠多头注意力,最后把各 field 的表示展平送入 MLP。相比 DCN 的固定交叉,8Attention 的权重随样本变化,是"动态特征交互"的代表。910运行:python3 ch04_feature_interaction/autoint.py11"""1213from __future__ import annotations1415import math16import pathlib17import sys1819import torch20import torch.nn as nn2122sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))2324from _train_utils import (  # noqa: E40225    N_FIELDS, MLP, BaselineMLP, count_params, embedding_table, print_comparison,26    run_experiment, stack_embeddings,27)282930class MultiHeadSelfAttention(nn.Module):31    def __init__(self, n_fields: int, dim: int, n_heads: int = 2):32        super().__init__()33        assert dim % n_heads == 034        self.n_heads = n_heads35        self.d_head = dim // n_heads36        self.wq = nn.Linear(dim, dim)37        self.wk = nn.Linear(dim, dim)38        self.wv = nn.Linear(dim, dim)39        self.wo = nn.Linear(dim, dim)4041    def forward(self, E: torch.Tensor) -> torch.Tensor:42        # E: [B, F, d]43        B, F, d = E.shape44        def split(t):45            return t.view(B, F, self.n_heads, self.d_head).transpose(1, 2)46        q, k, v = split(self.wq(E)), split(self.wk(E)), split(self.wv(E))47        att = torch.softmax(q @ k.transpose(-1, -2) / math.sqrt(self.d_head), dim=-1)48        h = (att @ v).transpose(1, 2).reshape(B, F, d)49        return self.wo(h)505152class AutoInt(nn.Module):53    def __init__(self, dim: int = 4, n_blocks: int = 1, n_heads: int = 2, hidden=(32,)):54        super().__init__()55        self.emb = embedding_table(dim)56        self.blocks = nn.ModuleList([MultiHeadSelfAttention(N_FIELDS, dim, n_heads)57                                     for _ in range(n_blocks)])58        self.norms = nn.ModuleList([nn.LayerNorm(dim) for _ in range(n_blocks)])59        self.mlp = MLP(dim * N_FIELDS, hidden, out_dim=1, dropout=0.2)6061    def forward(self, x):62        E = stack_embeddings(self.emb, x)63        for block, norm in zip(self.blocks, self.norms):64            E = norm(E + block(E))   # 残差 + LayerNorm65        return self.mlp(E.flatten(1)).squeeze(-1)6667    def attention_map(self, x):68        """返回第一个 block 的平均注意力矩阵,用于观察'谁和谁交互'。"""69        E = stack_embeddings(self.emb, x)70        block = self.blocks[0]71        B, F, d = E.shape72        q = block.wq(E).view(B, F, block.n_heads, block.d_head).transpose(1, 2)73        k = block.wk(E).view(B, F, block.n_heads, block.d_head).transpose(1, 2)74        att = torch.softmax(q @ k.transpose(-1, -2) / math.sqrt(block.d_head), dim=-1)75        return att.mean(1).mean(0)767778def main() -> None:79    print("AutoInt:A = softmax(QK^T/√d_k),H = AV(§15.5.3)\n")80    model, auc, _ = run_experiment(lambda: AutoInt(), "AutoInt", steps=600)81    _, base_auc, _ = run_experiment(BaselineMLP, "Baseline", steps=600)8283    # 观察某一个 batch 的注意力图84    from _train_utils import FIELD_NAMES, make_dataset85    X, _ = make_dataset(64, seed=3)86    with torch.no_grad():87        att = model.attention_map(torch.from_numpy(X).long())88    print("\nAutoInt 第一个 block 的平均注意力(行=query field,列=key field):")89    print("        " + "  ".join(f"{n[:4]:>4}" for n in FIELD_NAMES))90    for i, name in enumerate(FIELD_NAMES):91        row = "  ".join(f"{att[i, j].item():.2f}" for j in range(N_FIELDS))92        print(f"{name[:8]:>8}  " + row)93    print("\n  对角线较大表示 field 保留自身信息;非对角元越大,说明该 field 越依赖其他 field。")94    print("  权重随样本变化正是 Attention 与固定 Cross 结构的关键区别。")9596    print_comparison([97        ("Baseline MLP", base_auc, count_params(BaselineMLP())),98        ("AutoInt(1 block, 2 heads)", auc, count_params(AutoInt())),99    ])100101102if __name__ == "__main__":103    main()

如何用于学习或编写新代码

  1. 先看章节 README 的依赖和运行成本,定位这个例子的输入、输出及关键函数。
  2. 在原书 PDF 中找到对应概念的具体页;代码演示与书中论断分开标注。
  3. 只在依赖和耗时可控时运行单个示例,修改一个参数并记录真实输出与失败情形。
  4. 参考实现新代码时说明复用、改动、测试与尚未完成的生产依赖;教学示例并非生产系统。