DCN (Deep & Cross Network)学习日记
·
背景与动机
DeepFM 的局限
DeepFM = FM (二阶) + DNN (隐式高阶)
问题:
- FM 只显式建模二阶交互
- DNN 虽然能学高阶,但隐式、不可解释
- xDeepFM 的 CIN 虽然显式,但计算复杂、参数量大
DCN 的创新
核心思想: 用 Cross Network 显式建模特征交叉
DCN = Deep (线性) + Cross Network (显式交叉) + DNN (隐式高阶)
三大组件:
- Deep 部分 - 线性模型,记忆低阶特征
- Cross Network - 显式建模特征交叉
- Tower 部分 - 捕捉非线性
演进历史
FM (2010)
↓ 只能建模二阶交互
DeepFM (2017)
↓ 加入 DNN 隐式高阶
xDeepFM (2018)
↓ CIN 显式高阶,但复杂
DCN (2019) ⭐
↓ Cross Network 显式交叉,更简单
核心创新:Cross Network
Cross Network 是什么?
Cross Network = 显式建模特征交叉的网络
类似于 CIN,但计算更简单、更高效。
Cross Layer 的计算公式
数学公式:
xₗ₊₁ = xₗ ⊙ wₖ₊₁ + xₗ + bₖ₊₁
其中:
- xₗ: 第 l 层的输入
- wₖ₊₁: 第 l+1 层的权重矩阵
- bₖ₊₁: 第 l+1 层的偏置
- ⊙: 逐元素相乘
逐元素相乘 (Hadamard Product):
xₗ = [a₁, a₂, a₃]
w = [w₁, w₂, w₃]
xₗ ⊙ w = [a₁×w₁, a₂×w₂, a₃×w₃]
Cross Network 的层叠
Cross Layer 1:
输入: 原始特征
输出: 第 1 层交叉特征
Cross Layer 2:
输入: 原始特征 + 第 1 层交叉特征
输出: 第 2 层交叉特征
...
Cross Network vs CIN 对比
| 维度 | CIN | Cross Network |
|---|---|---|
| 计算公式 | 复杂的压缩 | 简单的线性 |
| 参数量 | n × n × k | n × k × L |
| 表达能力 | 更强 | 足够强 |
| 计算效率 | 较慢 | 更快 |
为什么 DCN 更流行?
- 计算简单:线性公式,无压缩
- 参数适中:比 CIN 少
- 显式交叉:保持可解释性
- 性能好:训练和推理速度快
DCN vs xDeepFM/DeepFM 对比
核心区别
| 模型 | Deep 部分 | 高阶部分 |
|---|---|---|
| DeepFM | FM 隐向量内积 | DNN 隐式 |
| xDeepFM | - | CIN 显式 |
| DCN | 线性模型 | Cross Network 显式 |
显式交叉对比
| 模型 | 交叉网络 | 计算复杂度 |
|---|---|---|
| xDeepFM | CIN (复杂) | 高 |
| DCN | Cross Network (简单) | 低 |
参数量对比
假设:
- 特征数 n = 5
- 隐向量维度 k = 8
- 交叉层数 L = 3
| 模型 | 交叉网络参数 | 总参数量 |
|---|---|---|
| DeepFM | 0 | ~5K |
| xDeepFM | CIN: L × n × n × k | ~15K |
| DCN | L × n × k | ~120 |
模型架构
整体结构
输入特征 (离散索引)
↓
Embedding 层 (稀疏→稠密)
↓
┌───────────────────┼───────────────────┐
↓ ↓ ↓
Deep 部分 Cross Network Tower 部分
(线性记忆) (显式交叉) (隐式非线性)
↓ ↓ ↓
Deep 输出 Cross 输出 Tower 输出
└───────────────────┴───────────────────┘
↓
最终输出层
各组件详解
1. Embedding 层
将离散特征索引映射到稠密向量
用户 123 → [0.23, 0.15, ...] # k 维
广告 45 → [0.67, 0.32, ...]
2. Deep 部分
线性模型,记忆低阶特征
Deep 输出 = Σᵢ wᵢxᵢ + b₀
类似 LR,但共享 Embedding。
3. Cross Network 部分
多层 Cross Layer,显式建模特征交叉
单层 Cross Layer:
xₗ₊₁ = xₗ ⊙ wₖ₊₁ + xₗ + bₖ₊₁
多层堆叠:
Cross Layer 1: x₁ = x₀ ⊙ w₁ + x₀ + b₁
Cross Layer 2: x₂ = x₀ ⊙ w₂ + x₁ + b₂
Cross Layer 3: x₃ = x₀ ⊙ w₃ + x₂ + b₃
关键: 每层都连接到原始输入 x₀
4. Tower 部分
隐式学习复杂的高阶非线性关系
Tower 输出 = MLP(Deep 输出 + Cross 输出)
参数量计算
假设:
- 特征数 n = 5
- 隐向量维度 k = 8
- Cross 层数 L = 3
- Tower 隐藏层 [64, 32]
各部分参数量:
Embedding: Σ(feature_dim × k)
Deep: n × k
Cross Network: L × n × k
Tower: 适当的参数
代码实现
Cross Layer 实现
class CrossLayer(nn.Module):
"""
Cross Layer: 显式建模特征交叉
公式:
xₗ₊₁ = xₗ ⊙ wₖ₊₁ + xₗ + bₖ₊₁
"""
def __init__(self, num_features, embedding_dim=8):
super().__init__()
self.num_features = num_features
self.embedding_dim = embedding_dim
# ==================== 权重矩阵 ====================
# 形状: (num_features, embedding_dim)
self.w = nn.Parameter(torch.randn(num_features, embedding_dim) * 0.01)
# 偏置: (embedding_dim,)
self.b = nn.Parameter(torch.zeros(embedding_dim))
def forward(self, x):
"""
Args:
x: (batch_size, num_features, embedding_dim) 前一层的输出
Returns:
output: (batch_size, num_features, embedding_dim) 当前层输出
"""
# ==================== Cross 操作 ====================
# x₀ ⊙ w: (batch_size, num_features, embedding_dim)
cross = x * self.w.unsqueeze(0)
# ==================== Residual 连接 ====================
# x₀ ⊙ w + x₀: (batch_size, num_features, embedding_dim)
output = cross + x + self.b
return output
DCN 实现
import torch
import torch.nn as nn
class DCN(nn.Module):
"""
Deep & Cross Network (DCN)
核心创新:
Cross Network 显式建模特征交叉
模型结构:
Deep (线性) + Cross Network (显式交叉) + Tower (隐式非线性)
"""
def __init__(self, feature_dims, embedding_dim=8,
num_cross_layers=3, hidden_dims=[64, 32]):
"""
Args:
feature_dims: 每个特征的可能取值数列表
embedding_dim: embedding 向量维度
num_cross_layers: Cross Network 的层数
hidden_dims: Tower 的隐藏层维度列表
"""
super().__init__()
self.feature_dims = feature_dims
self.num_features = len(feature_dims)
self.embedding_dim = embedding_dim
# ==================== Embedding 层 ====================
self.embeddings = nn.ModuleList([
nn.Embedding(dim, embedding_dim) for dim in feature_dims
])
# ==================== Deep 部分 ====================
# 线性模型: Σᵢ wᵢxᵢ + b₀
self.deep = nn.Linear(self.num_features, 1)
# ==================== Cross Network ====================
self.cross_layers = nn.ModuleList()
# 创建多层 Cross Layer
for _ in range(num_cross_layers):
self.cross_layers.append(
CrossLayer(self.num_features, embedding_dim)
)
# ==================== Tower 部分 ====================
# Tower 输入维度: num_features × embedding_dim × (num_cross_layers + 1)
# (原始 + 各层 Cross 输出)
tower dnn_input_dim = self.num_features * embedding_dim * (num_cross_layers + 1)
# 构建 Tower 层
tower_layers = []
for hidden_dim in hidden_dims:
tower_layers.append(nn.Linear(tower_input_dim, hidden_dim))
tower_layers.append(nn.ReLU())
tower_layers.append(nn.BatchNorm1d(hidden_dim))
tower_input_dim = hidden_dim
# Tower 输出层
tower_layers.append(nn.Linear(tower_input_dim, 1))
self.tower = nn.Sequential(*tower_layers)
def forward(self, x):
"""
Args:
x: (batch_size, num_features) 离散特征索引
Returns:
logits: (batch_size, 1) 预测分数
"""
batch_size = x.shape[0]
# ==================== Embedding ====================
embedded_features = []
for i, emb in enumerate(self.embeddings):
emb_i = emb(x[:, i])
embedded_features.append(emb_i)
all_embeddings = torch.cat(embedded_features, dim=1)
all_embeddings = all_embeddings.view(
batch_size, self.num_features, self.embedding_dim
)
# ==================== Cross Network ====================
# 保存各层输出,包括原始 embedding
cross_outputs = [all_embeddings]
for cross in self.cross_layers:
# 每层都连接到原始输入
layer_output = cross(all_embeddings)
cross_outputs.append(layer_output)
# ==================== Deep 部分 ====================
deep_output = self.deep(x.float())
# ==================== Tower 部分 ====================
# 拼接 Deep 输出和所有 Cross 输出
# (原始 + 各层 Cross 输出)
tower_input = torch.cat([all_embeddings] + cross_outputs, dim=1)
tower_input = tower_input.view(batch_size, -1)
# 通过 Tower 网络
tower_output = self.tower(tower_input)
# ==================== 合并输出 ====================
# DCN = Deep + Tower
output = deep_output + tower_output
return output
# ==================== 使用示例 ====================
if __name__ == '__main__':
# 特征定义
feature_dims = [1000, 500, 5, 4, 10]
# 创建 DCN 模型
model = DCN(
feature_dims=feature_dims,
embedding_dim=8,
num_cross_layers=3,
hidden_dims=[64, 32]
)
print('=== DCN 模型结构 ===')
print(model)
# ==================== 参数量分析 ====================
total_params = sum(p.numel() for p in model.parameters())
embedding_params = sum(p.numel() for p in model.embeddings.parameters())
deep_params = sum(p.numel() for p in model.deep.parameters())
cross_params = sum(p.numel() for p in model.cross_layers.parameters())
tower_params = sum(p.numel() for p in model.tower.parameters())
print(f'\n参数量分析:')
print(f' 总参数量: {total_params:,}')
print(f' Embedding: {embedding_params:,}')
print(f' Deep 部分: {deep_params:,}')
print(f' Cross Network: {cross_params:,}')
print(f' Tower 部分: {tower_params:,}')
# ==================== 生成训练数据 ====================
batch_size = 32
x = torch.tensor([
[torch.randint(0, dim, size=(1,)).item() for dim in feature_dims]
for _ in range(batch_size)
])
y = torch.randint(0, 2, (batch_size, 1), dtype=torch.float32)
# ==================== 训练配置 ====================
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
print(f'\n=== 开始训练 ===')
print(f'batch_size: {batch_size}')
print(f'特征数: {model.num_features}')
print(f'Cross 层数: {len(model.cross_layers)}')
# ==================== 训练循环 ====================
for epoch in range(100):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
print(f'Epoch {epoch + 1:3d}, Loss: {loss.item():.6f}')
# ==================== 预测示例 ====================
model.eval()
with torch.no_grad():
test_x = torch.tensor([[
torch.randint(0, dim, size=(1,)).item() for dim in feature_dims
]])
logits = model(test_x)
click_prob = torch.sigmoid(logits)
print(f'\n=== 预测结果 ===')
print(f'模型输出 (logits): {logits.item():.4f}')
print(f'点击概率 (sigmoid): {click_prob.item():.4f}')
# ==================== Cross Network 工作原理示例 ====================
print(f'\n=== Cross Network 工作原理示例 ===')
print('第 1 层:')
print('x₁ = x₀ ⊙ w₁ + x₀ + b₁')
print('其中: x₀ ⊙ w₁ 是显式特征交叉')
print(' + x₀ 是 residual 连接')
print('\n第 2 层:')
print('x₂ = x₀ ⊙ w₂ + x₁ + b₂')
print(' ^^^ 连接到原始输入,而非上一层')
print('\n优势: 简单的计算公式 + Residual 连接 + 显式交叉')
面试常见问题
Q1: DCN 的核心优势是什么?
A:
- 简单高效:Cross Network 公式简单,计算快
- 显式交叉:可解释的特征交叉
- 参数适中:比 xDeepFM 少,比 DeepFM 多
- 性能好:训练和推理速度快
Q2: Cross Network 和 CIN 的区别?
A:
| 对比维度 | CIN | Cross Network |
|---|---|---|
| 计算方式 | 压缩 | 线性逐元素相乘 |
| 公式 | 复杂 | 简单 |
| 参数量 | L × n × n × k | L × n × k |
| 表达能力 | 更强 | 足够强 |
Q3: 为什么每层都连接到原始输入?
A:
第 1 层: x₁ = f(x₀)
第 2 层: x₂ = f(x₀, x₁) # 连接到 x₀
第 3 层: x₃ = f(x₀, x₂) # 连接到 x₀
原因:
- 防止深层网络退化
- 保留原始特征信息
- 类似 ResNet 的思想
Q4: Deep 部分的作用?
A:
类似 Linear/Logistic Regression,记忆低阶特征的影响。
公式:
Deep 输出 = Σᵢ wᵢxᵢ + b₀
Q5: Tower 部分的作用?
A:
隐式学习复杂的高阶非线性关系。
类似 DeepFM 的 DNN 部分,但输入更丰富(包含 Cross 输出)。
Q6: DCN 的适用场景?
A:
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 需要显式交叉 | ✅ 推荐 | Cross Network 提供 |
| 计算效率重要 | ✅ 推荐 | 比 xDeepFM 快 |
| 数据量大 | ✅ 推荐 | 参数适中 |
| 简单场景 | ❌ 不推荐 | DeepFM 足够 |
| 最强表达力 | ❌ 不推荐 | xDeepFM 更强 |
Q7: 如何调优 Cross 层数?
A:
| 层数 | 效果 | 参数量 |
|---|---|---|
| 1 层 | 二阶交叉 | 少 |
| 2 层 | 三阶交叉 | 中 |
| 3+ 层 | 更高阶 | 多 |
建议:
- 从 2-3 层开始
- 根据验证集效果调整
- 层数过多容易过拟合
Q8: DCN 的主要优势总结?
A:
- Cross Network:显式、简单、高效的特征交叉
- Deep 部分:线性记忆
- Tower 部分:隐式高阶非线性
- 参数量:适中,比 xDeepFM 少
- 性能:计算快,训练和推理快
模型对比总结
FM (2010)
↓ 二阶交互
DeepFM (2017)
↓ FM + DNN
xDeepFM (2018)
↓ CIN 显式高阶(复杂)
DCN (2019) ⭐
↓ Cross Network 显式(简单)
AutoInt (2020)
↓ 自动交互阶数
快速检查清单
理解 DCN,你应该能回答:
- 解释 DCN 的三大组件
- 说明 Cross Network 的计算公式
- 理解逐元素相乘
- 了解 Residual 连接的作用
- 比较 DCN vs xDeepFM
- 计算 DCN 的参数量
- 知道如何调优 Cross 层数
- 能从零实现简单的 DCN
参考资料
- DCN 原始论文: https://arxiv.org/abs/1708.00144
- Cross Network: https://arxiv.org/abs/1708.00144
- 推荐系统模型库: https://github.com/shenweichen/DeepCTR
实现案例(CTR预测)
import torch
import torch.nn as nn
class CrossLayer(nn.Module):
"""
Cross Layer: 显式建模特征交叉
"""
def __init__(self, num_features, embedding_dim=8):
super().__init__()
# 权重矩阵: (embedding_dimkt, embedding_dim)
self.w = nn.Parameter(torch.randn(embedding_dim, embedding_dim) * 0.01)
self.b = nn.Parameter(torch.zeros(embedding_dim))
def forward(self, x0, xl):
"""
x0: (batch_size, num_features, embedding_dim) - 原始输入
xl: (batch_size, num_features, embedding_dim) - 当前层输入
output: (batch_size, num_features, embedding_dim)
"""
batch_size, num_features, embedding_dim = x0.shape
# Reshape for computation: (batch_size * num_features, embedding_dim)
xl_flat = xl.view(-1, embedding_dim)
# Compute xl^T * W * xl: (batch_size * num_features, embedding_dim)
cross_term = torch.matmul(xl_flat, self.w)
# Add bias and reshape back
cross_term = cross_term + self.b
cross_term = cross_term.view(batch_size, num_features, embedding_dim)
# Element-wise multiply with original input: x0 * (cross_term + b) + x0
output = x0 * cross_term + xl
return output
class DCN(nn.Module):
"""
Deep & Cross Network (DCN)
架构: Deep(线性) + Cross Network(显式交叉) + Tower(高阶)
"""
def __init__(self, feature_dims, embedding_dim=8,
num_cross_layers=3, hidden_dims=[64, 32]):
super().__init__()
self.feature_dims = feature_dims
self.num_features = len(feature_dims)
self.embedding_dim = embedding_dim
# Embedding 层
self.embeddings = nn.ModuleList([
nn.Embedding(dim, embedding_dim) for dim in feature_dims
])
# Deep 部分
self.deep = nn.Linear(self.num_features, 1)
# Cross Network
self.cross_layers = nn.ModuleList()
for _ in range(num_cross_layers):
self.cross_layers.append(
CrossLayer(self.num_features, embedding_dim)
)
# Tower 部分
tower_input_dim = self.num_features * embedding_dim
tower_layers = []
for hidden_dim in hidden_dims:
tower_layers.append(nn.Linear(tower_input_dim, hidden_dim))
tower_layers.append(nn.ReLU())
tower_layers.append(nn.BatchNorm1d(hidden_dim))
tower_input_dim = hidden_dim
tower_layers.append(nn.Linear(tower_input_dim, 1))
self.tower = nn.Sequential(*tower_layers)
def forward(self, x):
"""
x: (batch_size, num_features)
output: (batch_size, 1)
"""
batch_size = x.shape[0]
# Embedding
embedded_features = []
for i, emb in enumerate(self.embeddings):
emb_i = emb(x[:, i])
embedded_features.append(emb_i)
all_embeddings = torch.cat(embedded_features, dim=1)
all_embeddings = all_embeddings.view(
batch_size, self.num_features, self.embedding_dim
)
# Cross Network
x0 = all_embeddings
xl = x0
for cross in self.cross_layers:
xl = cross(x0, xl)
cross_output = xl
# Flatten for Tower
cross_output = cross_output.view(batch_size, -1)
# Deep 部分 - 使用平均池化的embeddings
deep_output = self.deep(all_embeddings.mean(dim=2))
# Tower 部分
tower_output = self.tower(cross_output)
# 合并输出
output = deep_output + tower_output
return output
if __name__ == '__main__':
# 特征定义
feature_dims = [1000, 500, 5, 4, 10]
model = DCN(
feature_dims=feature_dims,
embedding_dim=8,
num_cross_layers=3,
hidden_dims=[64, 32]
)
print('=== DCN 模型结构 ===')
print(model)
# 参数量分析
total_params = sum(p.numel() for p in model.parameters())
embedding_params = sum(p.numel() for p in model.embeddings.parameters())
deep_params = sum(p.numel() for p in model.deep.parameters())
cross_params = sum(p.numel() for p in model.cross_layers.parameters())
tower_params = sum(p.numel() for p in model.tower.parameters())
print(f'\n参数量分析:')
print(f' 总参数量: {total_params:,}')
print(f' Embedding: {embedding_params:,}')
print(f' Deep 部分: {deep_params:,}')
print(f' Cross Network: {cross_params:,}')
print(f' Tower 部分: {tower_params:,}')
# 生成训练数据
batch_size = 32
x = torch.tensor([
[torch.randint(0, dim, size=(1,)).item() for dim in feature_dims]
for _ in range(batch_size)
])
y = torch.randint(0, 2, (batch_size, 1), dtype=torch.float32)
# 训练
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
print(f'\n=== 开始训练 ===')
print(f'batch_size: {batch_size}')
print(f'特征数: {model.num_features}')
print(f'Cross 层数: {len(model.cross_layers)}')
for epoch in range(4000):
pred = model(x)
loss = criterion(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
print(f'Epoch {epoch + 1:3d}, Loss: {loss.item():.6f}')
# 预测
model.eval()
with torch.no_grad():
test_x = torch.tensor([[
torch.randint(0, dim, size=(1,)).item() for dim in feature_dims
]])
logits = model(test_x)
click_prob = torch.sigmoid(logits)
print(f'\n=== 预测结果 ===')
print(f'模型输出 (logits): {logits.item():.4f}')
print(f'点击概率 (sigmoid): {click_prob.item():.4f}')
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)