推荐算法(四)——经典模型 DeepFM 模型详解及代码实践

1 介绍

DeepFM 是华为诺亚方舟实验室在 2017 年提出的模型。
在这里插入图片描述论文传送门:

A Factorization-Machine based Neural Network for CTR Prediction

正如名称所示,DeepFM 是 Deep 与 FM 结合的产物,也是 Wide&Deep 的改进版,只是将其中的 LR 替换成了 FM,提升了模型 wide 侧提取信息的能力。学 DeepFM 之前建议先了解 FM Wide&Deep

DeepFM 与Wide&Deep 只是 FM 与 LR 的区别么?并不是。

2 模型结构

在这里插入图片描述
2.1 Sparse Features

一般类别特征无法直接输入模型,所以需要先 onehot 处理得到的其稀疏01向量表示。该层即表示经过 onehot 编码的类别特征与数值特征的拼接。

2.2 Dense Embeddings

该层为嵌入层,用于对高维稀疏的01向量做嵌入,得到低维稠密的向量 e (每个01向量对应自己的嵌入层,不同向量的嵌入过程相互独立,如上图所示)。然后将每个稠密向量横向拼接,在拼接上原始的数值特征,然后作为 Deep 与 FM 的输入。

2.3 FM Layer

在这里插入图片描述
FM 有两部分,线性部分交叉部分。线性部分 (黑色线段) 是给与每个特征一个权重,然后进行加权和;交叉部分 (红色线段) 是对特征进行两两相乘,然后赋予权重加权求和。然后将两部分结果累加在一起即为 FM Layer 的输出。

2.4 Hidden Layer

在这里插入图片描述
在这里插入图片描述
Deep 部分的输入 a0 为所有稠密向量的横向拼接,然后经过多层线性映射+非线性转换得到 Hidden Layer 的输出,一般会映射到1维,因为需要与 FM 的结果进行累加。

2.5 Output Units

在这里插入图片描述
输出层为 FM Layer 的结果与 Hidden Layer 结果的累加,低阶与高阶特征交互的融合,然后经过 Sigmoid 非线性转换,得到预测的概率输出。

3 实验结果

DeepFM 在CTR预估任务上的表现,以及与其他推荐算法的对比如下:

在这里插入图片描述
作者通过实验证明了 relu 激活函数比 tanh 更适合 DeepFM (但在其他模型上效果不一)。

在这里插入图片描述

4 总结

与 Wide&Deep 的异同:

相同点:都是线性模型与深度模型的结合,低阶与高阶特征交互的融合。

不同点:DeepFM 两个部分共享输入,而 Wide&Deep 的 wide 侧是稀疏输入,deep 侧是稠密输入;DeepFM 无需加入人工特征,可端到端的学习,线上部署更方便,Wide&Deep 则需要在输入上加入人工特征提升模型表达能力。

DeepFM 优缺点:

优点:

1 两部分联合训练,无需加入人工特征,更易部署;

2 结构简单,复杂度低,两部分共享输入,共享信息,可更精确的训练学习。

缺点:

1 将类别特征对应的稠密向量拼接作为输入,然后对元素进行两两交叉。这样导致模型无法意识到域的概念,FM 与 Deep 两部分都不会考虑到域,属于同一个域的元素应该对应同样的计算。

面试可能会考察的问题:

FM 本来就可以在稀疏输入的场景中进行学习,为什么要跟 Deep 共享稠密输入?虽然 FM 具有线性复杂度 O(nk),其中 n 为特征数,k 为隐向量维度,可以随着输入的特征数线性增长。但是经过 onehot 处理的类别特征维度往往要比稠密向量高上一两个数量级,这样还是会给 FM 侧引入大量多于的计算,不可取。

5 代码实践

如果你搭建过 FM 或者 Wide&Deep 模型,那么对于 DeepFM 的搭建就很随意了。为了便于理解,我还是会放上每个部分全部的代码。

Layer 搭建:

import tensorflow as tf
from tensorflow.keras.layers import Layer
from tensorflow.keras.layers import Input, Dense

class FM_layer(Layer):
    def __init__(self, k, w_reg, v_reg):
        super().__init__()
        self.k = k
        self.w_reg = w_reg
        self.v_reg = v_reg

    def build(self, input_shape):
        self.w0 = self.add_weight(name='w0', shape=(1,),
                                  initializer=tf.zeros_initializer(),
                                  trainable=True,)
        self.w = self.add_weight(name='w', shape=(input_shape[-1], 1),
                                 initializer=tf.random_normal_initializer(),
                                 trainable=True,
                                 regularizer=tf.keras.regularizers.l2(self.w_reg))
        self.v = self.add_weight(name='v', shape=(input_shape[-1], self.k),
                                 initializer=tf.random_normal_initializer(),
                                 trainable=True,
                                 regularizer=tf.keras.regularizers.l2(self.v_reg))

    def call(self, inputs, **kwargs):
        linear_part = tf.matmul(inputs, self.w) + self.w0   #shape:(batchsize, 1)

        inter_part1 = tf.pow(tf.matmul(inputs, self.v), 2)  #shape:(batchsize, self.k)
        inter_part2 = tf.matmul(tf.pow(inputs, 2), tf.pow(self.v, 2)) #shape:(batchsize, self.k)
        inter_part = 0.5*tf.reduce_sum(inter_part1 - inter_part2, axis=-1, keepdims=True) #shape:(batchsize, 1)

        output = linear_part + inter_part
        return output

class Dense_layer(Layer):
    def __init__(self, hidden_units, output_dim, activation):
        super().__init__()
        self.hidden_units = hidden_units
        self.output_dim = output_dim
        self.activation = activation

        #全连接无需定义第一层维度
        self.hidden_layer = [Dense(i, activation=self.activation)
                             for i in self.hidden_units]
        self.output_layer = Dense(self.output_dim, activation=None)

    def call(self, inputs):
        x = inputs
        for layer in self.hidden_layer:
            x = layer(x)
        output = self.output_layer(x)
        return output

Model 搭建:

from layer import FM_layer, Dense_layer

import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.layers import Embedding

class DeepFM(Model):
    def __init__(self, feature_columns, k, w_reg, v_reg, hidden_units, output_dim, activation):
        super().__init__()
        self.dense_feature_columns, self.sparse_feature_columns = feature_columns
        self.embed_layers = {
            'embed_' + str(i): Embedding(feat['feat_onehot_dim'], feat['embed_dim'])
             for i, feat in enumerate(self.sparse_feature_columns)
        }
        
        self.FM = FM_layer(k, w_reg, v_reg)
        self.Dense = Dense_layer(hidden_units, output_dim, activation)

    def call(self, inputs):
        dense_inputs, sparse_inputs = inputs[:, :13], inputs[:, 13:]
        # embedding
        sparse_embed = tf.concat([self.embed_layers['embed_{}'.format(i)](sparse_inputs[:, i])
                                  for i in range(sparse_inputs.shape[1])], axis=1)
        x = tf.concat([dense_inputs, sparse_embed], axis=-1)

        fm_output = self.FM(x)
        dense_output = self.Dense(x)
        output = tf.nn.sigmoid(0.5*(fm_output + dense_output))
        return output

完整训练代码可在文末仓库中查看。

写在最后

下一篇预告:推荐算法(五)——谷歌经典 DCN 原理及代码实践

完整的推荐算法复现代码可参考仓库: Recommend-System-tf2.0

希望看完此文的你能够有所收获…

  • 3
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值