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 的区别么?并不是。
一般类别特征无法直接输入模型,所以需要先 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 非线性转换,得到预测的概率输出。
DeepFM 在CTR预估任务上的表现,以及与其他推荐算法的对比如下:
作者通过实验证明了 relu 激活函数比 tanh 更适合 DeepFM (但在其他模型上效果不一)。
与 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 侧引入大量多于的计算,不可取。
如果你搭建过 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
希望看完此文的你能够有所收获…