首页 机器学习 深度学习 LLM 推荐算法 Agent 项目 关于

深度模型Wide&Deep模型

1 序言

在推荐系统(如 Google Play 应用商店)中,点击率(CTR)预估模型的性能直接决定了用户体验与商业收益。在 Wide & Deep 模型提出之前,学术界与工业界主要依赖以下两类范式:

  • Wide 组件(记忆能力): 基于大规模离散特征和交叉特征的线性模型(如 Logistic Regression)。通过频繁出现的特征组合(例如,User_Installed_App="Netflix" $\times$ Impression_App="Hulu"),模型能够直接记住历史高频共现的规则。这种方式简单高效,但无法推荐在训练集中未曾出现或罕见的特征组合
  • Deep 组件(泛化能力): 基于深度神经网络(DNN)的模型。通过将高维稀疏的 ID 类特征映射为低维稠密向量(Embedding),再输入多层感知机。DNN 能够学习到特征之间的潜在语义关系(例如,通过向量相似度发现安装过 “Netflix” 的用户可能也会对 “Disney+” 感兴趣),从而具备强大的泛化能力。然而,当用户有极度特殊的个性化需求或面对高度稀疏的长尾数据时,过度泛化可能导致推荐偏离目标。

Wide & Deep 的核心动机: 打破“非此即彼”的局限,在一个框架内同时实现记忆泛化。将两者的优势结合,使模型既能精准捕捉高频强关联规则,又能探索潜在的用户兴趣。

2 Wide&Deep模型结构

Wide & Deep 的核心架构可以用“双路并行”来概括,其最终输出通过联合训练(Joint Training)进行融合。

Wide&Deep模型结构

2.1 Wide部分

Wide 部分本质上是一个广义线性模型(Generalized Linear Model),其数学形式如下:

\[y = w^T x + b\]

其中,输入特征 $x$ 既包括原始的单热点(One-hot)特征,也包括极为关键的交叉积变换(Cross-product Transformation)特征。其定义为:

\[\phi_k(x) = \prod_{i=1}^d x_i^{c_{ki}}, \quad c_{ki} \in \{0, 1\}\]

示例: 如果条件 $k$ 是“用户安装了 Netflix 且当前展示的是 Hulu”,那么当且仅当这两个原始特征都为 1 时,交叉特征 $\phi_k(x)$ 才为 1,否则为 0。这使得 Wide 部分能够精准捕捉特定组合的强信号。

2.2 Deep部分

Deep 部分是一个典型的前馈神经网络。对于类别特征(如 User ID、Device Type),首先通过一个嵌入层(Embedding Layer)将其转化为低维稠密向量:

\[a^{(0)} = [\text{embed}_1(x_1), \text{embed}_2(x_2), \dots, \text{embed}_m(x_m)]\]

随后,这些嵌入向量与连续型特征(经过常态化处理)拼接在一起,送入多层感知机(MLP)中进行非线性变换:

\[a^{(l+1)} = \sigma(W^{(l)} a^{(l)} + b^{(l)})\]

2.3 联合训练 (Joint Training)

Wide & Deep 采用的是 联合训练 (Joint Training),而非集成(Ensemble/Stacking)。集成模型中各个子模型是独立训练的,而联合训练是在训练时同时优化 Wide 和 Deep 的参数,共同最小化最终的损失函数。

对于二分类逻辑回归任务,其最终的预测目标为:

\[P(Y=1\vert{}x) = \sigma(w_{wide}^T [x, \phi(x)] + w_{deep}^T a^{(L)} + b)\]

在优化器的选择上,论文采用了非对称的协同优化策略:

  • Wide 侧: 采用带 $L_1$ 正则化的 FTRL (Follow-the-Regularized-Leader) 算法。FTRL 能够带来极高的稀疏性,从而使 Wide 侧保留最有效的高频特征规则,减小模型体积。
  • Deep 侧: 采用 AdaGradAdam 优化器,加速稠密向量和深层网络的收敛。

4 Wide&Deep模型实现

import tensorflow as tf
from tensorflow.keras import layers

class WideAndDeepModel(tf.keras.Model):
    def __init__(self, user_vocab, item_vocab, embed_dim=8, hidden_units=[64, 32]):
        super(WideAndDeepModel, self).__init__()
        
        # --- Wide 侧组件 ---
        # 类别特征编码(用于线性部分)
        self.user_lookup = layers.StringLookup(vocabulary=user_vocab, mask_token=None)
        self.item_lookup = layers.StringLookup(vocabulary=item_vocab, mask_token=None)
        # Wide侧的线性映射层
        self.wide_linear = layers.Dense(1, use_bias=True, name='wide_linear')

        # --- Deep 侧组件 ---
        # 嵌入层
        self.user_embed = layers.Embedding(input_dim=len(user_vocab) + 1, output_dim=embed_dim)
        self.item_embed = layers.Embedding(input_dim=len(item_vocab) + 1, output_dim=embed_dim)
        
        # 多层感知机 (MLP)
        self.dnn_layers = []
        for units in hidden_units:
            self.dnn_layers.append(layers.Dense(units, activation='relu'))
        self.deep_linear = layers.Dense(1, use_bias=False, name='deep_linear')

        # --- 最终输出激活层 ---
        self.prediction_layer = layers.Activation('sigmoid', name='ctr_prediction')

    def call(self, inputs):
        """
        前向传播逻辑
        inputs 预期为一个字典,包含: 'dense_features', 'user_id', 'item_id'
        """
        dense_inputs = inputs['dense_features']
        sparse_user = inputs['user_id']
        sparse_item = inputs['item_id']

        # 1. Wide 侧前向计算
        user_id_encoded = self.user_lookup(sparse_user)
        item_id_encoded = self.item_lookup(sparse_item)
        
        # 工业界通常在此处对 encoded 特征做 Cross 变换,这里简化为离散特征拼接进入线性层
        wide_features = layers.Concatenate(axis=-1)([
            tf.cast(user_id_encoded, tf.float32), 
            tf.cast(item_id_encoded, tf.float32)
        ])
        wide_output = self.wide_linear(wide_features)

        # 2. Deep 侧前向计算
        user_embedding = layers.Flatten()(self.user_embed(user_id_encoded))
        item_embedding = layers.Flatten()(self.item_embed(item_id_encoded))
        
        # 拼接连续特征与稠密嵌入向量
        deep_features = layers.Concatenate(axis=-1)([dense_inputs, user_embedding, item_embedding])
        
        # 穿过 DNN 隐藏层
        x = deep_features
        for layer in self.dnn_layers:
            x = layer(x)
        deep_output = self.deep_linear(x)

        # 3. 联合 (Joint) 融合与激活
        merged_output = layers.Add()([wide_output, deep_output])
        return self.prediction_layer(merged_output)

5 总结

Wide & Deep 模型的提出,是推荐系统架构演进史上的一个重要里程碑。它用一种极其优雅且符合直觉的“双路结构”,完美回答了工业界如何在“守正(记忆历史)”与“出奇(探索未知)”之间寻找平衡。

优点总结 (Strengths)

  • 两全其美: 同时兼顾了线性模型的记忆能力和深度模型的泛化能力。
  • 扩展性极佳: 结构简单清晰,允许工程师根据业务独立调优 Wide 侧的特征工程或 Deep 侧的网络深度。

后续演进 (Evolution)

尽管 Wide & Deep 表现优异,但它仍留下了一个痛点:Wide 侧的交叉特征依然需要大量的人工特征工程(Manual Feature Engineering)