CH 15 / SOURCE EXAMPLE

dcn_v2.py

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

这个示例讲什么

│ ├── dcn_v2.py §15.5.2 DCN-V2:满矩阵 / 低秩(含 r=0 退化为向量版)

DCN-V2(第15章 §15.5.2) 把 DCN 的向量参数化改成矩阵: x_{l+1} = x_0 ⊙ (W_l x_l + b_l) + x_l 参数量从 O(Ld) 升到 O(Ld²),表达能力更强;工业上常用低秩分解 W_l ≈ U_l V_l^T,U_l, V_l ∈ R^{d×r} 在效果与显存之间折中。 运行:python3 ch04_feature_interaction/dcn_v2.py

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

源码结构与阅读入口

  1. MatrixCrossLayer · 第 29 行三种参数化方式:
  2. DCNV2 · 第 61 行
  3. main · 第 79 行

完整源码 · 104 行

1"""2DCN-V2(第15章 §15.5.2)34把 DCN 的向量参数化改成矩阵:5    x_{l+1} = x_0 ⊙ (W_l x_l + b_l) + x_l6参数量从 O(Ld) 升到 O(Ld²),表达能力更强;工业上常用低秩分解7    W_l ≈ U_l V_l^T,U_l, V_l ∈ R^{d×r}8在效果与显存之间折中。910运行:python3 ch04_feature_interaction/dcn_v2.py11"""1213from __future__ import annotations1415import pathlib16import sys1718import torch19import torch.nn as nn2021sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))2223from _train_utils import (  # noqa: E40224    N_FIELDS, MLP, BaselineMLP, count_params, embedding_table, print_comparison,25    run_experiment, stack_embeddings,26)272829class MatrixCrossLayer(nn.Module):30    """31    三种参数化方式:32        rank=None  : 满矩阵 W ∈ R^{d×d}33        rank=r>0   : 低秩 W = U V,U∈R^{d×r}, V∈R^{r×d}34        rank=0     : 退化为 DCN 的向量形式 x0 ⊙ (w ⊙ xl),即 W = w 的对角矩阵35    rank=0 的分支单独实现,避免创建 0 维 Linear 触发 PyTorch 的空张量警告。36    """3738    def __init__(self, dim: int, rank: int | None = None):39        super().__init__()40        self.rank = rank41        if rank is None:42            self.w = nn.Linear(dim, dim, bias=True)43        elif rank == 0:44            self.vec = nn.Parameter(torch.empty(dim))45            nn.init.normal_(self.vec, std=1e-3)46            self.bias = nn.Parameter(torch.zeros(dim))47        else:48            self.u = nn.Linear(dim, rank, bias=False)49            self.v = nn.Linear(rank, dim, bias=True)5051    def forward(self, x0, xl):52        if self.rank is None:53            wx = self.w(xl)54        elif self.rank == 0:55            wx = (xl * self.vec).sum(1, keepdim=True).expand_as(xl) + self.bias56        else:57            wx = self.v(self.u(xl))58        return x0 * wx + xl596061class DCNV2(nn.Module):62    def __init__(self, dim: int = 4, n_cross: int = 2, rank: int | None = None, hidden=(32,)):63        super().__init__()64        self.emb = embedding_table(dim)65        d = dim * N_FIELDS66        self.cross = nn.ModuleList([MatrixCrossLayer(d, rank) for _ in range(n_cross)])67        self.deep = MLP(d, hidden, out_dim=None, dropout=0.2)68        self.head = nn.Linear(d + hidden[-1], 1)6970    def forward(self, x):71        x0 = stack_embeddings(self.emb, x).flatten(1)72        xl = x073        for layer in self.cross:74            xl = layer(x0, xl)75        h = self.deep(x0)76        return self.head(torch.cat([xl, h], dim=1)).squeeze(-1)777879def main() -> None:80    d = 4 * N_FIELDS81    print(f"输入维度 d = {d}(10 个 field × 4 维)\n")82    print("Cross 层参数量随参数化方式的变化(2 层):")83    print(f"  DCN 向量版      : {2 * 2 * d:,}")84    print(f"  DCN-V2 满矩阵   : {2 * d * d + 2 * d:,}({d}x{d} 权重 + bias)")85    print(f"  DCN-V2 低秩 r=8 : {2 * (d * 8 + 8 * d + d):,}")86    print("  → 满矩阵是向量版的 d 倍;d=80 时还能承受,d=1024 时就会 OOM。\n")8788    _, a_vec, _ = run_experiment(lambda: DCNV2(rank=0), "DCN 向量版(r=0)", steps=200)89    _, a_full, _ = run_experiment(lambda: DCNV2(rank=None), "DCN-V2-full", steps=200)90    _, a_base, _ = run_experiment(BaselineMLP, "Baseline", steps=200)91    _, a_lr, _ = run_experiment(lambda: DCNV2(rank=8), "DCN-V2-lowrank(r=8)", steps=200)9293    print_comparison([94        ("Baseline MLP", a_base, count_params(BaselineMLP())),95        ("DCN 向量版(r=0 特例)", a_vec, count_params(DCNV2(rank=0))),96        ("DCN-V2 低秩 r=8", a_lr, count_params(DCNV2(rank=8))),97        ("DCN-V2 满矩阵", a_full, count_params(DCNV2(rank=None))),98    ])99    print("\n读法:满矩阵参数量最多,但不一定显著更好——正文强调 DCN-V2 的工业价值"100          "在于'把表达能力与成本一起考虑'(§15.5.2)。")101102103if __name__ == "__main__":104    main()

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

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