DCN:深度交叉网络与显式高阶特征交互
引言:为什么需要特征交叉?
在推荐系统与 CTR(点击率)预估任务中,模型的核心能力之一是从原始特征中自动学习有意义的特征交互。传统线性模型(如逻辑回归)依赖于人工特征工程来构造交叉特征。而深度学习模型虽然能够通过多层全连接网络(DNN)隐式地学习高阶特征交互,但这种交互往往是隐式且低效的——模型需要大量参数和数据才能捕捉到乘法关系,且无法保证一定学到真正的高阶交叉。
2017 年,Google 提出了 DCN(Deep & Cross Network),在经典的 Wide & Deep 架构基础上,引入了一个全新的 Cross Network(交叉网络),能够以显式、可控且参数高效的方式学习有限阶的高阶特征交互。本教程将从原理、数学推导、代码实现到调参技巧,带你全面掌握 DCN 及其升级版本 DCN-V2。
一、DCN 核心构思:显式高阶特征交互
1.1 从 Wide & Deep 到 DCN
Wide & Deep 模型的“Wide”部分是一个广义线性模型,需要人工设计交叉特征(如“安装了应用 A 且年龄在 18-25 之间”)。DNN 部分则自动学习隐式交叉。这种混合架构的痛点在于:
- Wide 部分高度依赖特征工程,无法扩展到没有领域知识的新特征。
- DNN 部分学习的交叉函数可能过于复杂且难以解释。
DCN 用一个 Cross Network 替代了人工构造的 Wide 部分,该网络能够自动生成有限阶的显式交叉特征,并与并行的 DNN 结合,形成 Deep & Cross Network。其关键创新在于:交叉网络以输入特征的显式叉乘(multiplicative)形式逐层叠加,每一层都显式地学习一个 $x_0 \cdot x_l^T w$ 的残差项,从而保证模型的交叉阶数随层数严格递增。
1.2 Cross Network 的核心特征
- 显式交叉:每层输出都包含原始输入 $x_0$ 与当前层输入 $x_l$ 的外积,叉乘操作是显式的。
- 阶数可控:一个具有 $L$ 层的交叉网络,其输出最高包含 $L+1$ 阶的特征交叉。
- 参数高效:交叉网络的参数量仅为 $2 \times d \times L$($d$ 为输入维度,$L$ 为层数),远少于同等表达能力的 DNN。
- 保留线性关系:交叉网络的输出可以看作是 $x_0$ 的一个线性函数,其权重由交叉层的非线性变换动态决定。
二、交叉网络的数学原理
2.1 单层交叉运算
设输入向量 $x_0 \in \mathbb{R}^d$ 是所有嵌入拼接后的向量。第 $l$ 层交叉层的公式为:
$$ x_{l+1} = x_0 \cdot (x_l^T w_l) + b_l + x_l $$
其中:
- $w_l, b_l \in \mathbb{R}^d$ 是第 $l$ 层的权重和偏置参数。
- $x_0 \cdot (x_l^T w_l)$ 表示标量 $x_l^T w_l$ 乘以向量 $x_0$,本质上是 $x_0$ 与 $x_l$ 的某种双线性交互。
- $+ x_l$ 是残差连接,确保每一层在学习新交叉的同时保留已有信息。
关键解释:$x_l^T w_l$ 把 $x_l$ 投影为一个标量“门控值”,然后用这个值缩放原始输入 $x_0$。因为 $x_0$ 包含了所有一阶特征,所以这一操作等价于将 $x_0$ 中的每个元素与投影后的 $x_l$ 进行乘法交互,产生二阶项;随着层数叠加,会形成更高阶的交叉。
2.2 逐层展开与高阶特性
假设忽略偏置 $b_l$(其作用可被后续层吸收),展开前两层:
- 初始:$x_0$
- 第 1 层:$x_1 = x_0 (x_0^T w_0) + x_0 = x_0 (x_0^T w_0 + 1)$
此时 $x_1$ 包含 $x_0$ 的二阶项(因为 $x_0 \cdot x_0^T$ 产生二阶交叉)。 - 第 2 层:$x_2 = x_0 (x_1^T w_1) + x_1 = x_0 ((x_0 (x_0^T w_0 + 1))^T w_1) + x_1$
可以产生 $x_0^3$ 的三阶交叉。
严格的数学归纳可证明:第 $l$ 层的输出 $x_l$ 是 $x_0$ 的 $l+1$ 阶多项式函数。这正是 DCN 能够显式学习有界阶数特征交叉的数学保证。
2.3 与全连接层的对比
一个标准的全连接层 $h_{l+1} = f(W_l h_l + b_l)$ 通过非线性激活函数可以近似任意函数,包括特征叉乘。但要学到 $x_i \cdot x_j$ 这样的乘法关系,DNN 往往需要大量参数将它们映射到隐空间中才能近似。Cross Network 用结构化的设计强制了乘法交互,从而在参数量极少的情况下直接建模二阶、三阶等有限阶交叉,且交叉形式可解释:最终输出可以分解为多个交叉项的线性组合。
三、DCN 的整体模型架构
一个完整的 DCN 模型由四部分组成:
- 嵌入层:将高维稀疏类别特征映射为低维稠密向量,并与数值特征拼接,形成 $x_0$。
- Cross Network:从 $x_0$ 开始,逐层应用交叉层,输出 $x_{L_c}$($L_c$ 为交叉层数)。
- Deep Network:一个标准的多层全连接网络,输入同样的 $x_0$,输出 $h_{L_d}$。
- 输出层:将交叉网络和深度网络的输出拼接,经过一个线性变换(或无隐藏层的全连接)得到最终 logit,再通过 sigmoid 输出预测概率。
其公式表示为:
$$ y = \sigma \left( [x_{L_c}^T, h_{L_d}^T] \cdot w_{\text{logit}} + b \right) $$
这种并联结构使得交叉网络专注于特征叉乘,而深度网络则负责学习更一般化的隐式关系,二者互补。
四、DCN 的优势与局限
4.1 主要优势
- 参数效率极高:交叉部分的参数量仅为 $O(d \cdot L_c)$,在 $d$ 较大时,依然远小于等效的 DNN。
- 阶数显式可控:通过设定交叉层数 $L_c$,可以精确控制模型学习的最高特征交叉阶数。通常 $L_c=3\sim6$ 即可覆盖大多数有意义的交叉。
- 保留线性结构:交叉网络可以看作是 $x_0$ 的一种“基展开”,每个展开项是 $x_0$ 的多项式,这使得模型具有较好的记忆能力,特别适合处理那些需要精确记忆特定组合模式的场景。
- 易于解释:可以可视化交叉项的权重,分析哪些特征组被模型重点关注。
4.2 潜在局限
- 同阶交互限制:原始 DCN 的交叉网络每一层只引入 $x_0$ 与 $x_l$ 的交互,学到的交叉项本质上都是 $x_0$ 的单项式(例如 $x_i x_j x_k$),即每个特征在交叉项中出现的最高幂次为 1。这种结构被称为“bit-wise”交叉,它无法捕获像 $x_i^2$ 或更具表现力的组合。
- 对输入顺序敏感:交叉运算不是对称的,特征初始化顺序可能影响学习结果。
- 容量有限:当数据要求更复杂的非多项式交互时,仅靠交叉网络不够,必须依赖并行的 DNN。
这些局限在后续的 DCN-V2 中得到了改进(用矩阵权重代替向量权重,支持特征分组交叉),我们会在本教程末尾简要介绍。
五、从零实现 Cross Network
下面用 PyTorch 风格伪代码展示交叉网络的实现思路。实际生产环境推荐使用 TensorFlow Recommenders 或 PyTorch 的封装。
import torch
import torch.nn as nn
class CrossLayer(nn.Module):
def __init__(self, input_dim):
super().__init__()
# 权重向量 w 和偏置 b
self.w = nn.Parameter(torch.randn(input_dim, 1))
self.b = nn.Parameter(torch.randn(input_dim))
def forward(self, x0, xl):
# x0: (batch_size, input_dim) 原始输入
# xl: (batch_size, input_dim) 上一层输出
# 计算标量: xl * w -> (batch, 1)
gate = torch.matmul(xl, self.w) # (batch, 1)
# 广播乘法: x0 * gate -> (batch, input_dim)
interaction = x0 * gate
# 残差连接
return interaction + self.b + xl
class CrossNetwork(nn.Module):
def __init__(self, input_dim, num_layers):
super().__init__()
self.layers = nn.ModuleList([CrossLayer(input_dim) for _ in range(num_layers)])
def forward(self, x0):
xl = x0
for layer in self.layers:
xl = layer(x0, xl)
return xl