Deep & Wide模型在PyTorch中的实现与优化指南

Deep & Wide模型在PyTorch中的实现与优化指南

Deep & Wide模型作为推荐系统领域的经典架构,通过结合深度神经网络(Deep)与线性模型(Wide)的优势,有效解决了传统推荐系统中记忆(Memorization)与泛化(Generalization)的平衡问题。本文将基于PyTorch框架,从模型原理、代码实现、训练优化到工业部署,系统性地介绍该模型的技术细节与实践经验。

一、Deep & Wide模型核心原理

1.1 模型架构设计

Deep & Wide模型由两部分组成:

  • Wide部分:线性模型(如逻辑回归),直接学习特征间的显式交互(如交叉特征),擅长捕捉高频出现的强规则模式。
  • Deep部分:深度神经网络,通过多层非线性变换学习特征的隐式交互,擅长发现低频但潜在的长尾模式。

两部分通过加权求和(或拼接后接MLP)输出最终预测值,结构示意如下:

  1. 输入特征 [Wide路径] 线性层 输出
  2. [Deep路径] 嵌入层 多层MLP 输出
  3. 合并 最终预测

1.2 优势分析

  • 记忆与泛化平衡:Wide部分保留历史规律,Deep部分探索新模式。
  • 工程可行性:相比纯深度模型,Wide部分可显式解释关键特征组合。
  • 训练效率:Wide部分收敛快,Deep部分可并行计算。

二、PyTorch实现步骤

2.1 环境准备

  1. import torch
  2. import torch.nn as nn
  3. import torch.optim as optim
  4. from torch.utils.data import Dataset, DataLoader
  5. # 检查GPU
  6. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

2.2 数据预处理

假设输入包含数值特征、类别特征和交叉特征:

  1. class CustomDataset(Dataset):
  2. def __init__(self, numerical_data, categorical_data, labels):
  3. self.numerical = torch.FloatTensor(numerical_data)
  4. self.categorical = torch.LongTensor(categorical_data) # 需预先编码
  5. self.labels = torch.FloatTensor(labels)
  6. def __len__(self):
  7. return len(self.labels)
  8. def __getitem__(self, idx):
  9. return (
  10. self.numerical[idx],
  11. self.categorical[idx],
  12. self.labels[idx]
  13. )

2.3 模型定义

  1. class DeepWideModel(nn.Module):
  2. def __init__(self, numerical_dim, categorical_dims, embedding_dims, hidden_dims=[256, 128]):
  3. super().__init__()
  4. # Wide部分(线性模型)
  5. self.wide = nn.Linear(numerical_dim + sum(categorical_dims), 1) # 假设交叉特征已拼接
  6. # Deep部分
  7. # 1. 嵌入层
  8. self.embeddings = nn.ModuleList([
  9. nn.Embedding(num_categories, dim)
  10. for num_categories, dim in zip(categorical_dims, embedding_dims)
  11. ])
  12. # 2. 数值特征处理
  13. self.numerical_fc = nn.Sequential(
  14. nn.Linear(numerical_dim, 64),
  15. nn.ReLU()
  16. )
  17. # 3. 合并后的MLP
  18. deep_input_dim = 64 + sum(embedding_dims) # 数值+嵌入维度
  19. self.deep_mlp = nn.Sequential(
  20. *[nn.Linear(dim_in, dim_out) for dim_in, dim_out in
  21. zip([deep_input_dim] + hidden_dims[:-1], hidden_dims)],
  22. nn.ReLU(),
  23. nn.Linear(hidden_dims[-1], 1)
  24. )
  25. def forward(self, numerical, categorical):
  26. # Wide部分
  27. wide_input = torch.cat([numerical, *categorical], dim=1) # 实际需处理交叉特征
  28. wide_out = self.wide(wide_input)
  29. # Deep部分
  30. # 1. 嵌入查找
  31. embeddings = [emb(cat) for emb, cat in zip(self.embeddings, categorical)]
  32. embedded_cat = torch.cat(embeddings, dim=1)
  33. # 2. 数值特征处理
  34. processed_num = self.numerical_fc(numerical)
  35. # 3. 合并
  36. deep_input = torch.cat([processed_num, embedded_cat], dim=1)
  37. deep_out = self.deep_mlp(deep_input)
  38. # 合并输出(可加权)
  39. return torch.sigmoid(wide_out + deep_out) # 二分类任务

