DeepFM:融合因子分解机与深度学习的CTR预测模型

314 阅读5分钟

引言

点击率(Click-Through Rate, CTR)预测是推荐系统和计算广告领域的核心任务。传统方法通常依赖人工特征工程或单一模型架构,难以同时捕捉低阶与高阶特征交互。为了克服这些限制,研究者们不断探索新的模型架构,以更有效地学习特征间的复杂交互关系。2017年,由Huifeng Guo、Ruiming Tang、Yunming Ye、Zhenguo Li 和 Xiuqiang He 提出的 DeepFM 模型,为这一领域带来了新的突破

DeepFM 是一种创新的混合模型,通过无缝集成 因子分解机(Factorization Machine, FM)深度神经网络(Deep Neural Network, DNN) ,实现了端到端的特征交互学习。它不仅能够捕捉低阶特征交互(如线性和二阶交互),还能学习高阶特征交互,而无需复杂的特征工程。这种设计使得 DeepFM 在处理高维稀疏数据时表现出色,且无需预训练,曾经是工业界广泛采用的解决方案之一

DeepFM可以看做是Wide&Cross的扩展,与Deep&Cross模型一样都是将wide部分进行修改,以进行更充分的特征交叉

1.1 CTR预测任务

假设训练数据集包含n个实例(χ, y),其中χ是一个包含多个字段的数据记录,通常涉及用户和商品的配对,y ∈ {0, 1} 是与该配对相关的标签,表示用户的点击行为(y = 1表示用户点击了该商品,y = 0表示没有点击)。χ可以包括类别字段(如性别、位置)和连续字段(如年龄)。

  • 对于类别字段,采用one-hot编码表示,
  • 而连续字段则可以直接使用数值表示,或通过离散化后用one-hot编码表示

每个实例将被转换为 (x, y),其中x=[xfield1,xfield2,...,xfieldj,...,xfieldm]x = [x_{field1}, x_{field2}, ..., x_{fieldj}, ..., x_{fieldm}]是一个d维向量,其中xfieldjx_{fieldj}xx的第j个字段的向量表示。通常,x是高维且稀疏的。CTR预测的任务是构建一个预测模型y^=CTRmodel(x)\hat{y} = CTR model(x),用于估计用户在特定场景下点击某个商品的概率。

给定用户-物品交互记录数据集,CTR预测旨在建模用户对物品的点击概率。输入特征通常包含类别型字段(如性别、位置)和连续型字段(如年龄),经编码后形成高维稀疏向量。模型的输出为概率值:

y^=CTR_model(x)(0,1)\hat{y} = \text{CTR\_model}(x) \in (0,1)

其中xRdx \in \mathbb{R}^d为稀疏特征向量,dd为特征维度。

1.2 核心挑战

  1. 特征交互复杂性:低阶(如一阶、二阶)与高阶(三阶及以上)交互均对预测有贡献。
  2. 数据稀疏性:类别型特征的独热编码导致输入维度极高且稀疏(如用户ID字段可达十亿维)。
  3. 端到端学习需求:传统方法依赖人工设计特征交互(如Wide & Deep),限制了模型泛化能力。

2. DeepFM模型架构

DeepFM由FM组件深度组件构成,二者共享输入嵌入层,实现低阶与高阶特征交互的联合学习(见图1)。模型输出为:

y^=sigmoid(yFM+yDNN)\hat{y} = \text{sigmoid}(y_{\text{FM}} + y_{\text{DNN}})

其中yFMy_{\text{FM}}为FM组件的输出,yDNNy_{\text{DNN}}为深度组件的输出。

image.png

2.1 FM组件:低阶特征交互建模

FM通过隐向量内积建模二阶特征交互,解决了传统方法在稀疏数据下的参数估计问题。其数学形式为:

yFM=w,x+j1=1dj2=j1+1dVi,Vjxj1xj2y_{\text{FM}} = \langle w, x \rangle + \sum_{j_1=1}^d \sum_{j_2=j_1+1}^d \langle V_i, V_j \rangle x_{j_1} x_{j_2}
  • 一阶项w,x\langle w, x \rangle:捕获线性特征重要性。
  • 二阶项Vi,Vjxj1xj2\sum \langle V_i, V_j \rangle x_{j_1}x_{j_2}:通过隐向量ViRkV_i \in \mathbb{R}^k内积建模特征对(i,j)(i,j)的交互强度。

优势

  • 隐向量共享机制允许在特征共现极少时仍能有效学习交互参数。
  • 计算复杂度优化至O(kd)O(kd),适用于大规模稀疏数据。

image.png

2.2 深度组件:高阶特征交互建模

深度组件为前馈神经网络,通过多层非线性变换捕获高阶特征交互。其关键设计包括:

嵌入层(Embedding Layer)

  • 将每个字段映射为定长稠密向量eiRke_i \in \mathbb{R}^k,解决输入稀疏性与维度爆炸问题。
  • 权重共享:FM的隐向量ViV_i直接作为嵌入层参数,实现两组件联合训练。

前向传播过程

a(l+1)=σ(W(l)a(l)+b(l))a^{(l+1)} = \sigma\left(W^{(l)} a^{(l)} + b^{(l)}\right)

其中a(0)=[e1,e2,,em]a^{(0)} = [e_1, e_2, \dots, e_m]为嵌入层输出,σ\sigma为激活函数(如ReLU),W(l)W^{(l)}b(l)b^{(l)}为第ll层参数。最终输出为:

yDNN=σ(WH+1aH+bH+1)y_{\text{DNN}} = \sigma\left(W^{|H|+1} a^{H} + b^{|H|+1}\right)

image.png

3. 创新点与技术优势

