推荐算法DeepFM
算法背景
对于一个基于CTR预估的推荐系统,最重要的是学习到用户点击行为背后隐含的特征组合。在不同的推荐场景中,低阶组合特征或者高阶组合特征可能都会对最终的CTR产生影响,所以说特征组合问题是深度学习推荐模型的重中之重。
对于上述特征组合问题,传统的解决方案有因子分解机算法(FM),该算法通过对于每一维特征的隐变量内积来提取组合特征,研究发现,该方案的结果相较于传统的算法有了较大的改善,但是FM算法其复杂度高,计算困难,通常只能用于二阶特征组合。对于高阶特征特征组合往往较为吃力。对于高阶特征组合通常使用多层网络组合的方式解决,即DNN。但DNN在进行特征处理的时候我们需要使用one-hot编码来处理离散特征,这会导致输入的维度猛增,存在着参数过大等问题,下面看一张AI大会上的图片
通常将特征分为不同的field,即将onehot特征转换为Dense Vector来解决该问题如下图:
再加上全连接就可以得到高阶的特征组合,同时加上FM来解决低阶特征组合的问题
DNN与FM的融合方式通常为串行和并行两种结构,具体如何连接这里不再多说,常用的融合模型为FNN和前面所学的Wide&Deep,但是W&D模型在output Units阶段直接将低阶和高阶特征进行组合,很容易让模型最终偏向学习到低阶或者高阶的特征,而不能做到很好的结合。所以就引出下面的DeepFM模型
算法结构与原理
首先从模型的结构入手,图如下:
从模型结构可以看出此模型将W&D模型的wide部分换为FM。下面一一来看一下两个两个部分
FM
FM为因子分解机,文章为https://www.csie.ntu.edu.tw/~b97053/paper/Rendle2010FM.pdf, FM主要目标是:解决数据稀疏的情况下,特征怎样组合的问题,可以在非常稀疏的数据中进行合理的参数估计,FM模型的时间复杂度是线性的,FM是一个通用模型,它可以用于任何特征为实值的情况。模型式子如下:
y ^ F M ( x ) = w 0 + ∑ i = 1 N w i x i + ∑ i = 1 N ∑ j = i + 1 N v i T v j x i x j \hat{y}{FM}(x) = w_0+\sum{i=1}^N w_ix_i + \sum_{i=1}^N \sum_{j=i+1}^N v_i^T v_j x_ix_j y^FM(x)=w0+∑i=1Nwixi+i=1∑Nj=i+1∑NviTvjxixj
其中 v i T v j v_i^Tv_j viTvj为引入的辅助向量,是为了降低时间复杂度.
Deep
Deep Module是为了学习高阶的特征组合,在上图中使用用全连接的方式将Dense Embedding输入到Hidden Layer,这里面Dense Embeddings就是为了解决DNN中的参数爆炸问题,这也是推荐模型中常用的处理方法。
Embedding层的输出是将所有id类特征对应的embedding向量concat到到一起输入到DNN中。其中 v i v_i vi表示第i个field的embedding,m是field的数量。 z 1 = [ v 1 , v 2 , . . . , v m ] z_1=[v_1, v_2, ..., v_m] z1=[v1,v2,...,vm] 上一层的输出作为下一层的输入,我们得到: z L = σ ( W L − 1 z L − 1 + b L − 1 ) z_L=\sigma(W_{L-1} z_{L-1}+b_{L-1}) zL=σ(WL−1zL−1+bL−1) 其中 σ \sigma σ表示激活函数,$z, W, b $分别表示该层的输入、权重和偏置。
最后进入DNN部分输出使用sigmod激活函数进行激活: y D N N = σ ( W L a L + b L ) y_{DNN}=\sigma(W^{L}a^L+b^L) yDNN=σ(WLaL+bL)
算法主要代码
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
DeepFM在模型的结构图中显示,模型大致由两部分组成,一部分是FM,还有一部分就是DNN, 而FM又由一阶特征部分与二阶特征交叉部分组成,所以可以将整个模型拆成三部分,分别是一阶特征处理linear部分,二阶特征交叉FM以及DNN的高阶特征交叉。在下面的代码中也能够清晰的看到这个结构。此外每一部分可能由是由不同的特征组成,所以在构建模型的时候需要分别对这三部分输入的特征进行选择。
思考
1.如果对于FM采用随机梯度下降SGD训练模型参数,请写出模型各个参数的梯度和FM参数训练的复杂度
2.m根据你的理解Sparse Feature中的不同颜色节点分别表示什么意思
第一个问题还在思考中,第二个黄色代表的是稀疏类别特征中属于那个类别,灰色则代表不属于,如同onehot编码中的0和1
参考
https://www.jianshu.com/p/6f1c2643d31b
https://github.com/datawhalechina/team-learning-rs/blob/master/DeepRecommendationModel/DeepFM.md
https://www.csie.ntu.edu.tw/~b97053/paper/Rendle2010FM.pdf
https://zhuanlan.zhihu.com/p/57873613