目录
一:背景
在CTR预估中,为了解决稀疏特征的问题,学者们提出了FM模型来建模特征之间的交互关系。但是FM模型只能表达特征之间两两组合之间的关系,无法建模两个特征之间深层次的关系或者说多个特征之间的交互关系,因此学者们通过Deep Network来建模更高阶的特征之间的关系。因此 FM和深度网络DNN的结合也就成为了CTR预估问题中主流的方法。有关FM和DNN的结合有两种主流的方法,并行结构和串行结构。两种结构的理解以及实现如下表所示:
结构 | 描述 | 常见模型 |
---|---|---|
并行结构 | FM部分和DNN部分分开计算,只在输出层进行一次融合得到结果 | DeepFM,DCN,Wide&Deep |
串行结构 | 将FM的一次项和二次项结果(或其中之一)作为DNN部分的输入,经DNN得到最终结果 | FNN,NFM,AFM |
今天介绍的NFM模型(Neural Factorization Machine),便是串行结构中一种较为简单的网络模型。NFM摒弃了直接把嵌入向量拼接输入到神经网络的做法,在嵌入层之后增加了Bi-Interaction操作来对二阶组合特征进行建模。这使得low level的输入表达的信息更加的丰富,极大的提高了后面隐藏层学习高阶非线性组合特征的能力。
二:原理
公式:
![]() |
其中第一项和第二项是线性回归部分,与FM相似,FM模拟数据的全局偏差和特征权重。第三项f(x)是NFM的核心组成部分,用于建模特征交互。它是一个多层前馈神经网络。如图2所示,接下来,我们一层一层地阐述f(x)的设计。
模型结构:
![]() |
2.1 Embedding Layer
和其他的DNN模型处理稀疏输入一样,Embedding将输入转换到低维度的稠密的嵌入空间中进行处理。这里做稍微不同的处理是,使用原始的特征值乘以Embedding vector,使得模型也可以处理real valued feature。
2.2 Bi-Interaction Layer
Bi是Bi-linear的缩写,这一层其实是一个pooling层操作,它把很多个向量转换成一个向量,其实它就是计算FM中的二次项的过程,因此得到的向量维度就是我们的Embedding的维度:
![]() |
代码:
with tf.variable_scope('interaction_layer'):
self.sum_square_emb = tf.square(tf.reduce_sum(self.embeddings, axis=1)) # [None, embedding_size]
self.square_sum_emb = tf.reduce_sum(tf.square(self.embeddings), axis=1) # [None, embedding_size]
self.fully_out = 0.5 * tf.subtract(self.sum_square_emb, self.square_sum_emb) # [None, embedding_size]
Hidden Layers就是我们的DNN部分,将Bi-Interaction Layer得到的结果接入多层的神经网络进行训练,从而捕捉到特征之间复杂的非线性关系。
在进行多层训练之后,将最后一层的输出求和同时加上一次项和偏置项,就得到了我们的预测输出:
![]() |
三:代码实现
with tf.name_scope('Embedding_Layer'):
self.embeddings = tf.nn.embedding_lookup(self.weights['feature_embeddings'],
self.feature_index) # [None, field_size, embedding_size]
feat_value = tf.reshape(self.feature_value, shape=[-1, self.field_size, 1]) # [None, field_size, 1]
self.embeddings = tf.multiply(self.embeddings, feat_value) # [None, field_size, embedding_size]
with tf.name_scope('linear_part'):
self.linear_part = tf.nn.embedding_lookup(self.weights['linear_w'],
self.feature_index) # [None, field_size, 1]
self.linear_part = tf.reduce_sum(tf.multiply(self.linear_part, feat_value),
axis=2) # [None, field_size]
self.linear_part = tf.nn.dropout(self.linear_part, self.dropout_keep_fm[0]) # [None, field_size]
self.linear_out = tf.reduce_sum(self.linear_part, axis=1, keepdims=True) # [None, 1]
self.w0 = tf.multiply(self.biases['w0'], tf.ones_like(self.linear_out)) # [None, 1]
with tf.variable_scope('interaction_layer'):
self.sum_square_emb = tf.square(tf.reduce_sum(self.embeddings, axis=1)) # [None, embedding_size]
self.square_sum_emb = tf.reduce_sum(tf.square(self.embeddings), axis=1) # [None, embedding_size]
self.fully_out = 0.5 * tf.subtract(self.sum_square_emb, self.square_sum_emb) # [None, embedding_size]
with tf.name_scope('fully_layer'):
for i in range(len(self.deep_layers)):
self.fully_out = tf.add(tf.matmul(self.fully_out, self.weights[i]), self.biases[i])
self.fully_out = self.deep_layers_activation(self.fully_out)
if (self.batch_norm):
self.fully_out = self.batch_norm_layer(self.fully_out, self.train_phase)
self.fully_out = tf.nn.dropout(self.fully_out, keep_prob=self.dropout_fm[i])
with tf.name_scope('out'):
self.out = tf.add_n([self.w0, self.linear_out, self.fully_out]) # # yAFM = w0 + wx + f(x)
四:总结
NFM主要的特点:
1.NFM核心就是在NN中引入了Bilinear Interaction(Bi-Interaction) pooling操作。基于此,NN可以在low level就学习到包含更多信息的组合特征。
2. 通过dnn来学习高阶的非线性的组合特征。
3. NFM相比于上面提到的DNN模型,模型结构更浅、更简单(shallower structure),但是性能更好,训练和调整参数更加容易。
所以,依旧是FM+DNN的组合套路,不同之处在于如何处理Embedding向量,这也是各个模型重点关注的地方。现在来看业界就如何用DNN来处理高维稀疏的数据并没有一个统一普适的方法,依旧在摸索中。
参考:
https://www.jianshu.com/p/4e65723ee632