DeepFM
动机
对于 CTR 问题,被证明的最有效的提升任务表现的策略是特征组合 (Feature Interaction), 在 CTR 问题的探究历史上来看就是如何更好地学习特征组合,进而更加精确地描述数据的特点。可以说这是基础推荐模型到深度学习推荐模型遵循的一个主要的思想。而组合特征大牛们研究过组合二阶特征,三阶甚至更高阶,但是面临一个问题就是随着阶数的提升,复杂度就成几何倍的升高。这样即使模型的表现更好了,但是推荐系统在实时性的要求也不能满足了。所以很多模型的出现都是为了解决另外一个更加深入的问题:如何更高效的学习特征组合?
为了解决上述问题,出现了 FM 和 FFM 来优化 LR 的特征组合较差这一个问题。并且在这个时候科学家们已经发现了 DNN 在特征组合方面的优势,所以又出现了 FNN 和 PNN 等使用深度网络的模型。但是 DNN 也存在局限性。
- DNN 局限 当我们使用 DNN 网络解决推荐问题的时候存在网络参数过于庞大的问题,这是因为在进行特征处理的时候我们需要使用 one-hot 编码来处理离散特征,这会导致输入的维度猛增。这里借用 AI 大会的一张图片:
![](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片2021-02-22-10-11-15.png)
这样庞大的参数量也是不实际的。为了解决 DNN 参数量过大的局限性,可以采用非常经典的 Field 思想,将 OneHot 特征转换为 Dense Vector
![](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片2021-02-22-10-11-40.png)
此时通过增加全连接层就可以实现高阶的特征组合,如下图所示:
![](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片2021-02-22-10-11-59.png)
- FNN 和 PNN 结合 FM 和 DNN 其实有两种方式,可以并行结合也可以串行结合。这两种方式各有几种代表模型。在 DeepFM 之前有 FNN,虽然在影响力上可能并不如 DeepFM,但是了解 FNN 的思想对我们理解 DeepFM 的特点和优点是很有帮助的。
![](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片2021-02-22-10-12-19.png)
FNN 是使用预训练好的 FM 模块,得到隐向量,然后把隐向量作为 DNN 的输入,但是经过实验进一步发现,在 Embedding layer 和 hidden layer1 之间增加一个 product 层(如上图所示)可以提高模型的表现,所以提出了 PNN,使用 product layer 替换 FM 预训练层。
- Wide&Deep FNN 和 PNN 模型仍然有一个比较明显的尚未解决的缺点:对于低阶组合特征学习到的比较少,这一点主要是由于 FM 和 DNN 的串行方式导致的,也就是虽然 FM 学到了低阶特征组合,但是 DNN 的全连接结构导致低阶特征并不能在 DNN 的输出端较好的表现。看来我们已经找到问题了,将串行方式改进为并行方式能比较好的解决这个问题。于是 Google 提出了 Wide&Deep 模型(将前几章),但是如果深入探究 Wide&Deep 的构成方式,虽然将整个模型的结构调整为了并行结构,在实际的使用中 Wide Module 中的部分需要较为精巧的特征工程,换句话说人工处理对于模型的效果具有比较大的影响(这一点可以在 Wide&Deep 模型部分得到验证)。
![image-20200910214310877](https://ryluo.oss-cn-chengdu.aliyuncs.com/Javaimage-20200910214310877.png)
综上所示,DeepFM 模型横空出世。
模型的结构与原理
![image-20210225180556628](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20210225180556628.png)
前面的 Field 和 Embedding 处理是和前面的方法是相同的,如上图中的绿色部分;DeepFM 将 Wide 部分替换为了 FM layer 如上图中的蓝色部分
这幅图其实有很多的点需要注意,很多人都一眼略过了,这里我个人认为在 DeepFM 模型中有三点需要注意:
- Deep 模型部分
- FM 模型部分
- Sparse Feature 中黄色和灰色节点代表什么意思
FM
详细内容参考 FM 模型部分的内容,下图是 FM 的一个结构图,从图中大致可以看出 FM Layer 是由一阶特征和二阶特征 Concatenate 到一起在经过一个 Sigmoid 得到 logits(结合 FM 的公式一起看),所以在实现的时候需要单独考虑 linear 部分和 FM 交叉特征部分。
![image-20210225181340313](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20210225181340313.png)
![image-20210225181010107](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20210225181010107.png)
Embedding 层的输出是将所有 id 类特征对应的 embedding 向量 concat 到到一起输入到 DNN 中。其中
上一层的输出作为下一层的输入,我们得到:
其中
最后进入 DNN 部分输出使用 sigmod 激活函数进行激活:
代码实现
DeepFM 在模型的结构图中显示,模型大致由两部分组成,一部分是 FM,还有一部分就是 DNN, 而 FM 又由一阶特征部分与二阶特征交叉部分组成,所以可以将整个模型拆成三部分,分别是一阶特征处理 linear 部分,二阶特征交叉 FM 以及 DNN 的高阶特征交叉。在下面的代码中也能够清晰的看到这个结构。此外每一部分可能由是由不同的特征组成,所以在构建模型的时候需要分别对这三部分输入的特征进行选择。
- linear_logits: 这部分是有关于线性计算,也就是 FM 的前半部分
的计算。对于这一块的计算,我们用了一个 get_linear_logits 函数实现,后面再说,总之通过这个函数,我们就可以实现上面这个公式的计算过程,得到 linear 的输出, 这部分特征由数值特征和类别特征的 onehot 编码组成的一维向量组成,实际应用中根据自己的业务放置不同的一阶特征 (这里的 dense 特征并不是必须的,有可能会将数值特征进行分桶,然后在当做类别特征来处理) - fm_logits: 这一块主要是针对离散的特征,首先过 embedding,然后使用 FM 特征交叉的方式,两两特征进行交叉,得到新的特征向量,最后计算交叉特征的 logits
- dnn_logits: 这一块主要是针对离散的特征,首先过 embedding,然后将得到的 embedding 拼接成一个向量 (具体的可以看代码,也可以看一下下面的模型结构图),通过 dnn 学习类别特征之间的隐式特征交叉并输出 logits 值
def DeepFM(linear_feature_columns, dnn_feature_columns):
# 构建输入层,即所有特征对应的Input()层,这里使用字典的形式返回,方便后续构建模型
dense_input_dict, sparse_input_dict = build_input_layers(linear_feature_columns + dnn_feature_columns)
# 将linear部分的特征中sparse特征筛选出来,后面用来做1维的embedding
linear_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), linear_feature_columns))
# 构建模型的输入层,模型的输入层不能是字典的形式,应该将字典的形式转换成列表的形式
# 注意:这里实际的输入与Input()层的对应,是通过模型输入时候的字典数据的key与对应name的Input层
input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
# linear_logits由两部分组成,分别是dense特征的logits和sparse特征的logits
linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_sparse_feature_columns)
# 构建维度为k的embedding层,这里使用字典的形式返回,方便后面搭建模型
# embedding层用户构建FM交叉部分和DNN的输入部分
embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
# 将输入到dnn中的所有sparse特征筛选出来
dnn_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))
fm_logits = get_fm_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers) # 只考虑二阶项
# 将所有的Embedding都拼起来,一起输入到dnn中
dnn_logits = get_dnn_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers)
# 将linear,FM,dnn的logits相加作为最终的logits
output_logits = Add()([linear_logits, fm_logits, dnn_logits])
# 这里的激活函数使用sigmoid
output_layers = Activation("sigmoid")(output_logits)
model = Model(input_layers, output_layers)
return model
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
关于每一块的细节,这里就不解释了,在我们给出的 GitHub 代码中,我们已经加了非常详细的注释,大家看那个应该很容易看明白, 为了方便大家的阅读,我们这里还给大家画了一个整体的模型架构图,帮助大家更好的了解每一块以及前向传播(画的图不是很规范,先将就看一下,后面我们会统一在优化一下这个手工图)。
![image-20210228161135777](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20210228161135777.png)
下面是一个通过 keras 画的模型结构图,为了更好的显示,数值特征和类别特征都只是选择了一小部分,画图的代码也在 github 中。
![image-20210225180556628](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片DeepFM.png)
思考
- 如果对于 FM 采用随机梯度下降 SGD 训练模型参数,请写出模型各个参数的梯度和 FM 参数训练的复杂度
- 对于下图所示,根据你的理解 Sparse Feature 中的不同颜色节点分别表示什么意思
![image-20210225180556628](https://ryluo.oss-cn-chengdu.aliyuncs.com/图片image-20210225180556628.png)
参考资料