一、前言
DeepFM的升级版本xDeepFM,发表于2018年KDD,论文地址为:xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems
论文对应的代码地址:https://github.com/Leavingseason/xDeepFM
另外文中如果出现错误,欢迎大家指正出来,我好进行修改,免得误导他人,毕竟个人能力有限,好的博客应该大家一起来维护。
二、算法原理
(1)xDeepFM的特征交互发生在向量级别,自动学习高阶的显示特征,模型的整体结构如下:也是分为三个部分,线性部分,CIN部分以及DNN部分,其中值得注意的地方就是,CIN部分的输入还是对稀疏矩阵中每个field的特征进行词嵌入之后拼接组合,DNN部分的输入还是稀疏矩阵进行词嵌入进行Flatten之后和连续特征进行拼接得到的,这部分学习的隐式的特征;
(2)模型中最难的部分就是CIN了,需要好好理解一下,实际上先介绍两个部分:
最后的通用公式如下:
通过如上的公式就能很好的表示特征之间的交互,从而能够显式的学习特征;
(3)很多人可能看公式看的头晕,包括我也一样,通过图像来理解整个过程可能比较好;
(4)xDeepFm模型依然也是包括线性交叉部分,CIN部分和DNN部分,后面两部分使得模型能够分别显式的学习和隐式的学习数据的特征;
(5)损失函数的设计依然是log损失,在推荐系统中还是很常见的;
四、实验结果:
五、代码实现
这部分代码参考了几份代码实现,其中变量的名称也是尽量和其他人的一致,因为看了几份代码中的变量名称也是差不多的,甚至是一致的,所以我也尽量不改变;
import tensorflow as tf
from tensorflow.python.keras.layers import Layer
from tensorflow.python.keras.initializers import glorot_normal
from tensorflow.python.keras.regularizers import l2
class cin(Layer):
def __init__(self, layers=(128, 128), if_direct=False, l2_reg=1e-5, seed=1024, **kwargs):
self.layers = layers
self.if_direct = if_direct
self.l2_reg = l2_reg
self.seed = seed
super(cin, self).__init__(**kwargs)
def build(self, input_shape):
self.filed_nums = [input_shape.shape[1].value]
self.filters = []
self.bias = []
for i, field_size in enumerate(self.layers):
self.filters.append(self.add_weight(name='filters'+str(i),
shape=(1, self.field_nums[-1] * self.filed_nums[0]),
dtype=tf.float32,
initializer=glorot_normal(seed=self.seed + i),
regularizer=l2(self.l2_reg)))
self.bias.append(self.add_weight(name='bias'+str(i), shape=(field_size), dtype=tf.float32,
initializer=tf.keras.initializer.Zeros()))
if self.if_direct:
self.filed_nums.append(field_size // 2)
else:
self.filed_nums.append(field_size)
super(cin, self).build(input_shape)
def call(self, inputs, **kwargs):#inputs: (batch, filed_size, emb_size)
emb_size = inputs.get_shape()[-1].value
hidden_nn_layers = [inputs]
final_res = []
split_tensor0 = tf.split(inputs, [1] * emb_size, axis=2) #(emb_size, batch, field_size, 1)
for i, field_size in enumerate(self.layers):
split_tensor = tf.split(hidden_nn_layers[-1], [1] * emb_size, axis=2)
dot_tensor_m = tf.matmul(split_tensor, split_tensor0, transpose_b=True) #(emb_size, batch, field_size, field_size)
dot_tensor_o = tf.reshape(dot_tensor_m, shape=[emb_size, -1, self.filed_nums[0] * self.filed_nums[i]])#(emb_size, batch, field_size * field_size)
dot_tensor_res = tf.transpose(dot_tensor_o, perm=[1, 0, 2])#(batch, emb_size, field_size * field_size)
cur_out = tf.nn.conv1d(dot_tensor_res, self.filters[i], stride=1, padding='VALID')#(batch, emb_size, field_size)
cur_out = tf.nn.bias_add(cur_out, self.self.bias[i])
cur_out = tf.transpose(cur_out, perm=[0, 2, 1])#(batch, field_size, emb_size)
#diect方式:当前输出作为下一层的输入,非direct方式:当前前一半作为下一层的输入,后一半作为输出结果的一部分;
if self.if_direct:
direct_connect = cur_out
next_hidden = cur_out
else:
if i != len(self.layers) - 1:
next_hidden, direct_connect = tf.split(cur_out, 2 * [field_size], axis=1)
else:
direct_connect = cur_out
next_hidden = 0
final_res.append(direct_connect)
hidden_nn_layers.append(next_hidden)
result = tf.concat(final_res, axis=1)
result = tf.reduce_sum(result, axis=-1, keep_dims=False)
return result