CH 15 / SOURCE EXAMPLE
afn.py
ch15 特征工程/code/ch04_feature_interaction/afn.py
这个示例讲什么
│ ├── afn.py §15.5.5 AFN:对数神经元与自适应阶数 O_j
AFN:自适应阶数的对数神经元(第15章 §15.5.5) 对数神经元: y = exp(Σ_i w_i ln x_i) = Π_i x_i^{w_i} 对第 j 个神经元、第 i 个 field: z_j = exp(Σ_i w_ij ln(e_i)) = Π_i e_i^{w_ij} O_j = Σ_i |w_ij| # 该神经元学到的有效交互阶数 AFN 的关键是"阶数本身是参数",不需要预先指定 2 阶还是 3 阶。Embedding 可能 为负,工程上先取绝对值并加 eps(正文提到的"数值处理"),并在前后加 BN。 运行:python3 ch04_feature_interaction/afn.py
上面来自章节 README 或源码自身的说明,不是自动验证的运行结果,也不能代替 PDF 的书中论断。讲解时请结合实际代码、输入输出与原书页码。
源码结构与阅读入口
LogTransformationLayer· 第 32 行J 个对数神经元,每个向量维度各自做一次 Π e_i^{w_ij}。AFN· 第 59 行main· 第 71 行
1"""2AFN:自适应阶数的对数神经元(第15章 §15.5.5)34对数神经元:5 y = exp(Σ_i w_i ln x_i) = Π_i x_i^{w_i}6对第 j 个神经元、第 i 个 field:7 z_j = exp(Σ_i w_ij ln(e_i)) = Π_i e_i^{w_ij}8 O_j = Σ_i |w_ij| # 该神经元学到的有效交互阶数910AFN 的关键是"阶数本身是参数",不需要预先指定 2 阶还是 3 阶。Embedding 可能11为负,工程上先取绝对值并加 eps(正文提到的"数值处理"),并在前后加 BN。1213运行:python3 ch04_feature_interaction/afn.py14"""1516from __future__ import annotations1718import pathlib19import sys2021import torch22import torch.nn as nn2324sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))2526from _train_utils import ( # noqa: E40227 FIELD_NAMES, N_FIELDS, MLP, BaselineMLP, count_params, embedding_table, print_comparison,28 run_experiment, stack_embeddings,29)303132class LogTransformationLayer(nn.Module):33 """J 个对数神经元,每个向量维度各自做一次 Π e_i^{w_ij}。"""3435 def __init__(self, n_fields: int, dim: int, n_neurons: int = 12):36 super().__init__()37 self.w = nn.Parameter(torch.empty(n_fields, n_neurons))38 # 初始化到 0 附近:起步接近"不交互",让模型自己长阶数39 nn.init.normal_(self.w, std=0.1)40 self.bn_in = nn.BatchNorm1d(n_fields * dim)41 self.bn_out = nn.BatchNorm1d(n_neurons * dim)42 self.dim = dim43 self.n_neurons = n_neurons4445 def forward(self, E: torch.Tensor) -> torch.Tensor:46 # E: [B, F, d]47 B, F, d = E.shape48 e = self.bn_in(E.flatten(1)).view(B, F, d)49 log_e = torch.log(e.abs() + 1e-3) # [B, F, d]50 # Σ_i w_ij ln(e_i) -> [B, J, d]51 z = torch.einsum("bfd,fj->bjd", log_e, self.w)52 out = torch.exp(torch.clamp(z, -6, 6)) # 防溢出53 return self.bn_out(out.flatten(1)).view(B, self.n_neurons, d)5455 def orders(self) -> list[float]:56 return [float(v) for v in self.w.abs().sum(0)]575859class AFN(nn.Module):60 def __init__(self, dim: int = 4, n_neurons: int = 12, hidden=(32,)):61 super().__init__()62 self.emb = embedding_table(dim)63 self.log_layer = LogTransformationLayer(N_FIELDS, dim, n_neurons)64 self.mlp = MLP(n_neurons * dim, hidden, out_dim=1, dropout=0.2)6566 def forward(self, x):67 z = self.log_layer(stack_embeddings(self.emb, x))68 return self.mlp(z.flatten(1)).squeeze(-1)697071def main() -> None:72 print("AFN:z_j = exp(Σ_i w_ij ln e_i),阶数 O_j = Σ_i |w_ij|(§15.5.5)\n")73 model, auc, _ = run_experiment(lambda: AFN(), "AFN", steps=600)74 _, base_auc, _ = run_experiment(BaselineMLP, "Baseline", steps=600)7576 with torch.no_grad():77 orders = model.log_layer.orders()78 print("\n各对数神经元学到的有效交互阶数 O_j:")79 for j, o in enumerate(orders):80 bar = "█" * max(1, int(o * 8))81 print(f" neuron {j:2d}: O={o:5.2f} {bar}")82 print(f"\n 阶数分布范围 {min(orders):.2f} ~ {max(orders):.2f}:"83 "模型自己选择了从近 0 阶(几乎不交互)到多阶的混合,")84 print(" 这就是'自适应阶数'——不需要人工指定最大交互阶数。")8586 print_comparison([87 ("Baseline MLP", base_auc, count_params(BaselineMLP())),88 ("AFN(12 神经元)", auc, count_params(AFN())),89 ])90 print("\n注:本实现按正文对 Embedding 取绝对值加 eps 再取对数(§15.5.5 的'数值处理')。")919293if __name__ == "__main__":94 main()如何用于学习或编写新代码
- 先看章节 README 的依赖和运行成本,定位这个例子的输入、输出及关键函数。
- 在原书 PDF 中找到对应概念的具体页;代码演示与书中论断分开标注。
- 只在依赖和耗时可控时运行单个示例,修改一个参数并记录真实输出与失败情形。
- 参考实现新代码时说明复用、改动、测试与尚未完成的生产依赖;教学示例并非生产系统。