教你復現頂會論文網絡結構(三)--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.網絡結構

該部分主要是將論文中公式與結構圖對應起來,理解每一個公式的含義以及網絡結構圖中每一部分的輸入輸出。
在這裏插入圖片描述
首先,當你看完一篇論文並理解了論文的主要思想後,需要嘗試着將網絡結構與論文中的每一步的數學公式一一對應上,在心中或者圖片上協商每一個環節的數學公式,然後考慮用深度學習框架來實現。
首先這篇論文中有數學公式(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框架來寫。如果覺得我實現的還不錯,記得給我一個星星哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章