參考文章:
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框架來寫。如果覺得我實現的還不錯,記得給我一個星星哦。