21.10. 深度分解机
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 SageMaker Studio Lab 中打开 Notebook

学习有效的特征组合对于点击率预测任务的成功至关重要。分解机在线性范式(例如,双线性交互)中对特征交互进行建模。这对于现实世界的数据通常是不够的,因为现实世界中固有的特征交叉结构通常非常复杂和非线性。更糟糕的是,在实践中,分解机通常只使用二阶特征交互。理论上,用分解机对更高阶的特征组合进行建模是可能的,但由于数值不稳定和计算复杂度高,通常不被采用。

一个有效的解决方案是使用深度神经网络。深度神经网络在特征表示学习方面很强大,并有潜力学习复杂的特征交互。因此,将深度神经网络整合到分解机中是很自然的。在分解机上增加非线性转换层,使其能够对低阶特征组合和高阶特征组合进行建模。此外,深度神经网络还可以捕获输入的非线性固有结构。在本节中,我们将介绍一个名为深度分解机(DeepFM) (Guo et al., 2017) 的代表性模型,它结合了FM和深度神经网络。

21.10.1. 模型架构

DeepFM由一个FM组件和一个深度组件组成,它们以并行结构集成。FM组件与用于建模低阶特征交互的双向分解机相同。深度组件是一个MLP,用于捕获高阶特征交互和非线性。这两个组件共享相同的输入/嵌入,它们的输出被相加作为最终预测。值得指出的是,DeepFM的精神类似于Wide & Deep架构,后者可以同时捕获记忆和泛化。DeepFM相对于Wide & Deep模型的优势在于,它通过自动识别特征组合,减少了手工特征工程的工作量。

为了简洁起见,我们省略了对FM组件的描述,并将其输出表示为 \(\hat{y}^{(FM)}\)。读者可以参考上一节了解更多细节。让 \(\mathbf{e}_i \in \mathbb{R}^{k}\) 表示第 \(i^\textrm{th}\) 个字段的潜在特征向量。深度组件的输入是所有字段的密集嵌入的拼接,这些嵌入是通过稀疏分类特征输入查找得到的,表示为

(21.10.1)\[\mathbf{z}^{(0)} = [\mathbf{e}_1, \mathbf{e}_2, ..., \mathbf{e}_f],\]

其中 \(f\) 是字段的数量。然后它被送入以下神经网络

(21.10.2)\[\mathbf{z}^{(l)} = \alpha(\mathbf{W}^{(l)}\mathbf{z}^{(l-1)} + \mathbf{b}^{(l)}),\]

其中 \(\alpha\) 是激活函数。 \(\mathbf{W}_{l}\)\(\mathbf{b}_{l}\) 是第 \(l^\textrm{th}\) 层的权重和偏置。让 \(y_{DNN}\) 表示预测的输出。DeepFM的最终预测是FM和DNN输出的总和。所以我们有

(21.10.3)\[\hat{y} = \sigma(\hat{y}^{(FM)} + \hat{y}^{(DNN)}),\]

其中 \(\sigma\) 是sigmoid函数。DeepFM的架构如下图所示。 DeepFM模型图示

值得注意的是,DeepFM并不是将深度神经网络与FM结合的唯一方法。我们也可以在特征交互上添加非线性层 (He and Chua, 2017)

import os
from mxnet import gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

21.10.2. DeepFM的实现

DeepFM的实现与FM的实现类似。我们保持FM部分不变,并使用一个以 relu 为激活函数的MLP块。Dropout也用于对模型进行正则化。MLP的神经元数量可以通过 mlp_dims 超参数进行调整。

class DeepFM(nn.Block):
    def __init__(self, field_dims, num_factors, mlp_dims, drop_rate=0.1):
        super(DeepFM, self).__init__()
        num_inputs = int(sum(field_dims))
        self.embedding = nn.Embedding(num_inputs, num_factors)
        self.fc = nn.Embedding(num_inputs, 1)
        self.linear_layer = nn.Dense(1, use_bias=True)
        input_dim = self.embed_output_dim = len(field_dims) * num_factors
        self.mlp = nn.Sequential()
        for dim in mlp_dims:
            self.mlp.add(nn.Dense(dim, 'relu', True, in_units=input_dim))
            self.mlp.add(nn.Dropout(rate=drop_rate))
            input_dim = dim
        self.mlp.add(nn.Dense(in_units=input_dim, units=1))

    def forward(self, x):
        embed_x = self.embedding(x)
        square_of_sum = np.sum(embed_x, axis=1) ** 2
        sum_of_square = np.sum(embed_x ** 2, axis=1)
        inputs = np.reshape(embed_x, (-1, self.embed_output_dim))
        x = self.linear_layer(self.fc(x).sum(1)) \
            + 0.5 * (square_of_sum - sum_of_square).sum(1, keepdims=True) \
            + self.mlp(inputs)
        x = npx.sigmoid(x)
        return x

21.10.3. 模型训练与评估

数据加载过程与FM相同。我们将DeepFM的MLP组件设置为一个具有金字塔结构(30-20-10)的三层密集网络。所有其他超参数与FM保持相同。

batch_size = 2048
data_dir = d2l.download_extract('ctr')
train_data = d2l.CTRDataset(os.path.join(data_dir, 'train.csv'))
test_data = d2l.CTRDataset(os.path.join(data_dir, 'test.csv'),
                           feat_mapper=train_data.feat_mapper,
                           defaults=train_data.defaults)
field_dims = train_data.field_dims
train_iter = gluon.data.DataLoader(
    train_data, shuffle=True, last_batch='rollover', batch_size=batch_size,
    num_workers=d2l.get_dataloader_workers())
test_iter = gluon.data.DataLoader(
    test_data, shuffle=False, last_batch='rollover', batch_size=batch_size,
    num_workers=d2l.get_dataloader_workers())
devices = d2l.try_all_gpus()
net = DeepFM(field_dims, num_factors=10, mlp_dims=[30, 20, 10])
net.initialize(init.Xavier(), ctx=devices)
lr, num_epochs, optimizer = 0.01, 30, 'adam'
trainer = gluon.Trainer(net.collect_params(), optimizer,
                        {'learning_rate': lr})
loss = gluon.loss.SigmoidBinaryCrossEntropyLoss()
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
loss 0.509, train acc 0.489, test acc 0.510
46234.4 examples/sec on [gpu(0), gpu(1)]
../_images/output_deepfm_61b173_5_1.svg

与FM相比,DeepFM收敛更快,性能更好。

21.10.4. 小结

  • 将神经网络与FM集成,使其能够对复杂和高阶的交互进行建模。

  • 在广告数据集上,DeepFM的性能优于原始的FM。

21.10.5. 练习

  • 改变MLP的结构,检查其对模型性能的影响。

  • 将数据集更改为Criteo,并将其与原始FM模型进行比较。

讨论