
这篇文章是微软于2016年提出的神经网络模型,目标是解决大规模点击率预估(CTR)的任务,自动完成特征交互建模,替代人工设计的组合特征工程。
在广告推荐系统中,准确预测用户是否会点击某个广告是核心任务。传统CTR模型(如LR, GBDT)严重依赖手工特征工程,尤其是组合交叉特征,比如“用户年龄 + 广告位置”。手工特征存在着许多问题,例如:手工组合容易漏掉有效的高阶组合,无法迁移泛化到新场景。因此,本文提出了Deep Crossing模型,结合深度学习的表达能力,自动学习特征交叉,在不依赖显式人工组合的前提下获得更强泛化性能。
💬 面试可能提问:
广告推荐中包含大量类别型特征(如用户年龄段、广告位、兴趣标签),这些特征本身独立表达能力有限,但它们之间的组合往往能捕捉更强的语义,比如「年轻人 + 晚上 + 游戏广告」这种组合可能比单独的特征更能反映用户点击行为。
LR(逻辑回归):只能建模线性关系,若需非线性交叉,必须手工构造组合特征,成本高、不全面。
GBDT:虽然可以自动学出一些非线性关系,但对高阶组合和稀疏类别特征的表达仍然有限。
随着网络加深,训练过程容易出现 梯度消失 或 退化问题(网络越深反而性能下降)。ResNet 提出的残差连接(skip connection)可以:
保留原始输入信息(identity mapping) 缓解深层网络训练难题 加速收敛,提高最终精度
Embedding, Stackding, Residual Unit, Scoring Layer
目的是将高维稀疏特征降维。与
Word2Vec不同,Deep Crossing中的Embedding是模型的整体优化动态调整输入向量表达的权重。而当特征维度低于256时,则采取直接扩展的策略,直接输入到Stacking层,其中,max(0,f(x))表示ReLU激活函数
直接对输入向量进行堆叠
class ResidualBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super(ResidualBlock, self).__init__()
self.fc1 = nn.Linear(input_dim, output_dim)
self.fc2 = nn.Linear(output_dim, output_dim)
self.layer_norm = nn.LayerNorm(output_dim)
def forward(self, x):
residual = x
x = F.relu(self.fc1(x))
x = self.fc2(x)
# 形状不同则表示是第一层,需要先映射到相同维度
if x.shape[1] == residual.shape[1]:
x = self.layer_norm(x + residual)
x = F.relu(x)
return xdef __init__(self, sparse_feature_sizes, dense_feature_dim, embedding_dim=16):
# 处理高维特征的Embedding层
self.embedding_layers = nn.ModuleList([
nn.Embedding(size, embedding_dim) for size in sparse_feature_sizes
])
self.dense_input_dim = dense_feature_dim
# 所有输入特征维度
self.input_dim = len(sparse_feature_sizes) * embedding_dim + self.dense_input_dim
def forward(self, sparse_inputs, dense_inputs):
# Embedding层
embeddings = [layer(sparse_inputs[:, i]) for i, layer in enumerate(self.embedding_layers)]
# Stacking层
sparse_embedded = torch.cat(embeddings, dim=-1)
combined_inputs = torch.cat([sparse_embedded, dense_inputs], dim=-1)class DeepCrossing(nn.Module):
def __init__(self, sparse_feature_sizes, dense_feature_dim, embedding_dim=16, hidden_units=[256, 256, 256]):
super(DeepCrossing, self).__init__()
# 处理高维特征的Embedding层
self.embedding_layers = nn.ModuleList([
nn.Embedding(size, embedding_dim) for size in sparse_feature_sizes
])
self.dense_input_dim = dense_feature_dim
# 所有输入特征维度
self.input_dim = len(sparse_feature_sizes) * embedding_dim + self.dense_input_dim
# Residual blocks
self.residual_blocks = nn.Sequential(
*[ResidualBlock(self.input_dim if i == 0 else hidden_units[i - 1], hidden_units[i]) for i in
range(len(hidden_units))]
)
self.output_layer = nn.Linear(hidden_units[-1], 1)
def forward(self, sparse_inputs, dense_inputs):
# Embedding层
embeddings = [layer(sparse_inputs[:, i]) for i, layer in enumerate(self.embedding_layers)]
# Stacking层
sparse_embedded = torch.cat(embeddings, dim=-1)
combined_inputs = torch.cat([sparse_embedded, dense_inputs], dim=-1)
# 将特征输入到ResNet中
x = self.residual_blocks(combined_inputs)
logits = self.output_layer(x)
# 原文最后是通过sigmoid激活函数进行输出
output = torch.sigmoid(logits)
return output
class ResidualBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super(ResidualBlock, self).__init__()
self.fc1 = nn.Linear(input_dim, output_dim)
self.fc2 = nn.Linear(output_dim, output_dim)
self.layer_norm = nn.LayerNorm(output_dim)
def forward(self, x):
residual = x
x = F.relu(self.fc1(x))
x = self.fc2(x)
if x.shape[1] == residual.shape[1]:
x = self.layer_norm(x + residual)
x = F.relu(x)
return x原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。