教你复现顶会论文网络结构(三)--Wide&Deep模型网络结构

参考文章:
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。

0.介绍

2016年,Google提出Wide&Deep模型,将线性模型与DNN很好的结合起来,在提高模型泛化能力的同时,兼顾模型的记忆性。Wide&Deep这种线性模型与DNN的并行连接模式,后来成为推荐领域的经典模式。

1.网络结构

该部分主要是将论文中公式与结构图对应起来,理解每一个公式的含义以及网络结构图中每一部分的输入输出。
教你复现顶会论文网络结构(三)--Wide&Deep模型网络结构_第1张图片
首先,当你看完一篇论文并理解了论文的主要思想后,需要尝试着将网络结构与论文中的每一步的数学公式一一对应上,在心中或者图片上协商每一个环节的数学公式,然后考虑用深度学习框架来实现。
首先这篇论文中有数学公式(1),(2),(3)对应着网络模型。
然后需要一步一步的将公式对应到网络模型中,

公式(1)

在这里插入图片描述
wide部分输入时x = [x1; x2; :::; xd],输出是y,在这里需要注意的是交叉特征的构建,即公式1,因此在数据特征准备时,需要进行交叉特征的构建。

公式(2)

在这里插入图片描述
公式2中表示将embedding的向量与稠密向量拼接在一起后,Deep部分的网络结构

公式(3)

在这里插入图片描述
这个公式是两部分的拼接过程。最终输出概率值。

2.代码复现

该部分主要是按照网络结构图,用代码的方式实现。在代码实现的过程中,我们需要紧紧结合数学公式体会其中的含义以及如何用代码来实现这些数学公式。
我是基于数据集:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction来实现的。

Wide Layers

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

Deep Layers

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 

Concatenate layer

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

Train

    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()))

3.总结

你看,通过这样一步步将公式与代码对应起来,就好实现多了,对于不同的计算公式采用不同的函数需要多看文档,这样才可以选用正确的api。
最后,如果需要获取全部代码,请看下我的github上仓库:https://github.com/Snail110/recsys

这里面是用tensorflow2.0框架来写。如果觉得我实现的还不错,记得给我一个星星哦。

你可能感兴趣的:([Recommender,System],[Deep,Learning])