Wide&Deep模型与DCN模型

·4182·9 分钟·
AI摘要: 本文介绍了Wide & Deep模型和DCN(Deep & Cross Network)模型的原理、实现及其在处理特征学习中的应用。文章详细描述了Wide部分的简单线性变换和Deep部分的多层感知机结构,并解释了如何通过Cross Network改进Wide部分以显式建模特征之间的交叉。

Wide & Deep 原理

网上有很多博客讲解提到Wide部分提供记忆能力,学习简单的特征模式,而Deep部分提供泛华能力,学习复杂的特征模式。看起来云里雾里,但是直接看Wide & Deep代码就很清晰了:


class WideAndDeepModel(nn.Module):
    def __init__(self, categorical_dims, numerical_dim, embedding_dim, hidden_layers):
        super(WideAndDeepModel, self).__init__()
        # Wide 部分
        self.wide = nn.Linear(len(categorical_dims) + numerical_dim, 1)
        
        # Deep 部分
        self.embeddings = nn.ModuleList([
            nn.Embedding(dim, embedding_dim) for dim in categorical_dims
        ])
        deep_input_dim = len(categorical_dims) * embedding_dim + numerical_dim
        layers = []
        for dim in hidden_layers:
            layers.append(nn.Linear(deep_input_dim, dim))
            layers.append(nn.ReLU())
            deep_input_dim = dim
        layers.append(nn.Linear(deep_input_dim, 1))
        self.deep = nn.Sequential(*layers)

    def forward(self, categorical_data, numerical_data):
        # Wide 部分
        wide_input = torch.cat([categorical_data.float(), numerical_data], dim=1)
        wide_output = self.wide(wide_input)
        
        # Deep 部分
        embeddings = [emb(categorical_data[:, i]) for i, emb in enumerate(self.embeddings)]
        deep_input = torch.cat(embeddings + [numerical_data], dim=1)
        deep_output = self.deep(deep_input)
        
        # 合并 Wide 和 Deep 输出
        output = torch.sigmoid(wide_output + deep_output)
        return output

  • Wide部分就是一个简答的Linear线性层,非常简单直接的线性变换;
  • Deep部分就是稍微复杂一点的MLP,线性层之间套Relu激活函数而已,更深一点的神经网络罢了;
  • Wide和Deep两个部分的输出都丢到sigmoid函数中归一化;

这样就容易理解了,浅层神经网络学习浅层特征,深层神经网络学习复杂特征。

其实在CV中更好理解一点,用CNN来学习猫狗分类,浅层网络学习颜色、灰度强度等简单特征,深层网络就学习轮廓之类的复杂特征。

Wide & Deep常见问题

Deep & Cross模型

Deep & Cross模型是Wide & Deep模型最常见简单的进化版,就是将Wide部分改成Cross网络。

其实后续还有很多改进,基本都是将弱鸡的Wide部分替换为更加多样化的交叉操作,比如Cross,FM之类的模块。(传统推荐模型和深度学习模型在此刻会师)

具体来看DCN:

  • Deep部分:无需多言,基本没改,还是个DNN(深度学习神经网络,负责学习特征的复杂非线性组合)
  • Wide部分:Cross Network(交叉网络,负责显式地建模特征之间的交叉,捕捉高阶特征之间的相互作用)。

这两种网络的各自输出,然后融合起来,通常是通过简单的加权求和或者简单拼接的方式,最终的输出经过线性层和激活函数得到预测结果(和原版Wide & Deep一模一样)

Cross Network

对于输入向量 xx , 交叉网络的第 ll 层可以表示为:xl=x0(Wlxl1)T+bl+xl1x_l = x_0 (W_l x_{l - 1})^T + b_l + x_{l - 1} ,这样可以学习到不同特征之间的交叉

之所以每次的二阶交叉部分都是和x0x_0操作,是希望更多地保留原始的显示特征,避免在多层交叉之后学习到的全是深层隐式特征(这是Deep部分负责的事情)

公式懒得看,直接看模型结构图中的Wide(Cross)部分

image-20250124223404484

代码实现:



class DCN(nn.Module):
    def __init__(self, categorical_dims, numerical_dim, embedding_dim, hidden_layers, cross_layers):
        super(DCN, self).__init__()
        # 嵌入层,用于将类别特征映射到低维稠密空间
        self.embeddings = nn.ModuleList([
            nn.Embedding(dim, embedding_dim) for dim in categorical_dims
        ])
        
        # Cross Network 部分
        input_dim = len(categorical_dims) * embedding_dim + numerical_dim
        self.cross_layers = nn.ModuleList([
            nn.Linear(input_dim, input_dim) for _ in range(cross_layers)
        ])
        
        # Deep Network 部分
        deep_layers = []
        for dim in hidden_layers:
            deep_layers.append(nn.Linear(input_dim, dim))
            deep_layers.append(nn.ReLU())
            input_dim = dim
        self.deep = nn.Sequential(*deep_layers)
        
        # 输出层
        self.output = nn.Linear(hidden_layers[-1] + len(categorical_dims) * embedding_dim + numerical_dim, 1)

    def forward(self, categorical_data, numerical_data):
        # 嵌入层:对类别特征进行编码
        embeddings = [emb(categorical_data[:, i]) for i, emb in enumerate(self.embeddings)]
        embed_concat = torch.cat(embeddings, dim=1)
        
        # 拼接嵌入和数值特征作为输入
        x = torch.cat([embed_concat, numerical_data], dim=1) # [batch, Category_num * Dim + Numeric_num]
        
        # Cross Network 部分
        x_cross = x.clone() # [batch, Category_num * Dim + Numeric_num]
        for layer in self.cross_layers:
            x_cross = x + layer(x_cross)  # 层间显式交叉
        
        # Deep Network 部分
        x_deep = self.deep(x)
        
        # 合并 Cross 和 Deep 输出
        combined = torch.cat([x_cross, x_deep], dim=1)
        output = torch.sigmoid(self.output(combined))
        return output

Kaggle学习赛初探