参考文章:
1、https://github.com/brightnesss/deep-cross/blob/master/CDNet.py
2、https://zhuanlan.zhihu.com/p/92279796
该系列主要是复现一些经典的网络结构与顶会论文的网络结构,我一开始看论文,以为看到网络结构和了解结构原理后,就完全学到了这篇论文的精髓,谁知,等到自己想要用这个网络结构时,无法打通理解与代码复现的这一步,这就导致我在科研或者工作时的束手无措,因此,我就决定探究如何将一篇论文中的结构和论文中的公式用代码的形式复现出来。
深度学习框架:tensorflow2.0 ,numpy。
语言:python。
复现的代码全部在:https://github.com/Snail110/recsys。
2016年,Google提出Wide&Deep模型,将线性模型与DNN很好的结合起来,在提高模型泛化能力的同时,兼顾模型的记忆性。Wide&Deep这种线性模型与DNN的并行连接模式,后来成为推荐领域的经典模式。
该部分主要是将论文中公式与结构图对应起来,理解每一个公式的含义以及网络结构图中每一部分的输入输出。
首先,当你看完一篇论文并理解了论文的主要思想后,需要尝试着将网络结构与论文中的每一步的数学公式一一对应上,在心中或者图片上协商每一个环节的数学公式,然后考虑用深度学习框架来实现。
首先这篇论文中有数学公式(1),(2),(3)对应着网络模型。
然后需要一步一步的将公式对应到网络模型中,
wide部分输入时x = [x1; x2; :::; xd],输出是y,在这里需要注意的是交叉特征的构建,即公式1,因此在数据特征准备时,需要进行交叉特征的构建。
公式2中表示将embedding的向量与稠密向量拼接在一起后,Deep部分的网络结构
该部分主要是按照网络结构图,用代码的方式实现。在代码实现的过程中,我们需要紧紧结合数学公式体会其中的含义以及如何用代码来实现这些数学公式。
我是基于数据集:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction来实现的。
class Wide(tf.keras.layers.Layer):
def __init__(self,units=1):
# input_dim = num_size + embed_size = input_size
super(Wide, self).__init__()
# self.units = units
self.linear = tf.keras.layers.Dense(units=units,activation='relu')
def call(self, inputs):
output = self.linear(inputs)
return output
class Deep(tf.keras.layers.Layer):
def __init__(self,num_feat,num_field,dropout_deep,deep_layer_sizes,embedding_size=10):
# input_dim = num_size + embed_size = input_size
super(Deep, self).__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]))
# last layer
self.fc = tf.keras.layers.Dense(1,activation=None,use_bias=True)
def call(self,feat_index,feat_value):
# 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 = tf.keras.layers.Flatten()(feat_embedding)
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)
output = self.fc(y_deep)
return output
class WideDeep(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
self.wide = Wide(units=1)
self.deep = Deep(num_feat,num_field,dropout_deep,deep_layer_sizes)
self.fc = tf.keras.layers.Dense(1,activation=None,use_bias=True)
def call(self,num_input,feat_index,feat_value):
x1 = self.wide(num_input)
x2 = self.deep(feat_index,feat_value)
x3 = tf.keras.layers.concatenate([x1,x2],axis=-1)
output = self.fc(x3)
return output
AID_DATA_DIR = "../data/Criteo/"
feat_dict_ = pickle.load(open(AID_DATA_DIR + '/cross_feat_dict_10.pkl2', 'rb'))
widedeep = WideDeep(num_feat=len(feat_dict_) + 1, num_field=52, dropout_deep=[0.5, 0.5, 0.5],
deep_layer_sizes=[400, 400],embedding_size=10)
train_label_path = AID_DATA_DIR + 'traincross_label'
train_idx_path = AID_DATA_DIR + 'traincross_idx'
train_value_path = AID_DATA_DIR + 'traincross_value'
train_num_path = AID_DATA_DIR + 'traincross_num'
# 这种读取数据方式采用TextLineDataset,数据为大文件时,节省内存,效率训练
def get_batch_dataset(label_path, idx_path, value_path,num_path):
label = tf.data.TextLineDataset(label_path)
idx = tf.data.TextLineDataset(idx_path)
value = tf.data.TextLineDataset(value_path)
num = tf.data.TextLineDataset(num_path)
label = label.map(lambda x: tf.strings.to_number(tf.strings.split(x, sep='\t')), num_parallel_calls=12)
idx = idx.map(lambda x: tf.strings.to_number(tf.strings.split(x, sep='\t')), num_parallel_calls=12)
value = value.map(lambda x: tf.strings.to_number(tf.strings.split(x, sep='\t')), num_parallel_calls=12)
num = num.map(lambda x: tf.strings.to_number(tf.strings.split(x, sep='\t')), num_parallel_calls=12)
batch_dataset = tf.data.Dataset.zip((num,label, idx, value))
batch_dataset = batch_dataset.shuffle(buffer_size=128)
batch_dataset = batch_dataset.batch(128)
batch_dataset = batch_dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
return batch_dataset
train_batch_dataset = get_batch_dataset(train_label_path, train_idx_path, train_value_path,train_num_path)
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.BinaryAccuracy(name='train_acc')
loss_object = tf.keras.losses.BinaryCrossentropy()
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
@tf.function
def train_one_step(model, optimizer, idx, value, label, num):
with tf.GradientTape() as tape:
output = model(num, idx, value)
loss = loss_object(y_true=label, y_pred=output)
grads = tape.gradient(loss, model.trainable_variables)
grads = [tf.clip_by_norm(g, 100) for g in grads]
optimizer.apply_gradients(grads_and_vars=zip(grads, model.trainable_variables))
train_loss(loss)
train_accuracy(label, output)
EPOCHS = 50
for epoch in range(EPOCHS):
for num, label, idx, value in train_batch_dataset:
train_one_step(widedeep, optimizer, idx, value, label,num)
template = 'Epoch {}, Loss: {}, Accuracy: {}'
print(template.format(epoch + 1,
train_loss.result(), train_accuracy.result()))
你看,通过这样一步步将公式与代码对应起来,就好实现多了,对于不同的计算公式采用不同的函数需要多看文档,这样才可以选用正确的api。
最后,如果需要获取全部代码,请看下我的github上仓库:https://github.com/Snail110/recsys
这里面是用tensorflow2.0框架来写。如果觉得我实现的还不错,记得给我一个星星哦。