首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【论文精读 | Deep Crossing Web-Scale Modeling without Manually Crafted Combinatorial】

【论文精读 | Deep Crossing Web-Scale Modeling without Manually Crafted Combinatorial】

原创
作者头像
九年义务漏网鲨鱼
发布2025-07-16 19:55:27
发布2025-07-16 19:55:27
7440
举报
文章被收录于专栏:论文精读论文精读

这篇文章是微软于2016年提出的神经网络模型,目标是解决大规模点击率预估(CTR)的任务,自动完成特征交互建模,替代人工设计的组合特征工程。

一、研究动机

在广告推荐系统中,准确预测用户是否会点击某个广告是核心任务。传统CTR模型(如LR, GBDT)严重依赖手工特征工程,尤其是组合交叉特征,比如“用户年龄 + 广告位置”。手工特征存在着许多问题,例如:手工组合容易漏掉有效的高阶组合,无法迁移泛化到新场景。因此,本文提出了Deep Crossing模型,结合深度学习的表达能力,自动学习特征交叉,在不依赖显式人工组合的前提下获得更强泛化性能。

💬 面试可能提问:

  1. 为什么广告推荐中需要特征交叉?

广告推荐中包含大量类别型特征(如用户年龄段、广告位、兴趣标签),这些特征本身独立表达能力有限,但它们之间的组合往往能捕捉更强的语义,比如「年轻人 + 晚上 + 游戏广告」这种组合可能比单独的特征更能反映用户点击行为。

  1. GBDT/LR做特征交叉有什么缺陷?

LR(逻辑回归):只能建模线性关系,若需非线性交叉,必须手工构造组合特征,成本高、不全面。

GBDT:虽然可以自动学出一些非线性关系,但对高阶组合和稀疏类别特征的表达仍然有限。

  1. 为什么用神经网络可以“自动学习交叉”?

随着网络加深,训练过程容易出现 梯度消失退化问题(网络越深反而性能下降)。ResNet 提出的残差连接(skip connection)可以:

保留原始输入信息(identity mapping) 缓解深层网络训练难题 加速收敛,提高最终精度

二、模型

  • 模型架构主要包括了:Embedding, Stackding, Residual Unit, Scoring Layer
  • 损失函数:交叉熵损失
y=-\frac{1}{N}\sum_{i=1}^N (y_i\log(p_i)+(1-y_i)\log(1-p_i))
  • Embedding

目的是将高维稀疏特征降维。与Word2Vec不同,Deep Crossing中的Embedding是模型的整体优化动态调整输入向量表达的权重。而当特征维度低于256时,则采取直接扩展的策略,直接输入到Stacking层,其中,max(0,f(x))表示ReLU激活函数

X_j^O=max(0,W_jX_j^O+b_j)
  • Stacking

直接对输入向量进行堆叠

三、Pytorch实现

  • Redisual Unit
代码语言:python
复制
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
  • Embedding & Stacking
代码语言:python
复制
def __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)
  • 完整代码
代码语言:python
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、研究动机
  • 二、模型
  • 三、Pytorch实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档