2.4 训练流程

  1. def train_model(model, train_loader, val_loader, epochs=10, lr=0.001):
  2. model = model.to(device)
  3. criterion = nn.BCELoss()
  4. optimizer = optim.Adam(model.parameters(), lr=lr)
  5. for epoch in range(epochs):
  6. model.train()
  7. for numerical, categorical, labels in train_loader:
  8. numerical, categorical, labels = (
  9. numerical.to(device),
  10. [c.to(device) for c in categorical],
  11. labels.to(device)
  12. )
  13. optimizer.zero_grad()
  14. outputs = model(numerical, categorical)
  15. loss = criterion(outputs, labels.unsqueeze(1))
  16. loss.backward()
  17. optimizer.step()
  18. # 验证
  19. model.eval()
  20. val_loss = 0
  21. with torch.no_grad():
  22. for numerical, categorical, labels in val_loader:
  23. numerical, categorical, labels = (
  24. numerical.to(device),
  25. [c.to(device) for c in categorical],
  26. labels.to(device)
  27. )
  28. outputs = model(numerical, categorical)
  29. val_loss += criterion(outputs, labels.unsqueeze(1)).item()
  30. print(f"Epoch {epoch+1}, Val Loss: {val_loss/len(val_loader):.4f}")

三、关键优化策略

3.1 特征工程优化

  • Wide部分特征:需人工设计高阶交叉特征(如用户年龄×商品价格区间),可通过工具如FeatureTools自动化生成。
  • Deep部分特征:对类别特征采用哈希嵌入(Hash Embedding)减少内存占用,数值特征做分桶或标准化。

3.2 模型结构优化

  • Wide部分权重:可引入L1正则化增强稀疏性,突出关键特征。
  • Deep部分深度:根据数据规模调整层数,小数据集建议2-3层,大数据集可尝试5层以上。
  • 激活函数选择:Deep部分首层用ReLU,末层用线性输出(回归任务)或Sigmoid(分类任务)。

3.3 训练技巧

  • 学习率调度:采用torch.optim.lr_scheduler.ReduceLROnPlateau动态调整。
  • 梯度裁剪:防止Deep部分梯度爆炸。
  • 混合精度训练:使用torch.cuda.amp加速训练。

四、工业级部署建议

4.1 模型压缩

  • 量化:将FP32权重转为INT8,减少模型体积和推理延迟。
  • 剪枝:移除Deep部分中权重接近零的神经元。
  • 知识蒸馏:用大模型指导小模型训练,保持性能的同时减少参数量。

4.2 服务化部署

  • ONNX转换:将PyTorch模型导出为ONNX格式,兼容多平台推理引擎。
  • 批处理优化:合并多个请求的输入,提高GPU利用率。
  • A/B测试框架:集成到推荐系统中,实时对比Deep & Wide与其他模型的CTR/GMV指标。

五、常见问题与解决方案

5.1 过拟合问题

  • 现象:训练集AUC高,验证集AUC低。
  • 解决
    • Wide部分增加L1正则化。
    • Deep部分添加Dropout层(如nn.Dropout(0.3))。
    • 扩大训练数据量。

5.2 收敛慢问题

  • 现象:损失下降缓慢,验证指标停滞。
  • 解决
    • 检查学习率是否过大(导致震荡)或过小(收敛慢)。
    • 对Deep部分使用预训练的嵌入层(如从Word2Vec迁移)。
    • 采用学习率预热策略。

5.3 特征交互缺失

  • 现象:模型对新出现的特征组合表现差。
  • 解决
    • 定期更新Wide部分的交叉特征库。
    • 在Deep部分引入注意力机制(如Self-Attention)自动学习特征交互。

六、总结与展望

Deep & Wide模型通过结合线性模型的可解释性和深度模型的泛化能力,已成为推荐系统的标配架构。在PyTorch中实现时,需重点关注特征工程、模型结构设计和训练优化。未来方向包括:

  • 引入图神经网络(GNN)增强特征交互建模。
  • 结合强化学习实现动态权重调整。
  • 探索自动化特征交叉生成(如AutoCross)。

通过合理的设计和优化,Deep & Wide模型可在保持工程可行性的同时,持续提升推荐系统的精准度和多样性。