教你復現頂會論文網絡結構(一)--DCN模型網絡結構(Keras)

參考文章:
1、https://blog.csdn.net/roguesir/article/details/79763204
2、論文:https://arxiv.org/abs/1708.05123
3、https://www.jianshu.com/p/77719fc252fa
4、https://zhuanlan.zhihu.com/p/55234968 edition=yidianzixun&utm_source=yidianzixun&yidian_docid=0L8oiFUo

概述

該系列主要是復現一些經典的網絡結構與頂會論文的網絡結構,我一開始看論文,以爲看到網絡結構和了解結構原理後,就完全學到了這篇論文的精髓,誰知,等到自己想要用這個網絡結構時,無法打通理解與代碼復現的這一步,這就導致我在科研或者工作時的束手無措,因此,我就決定探究如何將一篇論文中的結構和論文中的公式用代碼的形式復現出來。
深度學習框架:tensorflow2.0 keras(會側重在Keras),numpy。
語言:python。
復現的代碼全部在:https://github.com/Snail110/recsys。

0.介紹

本文介紹斯坦福與Google聯合發表在AdKDD 2017上的論文《Deep & Cross Network for Ad Click Predictions》。這篇論文是Google 對 Wide & Deep工作的一個後續研究,文中提出 Deep & Cross Network,將Wide部分替換爲由特殊網絡結構實現的Cross,自動構造有限高階的交叉特徵,並學習對應權重,告別了繁瑣的人工叉乘。文章發表後業界就有一些公司效仿此結構並應用於自身業務,成爲其模型更新迭代中的一環。

1.網絡結構

該部分主要是將論文中公式與結構圖對應起來,理解每一個公式的含義以及網絡結構圖中每一部分的輸入輸出。
在這裏插入圖片描述
首先,當你看完一篇論文並理解了論文的主要思想後,需要嘗試着將網絡結構與論文中的每一步的數學公式一一對應上,在心中或者圖片上協商每一個環節的數學公式,然後考慮用深度學習框架來實現。
首先這篇論文中有數學公式(1),(2),(3),(4),(5),(6)對應着網絡模型。
然後需要一步一步的將公式對應到網絡模型中,

公式(1)

在這裏插入圖片描述
說的是embedding過程,那麼對應着網絡圖中是Spare feature到Embedding vec。屬於比較常見的將稀疏特徵轉換成稠密特徵的過程,但是在這裏需要注意的是每一個field的embedding的長度需要計算得到:每個categorical特徵嵌入維度爲: 6(category)1/4 6*(category)^{1/4} category是每field的unique個數。

公式(2)

在這裏插入圖片描述
公式2中表示將embedding的向量與稠密向量拼接在一起,主要是講稀疏特徵域數值型特徵拼接在一起。對應圖二就是 embedding and stacking layer。

公式(3)

在這裏插入圖片描述
這個公式是本篇論文的核心思想,是cross network的參數更新環節。 x0x_{0}是公式2中的輸出,同時是公式3的輸入,xlx_{l}表示l層的輸入,xl+1x_{l+1}表示l層的輸出及l+1的輸入。因此公式3對應着圖中cross部分。

公式(4)

在這裏插入圖片描述
公式4是常見的deep的全連接層的參數更新公式,不需要過多解釋,對應着圖中的deep部分。

公式(5)

在這裏插入圖片描述
公式5是在deep部分輸出和cross輸出後的計算公式,對應着combination layer層。

2.代碼復現

該部分主要是按照網絡結構圖,用代碼的方式實現。在代碼實現的過程中,我們需要緊緊結合數學公式體會其中的含義以及如何用代碼來實現這些數學公式。
我是基於數據集:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction來實現的。

Embedding and stacking layer

這部分主要是實現 sparse 到embedding vector過程以及數值型vector’和embedding vector之間的拼接。

from keras import layers
from keras.layers import Input,Dense,Embedding,Reshape,Add,Flatten,merge,Lambda,concatenate
from keras.optimizers import Adam
from keras.models import Model
from keras.utils import plot_model, np_utils
# from sklearn.preprocessing import OneHotEncoder,StandarScaler
from sklearn.metrics import accuracy_score
import random
from keras import backend as K

這裏採用的是keras框架,那麼從sparse 到embedding vector 我們用的keras.layers.Embedding函數。

def embedding_input(name,input_dim,output_dim):
	# embedding的輸入和輸出 
    inp = Input(shape = (1,),dtype = 'int64',name = name)
    # 對應公式1
    embeddings = Embedding(input_dim,output_dim,input_length =1)(inp)
    return inp,embeddings
def continus_input(name):
	#  數值型 vec的輸入與輸出
    inp = Input(shape=(1,),dtype = 'float32',name = name)
    return inp, Reshape((1,1))(inp)

由於不同的sparse特徵具有不同的長度,則embedding_layer函數需要迭代這些特徵,一一進行設置輸入大小與輸出大小,然後將這sparse特徵和數值型特徵進行拼接。