3.1 共享嵌入的联合学习机制

  • 参数共享:FM与DNN共享嵌入层,避免预训练并增强特征表示一致性。
  • 端到端训练:通过反向传播联合优化所有参数,提升模型收敛效率。

3.2 对比现有模型

image.png

模型特征交互类型需要预训练特征工程需求
FNN仅高阶是(FM初始化)
PNN高阶(乘积层)
Wide & Deep低阶+高阶高(需设计Wide部分)
DeepFM低阶+高阶

关键区别

  • FNN依赖FM预训练,导致嵌入表示受限于FM初始化。
  • Wide & Deep需人工设计Wide部分的交叉特征,而DeepFM通过FM自动实现。

3.3 实验结论

从表格可以看到,各种指标的提升都很小,但是引用原文的话:

  • “具体来说,DeepFM在AUC和Logloss两个关键指标上分别比竞争对手高出0.37%和0.42%。虽然这些提升看似微小,但在CTR预测领域,即使是微小的改进也可能带来巨大的商业价值。”
  • 根据Cheng等人(2016)的报告,与逻辑回归(LR)相比,Wide & Deep模型在AUC上提高了0.275%(离线),而在线CTR的提升达到了3.9% 考虑到Company*的应用商店每天的营业额高达数百万美元,即使是CTR的微小提升也能带来额外的数百万美元收益。因此,DeepFM模型在商业应用中的潜力巨大。

image.png

4. 代码实现

注意这里为了简化,考虑的是将数值型进行离散化ont-hot处理

class DeepFM(nn.Module):
    def __init__(self, feature_sizes, embedding_dim=10, hidden_dims=[64, 32], num_classes=1,dropout = 0.2):
        """
        feature_sizes: 每个特征的唯一值数量列表
        embedding_dim: 嵌入维度
        hidden_dims: DNN隐藏层维度列表
        num_classes: 输出维度(二分类为1)
        """
        super(DeepFM, self).__init__()
        self.feature_sizes = feature_sizes
        self.num_fields = len(feature_sizes)
        self.embedding_dim = embedding_dim

        #FM 一阶项
        self.linear = nn.Embedding(sum(feature_sizes) + 1, 1)

        #FM/DNN 共享嵌入层
        self.embedding = nn.ModuleList([
            nn.Embedding(dim, embedding_dim)
            for dim in feature_sizes
        ])

        #DNN 部分
        dnn_input_dim = self.num_fields * embedding_dim
        self.dnn = nn.Sequential()
        for i, hidden_dim in enumerate(hidden_dims):
            self.dnn.add_module(
                name = f"fc_{i}",
                module = nn.Linear(dnn_input_dim, hidden_dim)
            )
            self.dnn.add_module(
                name = f"bn_{i}",
                module = nn.BatchNorm1d(hidden_dim)
            )

            self.dnn.add_module(
                name = f"relu_{i}",
                module = nn.ReLU()
            )
            self.dnn.add_module(
                name = f"dropout_{i}",
                module = nn.Dropout(dropout)
            )
            dnn_input_dim = hidden_dim

        #最终输出层
        self.dnn_output = nn.Linear(hidden_dims[-1], num_classes)

        #初始化权重
        self._initialize_weights()

    def _initialize_weights(self):
        for m in self.modules(): 
            if isinstance(m, nn.Embedding):
                nn.init.xavier_normal_(m.weight) #使用Xavier初始化
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)#原地修改
                nn.init.constant_(m.bias, 0) 
            
    def forward(self,x):
        """
        x: 输入特征 [batch_size, num_fields] (LongTensor)
        """

        #FM 一阶项
        linear_part = torch.sum(self.linear(x), dim = 1) #[batch_size, 1]

        #获取嵌入向量
        embeds = []
        for i in range(self.num_fields):
            embed = self.embedding[i](x[:, i]) #[batch_size, embedding_dim]
            embeds.append(embed)
            
        #FM 二阶段项,注意这里采用化简公式计算
        fm_second_order = 0
        sum_embed = torch.stack(embeds, dim = 1) #[batch_size, num_fields, embedding_dim]
        sum_embed = torch.sum(sum_embed, dim = 1) #[batch_size, embedding_dim],其实就是将所有的特征与给加起来

        square_of_sum = sum_embed.pow(2) #[batch_size, embedding_dim]

        sum_of_square = torch.stack([e.pow(2) for e in embeds],dim = 1).sum(dim = 1) #[batch_size, embedding_dim]
        fm_second_order = 0.5 * (square_of_sum - sum_of_square).sum(dim = 1, keepdim = True) #[batch_size, 1]

        #DNN部分
        dnn_input = torch.cat(embeds, dim = 1) #[batch_size, num_fields * embedding_dim]
        dnn_output = self.dnn(dnn_input) #[batch_size, hidden_dims[-1]]
        dnn_final_output = self.dnn_output(dnn_output) #[batch_size, num_classes(二分类为1)]

        #输出
        output = linear_part + fm_second_order + dnn_final_output #[batch_size, num_classes(二分类为1)]
        return torch.sigmoid(output).squeeze(1)

参考文献

  1. Rendle, S. (2010). Factorization Machines. ICDM.
  2. Cheng, H. et al. (2016). Wide & Deep Learning for Recommender Systems. DLRS.
  3. Zhang, W. et al. (2016). Deep Learning over Multi-field Categorical Data. ECIR.
  4. Qu, Y. et al. (2016). Product-based Neural Networks for User Response Prediction. ICDM.
  5. Guo, H., Tang, R., Ye, Y., Li, Z., & He, X. (2017). DeepFM: A Factorization-Machine based Neural Network for CTR Prediction. arXiv preprint arXiv:1703.04247.
  6. 王喆 《深度学习推荐系统》