参考文章:
TensorFlow 2.0 implementation of NFM
Reference:
Neural Factorization Machines for Sparse Predictive Analytics
https://www.jianshu.com/p/4e65723ee632
该系列主要是复现一些经典的网络结构与顶会论文的网络结构,我一开始看论文,以为看到网络结构和了解结构原理后,就完全学到了这篇论文的精髓,谁知,等到自己想要用这个网络结构时,无法打通理解与代码复现的这一步,这就导致我在科研或者工作时的束手无措,因此,我就决定探究如何将一篇论文中的结构和论文中的公式用代码的形式复现出来。
深度学习框架:tensorflow2.0 ,numpy。
语言:python。
复现的代码全部在:https://github.com/Snail110/recsys。
FM和深度网络DNN的结合也就成为了CTR预估问题中主流的方法。有关FM和DNN的结合有两种主流的方法,并行结构和串行结构。两种结构的理解以及实现如下表所示:
结构 | 描述 | 常见模型 |
---|---|---|
并行结构 | FM部分和DNN部分分开计算,只在输出层进行一次融合得到结果 | DeepFM,DCN,Wide&Deep |
串行结构 | 将FM的一次项和二次项结果(或其中之一)作为DNN部分的输入,经DNN得到最终结果 | PNN,NFM,AFM |
该部分主要是将论文中公式与结构图对应起来,理解每一个公式的含义以及网络结构图中每一部分的输入输出。
首先,当你看完一篇论文并理解了论文的主要思想后,需要尝试着将网络结构与论文中的每一步的数学公式一一对应上,在心中或者图片上协商每一个环节的数学公式,然后考虑用深度学习框架来实现。
首先这篇论文中有数学公式(1),(2),(3)对应着网络模型。
然后需要一步一步的将公式对应到网络模型中,
输出层是若干层的全连接层,经过多次的deep layers 获取深度特征。
Bi-Interaction Layer名字挺高大上的,其实它就是计算FM中的二次项的过程,因此得到的向量维度就是我们的Embedding的维度。最终的结果是:
Hidden Layers就是我们的DNN部分,将Bi-Interaction Layer得到的结果接入多层的神经网络进行训练,从而捕捉到特征之间复杂的非线性关系。
在进行多层训练之后,将最后一层的输出求和同时加上一次项和偏置项,就得到了我们的预测输出:
该部分主要是按照网络结构图,用代码的方式实现。在代码实现的过程中,我们需要紧紧结合数学公式体会其中的含义以及如何用代码来实现这些数学公式。
class BiInteraction(tf.keras.layers.Layer):
def __init__(self, Units=1, **kwargs):
self.units = Units
super(BiInteraction, self).__init__(**kwargs)
def build(self, input_shape):
input_dim = input_shape[2]
# self.W = self.add_weight(shape=(input_dim, self.units), initializer='random_normal', trainable=True)
# self.b = self.add_weight(shape=(input_dim, self.units), initializer='random_normal', trainable=True)
self.linearlayer = tf.keras.layers.Dense(input_dim, activation='relu', use_bias=True)
def call(self, input):
# sum-square-part
self.summed_features_emb = tf.reduce_sum(input,1) # None * K
# print("self.summed_features_emb:",self.summed_features_emb.get_shape())
self.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K
# square-sum-part
self.squared_features_emb = tf.square(input)
self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb,1) # None * K
# second order
self.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square,self.squared_sum_features_emb) # None * K
# print("y_second_order:",self.y_second_order.get_shape()) # 128 * 10
output = self.linearlayer(self.y_second_order)
return output
class NFM(tf.keras.Model):
def __init__(self, num_feat, num_field, dropout_deep, deep_layer_sizes, embedding_size=10):
super().__init__()
self.num_feat = num_feat # F =features nums
self.num_field = num_field # N =fields of a feature
self.dropout_deep = dropout_deep
# Embedding 这里采用embeddings层因此大小为F* M F为特征数量,M为embedding的维度
feat_embeddings = tf.keras.layers.Embedding(num_feat, embedding_size,
embeddings_initializer='uniform') # F * M
self.feat_embeddings = feat_embeddings
# fc layer
self.deep_layer_sizes = deep_layer_sizes
# 神经网络方面的参数
for i in range(len(deep_layer_sizes)):
setattr(self, 'dense_' + str(i), tf.keras.layers.Dense(deep_layer_sizes[i]))
setattr(self, 'batchNorm_' + str(i), tf.keras.layers.BatchNormalization())
setattr(self, 'activation_' + str(i), tf.keras.layers.Activation('relu'))
setattr(self, 'dropout_' + str(i), tf.keras.layers.Dropout(dropout_deep[i]))
self.bilayer = BiInteraction(1)
# last layer
self.fc = tf.keras.layers.Dense(1, activation=None, use_bias=True)
self.linearlayer = tf.keras.layers.Dense(deep_layer_sizes[-1], activation='relu', use_bias=True)
def call(self, feat_index, feat_value):
# call函数接收输入变量
# embedding part feat_index = inputs为输入 feat_embeddings为一个layer。
feat_embedding_0 = self.feat_embeddings(feat_index) # Batch * N * M
# print(feat_value.get_shape())
feat_embedding = tf.einsum('bnm,bn->bnm', feat_embedding_0, feat_value)
y_deep = self.attentionlayer(feat_embedding)
y_linear = self.linearlayer(tf.reduce_sum(feat_embedding,1))
for i in range(len(self.deep_layer_sizes)):
y_deep = getattr(self, 'dense_' + str(i))(y_deep)
y_deep = getattr(self, 'batchNorm_' + str(i))(y_deep)
y_deep = getattr(self, 'activation_' + str(i))(y_deep)
y_deep = getattr(self, 'dropout_' + str(i))(y_deep)
y = y_deep + y_linear
output = self.fc(y)
return output
你看,通过这样一步步将公式与代码对应起来,就好实现多了,对于不同的计算公式采用不同的函数需要多看文档,这样才可以选用正确的api。
最后,如果需要获取全部代码,请看下我的github上仓库:https://github.com/Snail110/recsys
这里面是用tensorflow2.0框架来写。如果觉得我实现的还不错,记得给我一个星星哦。