def embedding_layers(fd):
    # 該函數主要是定義輸入和embedding輸入的網絡層
    embeddings_tensors = []
    continus_tensors = []
    cate_feature = fd.feat_cate_len
    numeric_feature = fd.numeric_cols
    for ec in cate_feature:
        layer_name = ec + '_inp'
        # for categorical features, embedding特徵在維度保持在6×(category cardinality)**(1/4)
        embed_dim = cate_feature[ec] if int(6 * np.power(cate_feature[ec],1/4)) > cate_feature[ec] else int(6 * np.power(cate_feature[ec],1/4))
        t_inp, t_embedding = embedding_input(layer_name,cate_feature[ec],embed_dim)
        embeddings_tensors.append((t_inp,t_embedding))
        del (t_inp, t_embedding)
    for cc in numeric_feature:
        layer_name = cc +'_in'
        t_inp,t_build = continus_input(layer_name)
        continus_tensors.append((t_inp,t_build))
        del (t_inp,t_build)
    # category feature的輸入 這裏的輸入特徵順序一致  對應公式2
    inp_layer = [et[0] for et in embeddings_tensors]
    inp_embed = [et[1] for et in embeddings_tensors]
    # numeric feature的輸入
    inp_layer += [ct[0] for ct in continus_tensors]
    inp_embed += [ct[1] for ct in continus_tensors]
    
    return inp_layer,inp_embed

Cross Layer

該部分主要是定義交叉層代碼,主要實現公式3,**在這裏需要注意是公式3的矩陣乘法是x0x_{0}xTx^{T}W,但是在我們實現代碼的時候需要先計算xTx^{T}W,這樣的結果是得到度下降很多值,大大節省內存,降低計算量。
(x0x_{0}
xTx^{T})W = x0x_{0}(xTx^{T}*W)

class CrossLayer(layers.Layer):
    def __init__(self,output_dim,num_layer,**kwargs):
        self.output_dim = output_dim
        self.num_layer = num_layer
        super(CrossLayer,self).__init__(**kwargs)
    
    def build(self,input_shape):
        self.input_dim = input_shape[2]
        self.W = []
        self.bias = []
        for i in range(self.num_layer):
            self.W.append(self.add_weight(shape=[1,self.input_dim],initializer = 'glorot_uniform',name='w_{}'.format(i),trainable=True))
            self.bias.append(self.add_weight(shape=[1,self.input_dim],initializer = 'zeros',name='b_{}'.format(i),trainable=True))
        self.built = True
    def call(self,input):
        for i in range(self.num_layer):
            if i==0:
#                 cross = Lambda(lambda x: Add()([K.sum(self.W[i]*K.batch_dot(K.reshape(x,(-1,self.input_dim,1)),x),axis=1,keepdims=True),self.bias[i],x]))(input)
                # 這種方法利於內存釋放,先計算矩陣中簡單的計算 對應公式3
                cross = Lambda(lambda x: K.batch_dot(K.dot(x,K.transpose(self.W[i])),x) + self.bias[i] + x)(input)
            else:
#                 cross = Lambda(lambda x: Add()([K.sum(self.W[i]*K.batch_dot(K.reshape(x,(-1,self.input_dim,1)),input),axis=1,keepdims=True),self.bias[i],input]))(cross)
                cross = Lambda(lambda x: K.batch_dot(K.dot(x,K.transpose(self.W[i])),input) + self.bias[i] + x)(cross)
        return Flatten()(cross)
        
    def compute_output_shape(self,input_shape):
        return (None,self.output_dim)   

Deep layer and Concatenate layer

def DCN(inp_layer,inp_embed):
    inp = concatenate(inp_embed,axis=-1)
    #deep layer 對應公式 4
    for i in range(6):
        if i ==0:
            deep = Dense(272,activation='relu')(Flatten()(inp))
        else:
            deep = Dense(272,activation='relu')(deep)
    cross = CrossLayer(output_dim = inp.shape[2],num_layer=8,name = "cross_layer")(inp)
        # concat both layers  對應公式5
    output = concatenate([deep,cross],axis=-1)
    output = Dense(2,activation='sigmoid')(output)
    model = Model(inp_layer,output)
    return model

Loss function

def fit(model,X,y):
    model = DCN(inp_layer,inp_embed)
    print(model.summary())
    model.compile(Adam(0.01),loss = 'binary_crossentropy',metrics = ['accuracy'])
    model.fit([X[c] for c in X.columns],y,batch_size=256,epochs=10)

3.總結

你看,通過這樣一步步將公式與代碼對應起來,就好實現多了,對於不同的計算公式採用不同的函數需要多看文檔,這樣纔可以選用正確的api。
最後,如果需要獲取全部代碼,請看下我的github上倉庫:https://github.com/Snail110/recsys/blob/master/DCN-keras.ipynb
這裏面是用keras框架來寫的,其他目錄也有tensorflow框架來寫。如果覺得我實現的還不錯,記得給我一個星星哦。

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