1. 程式人生 > >[阿里DIN] 深度興趣網路原始碼分析 之 整體程式碼結構

[阿里DIN] 深度興趣網路原始碼分析 之 整體程式碼結構

# [阿里DIN] 深度興趣網路原始碼分析 之 整體程式碼結構 [toc] ## 0x00 摘要 Deep Interest Network(DIN)是阿里媽媽精準定向檢索及基礎演算法團隊在2017年6月提出的。其針對電子商務領域(e-commerce industry)的CTR預估,重點在於充分利用/挖掘使用者歷史行為資料中的資訊。 本文為系列第三篇,將分析DIN原始碼整體思路。採用的是 https://github.com/mouna99/dien 中的實現。 因為此專案包括 DIN,DIEN 等幾個模型,所以部分檔案是 DIEN 模型使用,這裡也順帶提一下,後續會有專門文章講解。 ## 0x01 檔案簡介 資料檔案主要包括: - **uid_voc.pkl**:使用者字典,使用者名稱對應的id; - **mid_voc.pkl**:movie字典,item對應的id; - **cat_voc.pkl**:種類字典,category對應的id; - **item-info**:item對應的category資訊; - **reviews-info**:review 元資料,格式為:userID,itemID,評分,時間戳,用於進行負取樣的資料; - **local_train_splitByUser**:訓練資料,一行格式為:label、使用者名稱、目標item、 目標item類別、歷史item、歷史item對應類別; - **local_test_splitByUser**:測試資料,格式同訓練資料; 程式碼主要包含: - **rnn.py**:對tensorflow中原始的rnn進行修改,目的是將attention同rnn進行結合 - **vecAttGruCell.py**: 對GRU原始碼進行修改,將attention加入其中,設計AUGRU結構 - **data_iterator.py**: 資料迭代器,用於資料的不斷輸入 - **utils.py**:一些輔助函式,如dice啟用函式、attention score計算等 - **model.py**:DIEN模型檔案 - **train.py**:模型的入口,用於訓練資料、儲存模型和測試資料 ## 0x02 總體架構 DIN 試圖捕獲之前點選的 item 和目標 item 之間的不同相似性。 首先還是要從論文中摘取架構圖進行說明。 ![](https://img2020.cnblogs.com/blog/1850883/202010/1850883-20201021214201216-931825598.png) - Deep Interest NetWork有以下幾點創新: 1. **針對Diversity:** 針對使用者廣泛的興趣,DIN用*an interest distribution*去表示,即用 Pooling(weighted sum)對Diversity建模(對使用者多種多樣的興趣建模)。 2. **針對Local Activation:**利用attention機制實現 Local Activation,從使用者歷史行為中動態學習使用者興趣的embedding向量,針對不同的廣告構造不同的使用者抽象表示,從而實現了在資料維度一定的情況下,更精準地捕捉使用者當前的興趣。對使用者歷史行為進行了不同的加權處理,針對不同的廣告,使用者歷史行為的權重不一致。即針對當前候選Ad,去區域性的啟用(*Local Activate*)相關的歷史興趣資訊。和當前候選Ad相關性越高的歷史行為,會獲得更高的*attention score*,從而會主導這一次預測。 3. CTR中**特徵稀疏而且維度高**,通常利用L1、L2、Dropout等手段防止過擬合。由於傳統L2正則計算的是全部引數,CTR預估場景的模型引數往往數以億計。DIN提出了一種正則化方法,在每次小批量迭代中,給與不同頻次的特徵不同的正則權重; 4. 由於傳統的**啟用函式**,如Relu在輸入小於0時輸出為0,將導致許多網路節點的迭代速度變慢。PRelu雖然加快了迭代速度,但是其分割點預設為0,實際上分割點應該由資料決定。因此,DIN提出了一種資料動態自適應啟用函式Dice。 5. **針對大規模稀疏資料的模型訓練:**當DNN深度比較深(引數非常多),輸入又非常稀疏的時候,很容易過擬合。DIN提出**Adaptive regularizaion**來防止過擬合,效果顯著。 ## 0x03 總體程式碼 DIN程式碼是從train.py開始。train.py 先用初始模型評估一遍測試集,然後呼叫 train: - 獲取 訓練資料 和 測試資料,這兩個都是資料迭代器,用於資料的不斷輸入 - 根據 model_type 生成相應的model - 按照batch訓練,每1000次評估測試集。 程式碼如下: ```python def train( train_file = "local_train_splitByUser", test_file = "local_test_splitByUser", uid_voc = "uid_voc.pkl", mid_voc = "mid_voc.pkl", cat_voc = "cat_voc.pkl", batch_size = 128, maxlen = 100, test_iter = 100, save_iter = 100, model_type = 'DNN', seed = 2, ): with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess: ## 訓練資料 train_data = DataIterator(train_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen, shuffle_each_epoch=False) ## 測試資料 test_data = DataIterator(test_file, uid_voc, mid_voc, cat_voc, batch_size, maxlen) n_uid, n_mid, n_cat = train_data.get_n() ...... elif model_type == 'DIN': model = Model_DIN(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE) elif model_type == 'DIEN': model = Model_DIN_V2_Gru_Vec_attGru_Neg(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE) ...... sess.run(tf.global_variables_initializer()) sess.run(tf.local_variables_initializer()) iter = 0 lr = 0.001 for itr in range(3): loss_sum = 0.0 accuracy_sum = 0. aux_loss_sum = 0. for src, tgt in train_data: uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, noclk_mids, noclk_cats = prepare_data(src, tgt, maxlen, return_neg=True) loss, acc, aux_loss = model.train(sess, [uids, mids, cats, mid_his, cat_his, mid_mask, target, sl, lr, noclk_mids, noclk_cats]) loss_sum += loss accuracy_sum += acc aux_loss_sum += aux_loss iter += 1 if (iter % test_iter) == 0: eval(sess, test_data, model, best_model_path) loss_sum = 0.0 accuracy_sum = 0.0 aux_loss_sum = 0.0 if (iter % save_iter) == 0: model.save(sess, model_path+"--"+str(iter)) lr *= 0.5 ``` ## 0x04 模型基類 模型的基類是 Model,其建構函式`__init__`可以理解為 行為序列層(Behavior Layer)
:主要作用是將使用者瀏覽過的商品轉換成對應的embedding,並且按照瀏覽時間做排序,即把原始的id類行為序列特徵轉換成Embedding行為序列。 ### 4.1 基本邏輯 基本邏輯如下: - 在 'Inputs' scope下,構建各種 placeholder 變數; - 在 'Embedding_layer' scope下,構建user, item的embedding lookup table,將輸入資料轉換為對應的embedding; - 把 各種 embedding vector 結合起來,比如將item的id對應的embedding 以及 item對應的cateid的embedding進行拼接,共同作為item的embedding; ### 4.2 模組分析 下面的 B 是 batch size,T 是序列長度,H 是hidden size,程式中初始化變數如下: ```python EMBEDDING_DIM = 18 HIDDEN_SIZE = 18 * 2 ATTENTION_SIZE = 18 * 2 best_auc = 0.0 ``` #### 4.2.1 構建變數 首先是構建placeholder變數。 ```python with tf.name_scope('Inputs'): # shape: [B, T] #使用者行為特徵(User Behavior)中的 movie id 歷史行為序列。T為序列長度 self.mid_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='mid_his_batch_ph') # shape: [B, T] #使用者行為特徵(User Behavior)中的 category id 歷史行為序列。T為序列長度 self.cat_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='cat_his_batch_ph') # shape: [B], user id 序列。 (B:batch size) self.uid_batch_ph = tf.placeholder(tf.int32, [None, ], name='uid_batch_ph') # shape: [B], movie id 序列。 (B:batch size) self.mid_batch_ph = tf.placeholder(tf.int32, [None, ], name='mid_batch_ph') # shape: [B], category id 序列。 (B:batch size) self.cat_batch_ph = tf.placeholder(tf.int32, [None, ], name='cat_batch_ph') self.mask = tf.placeholder(tf.float32, [None, None], name='mask') # shape: [B]; sl:sequence length,User Behavior中序列的真實序列長度(?) self.seq_len_ph = tf.placeholder(tf.int32, [None], name='seq_len_ph') # shape: [B, T], y: 目標節點對應的 label 序列, 正樣本對應 1, 負樣本對應 0 self.target_ph = tf.placeholder(tf.float32, [None, None], name='target_ph') # 學習速率 self.lr = tf.placeholder(tf.float64, []) self.use_negsampling =use_negsampling if use_negsampling: self.noclk_mid_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_mid_batch_ph') #generate 3 item IDs from negative sampling. self.noclk_cat_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_cat_batch_ph') ``` 具體各種shape可以參見下面執行時變數 ```python self = {Model_DIN_V2_Gru_Vec_attGru_Neg} cat_batch_ph = {Tensor} Tensor("Inputs/cat_batch_ph:0", shape=(?,), dtype=int32) uid_batch_ph = {Tensor} Tensor("Inputs/uid_batch_ph:0", shape=(?,), dtype=int32) mid_batch_ph = {Tensor} Tensor("Inputs/mid_batch_ph:0", shape=(?,), dtype=int32) cat_his_batch_ph = {Tensor} Tensor("Inputs/cat_his_batch_ph:0", shape=(?, ?), dtype=int32) mid_his_batch_ph = {Tensor} Tensor("Inputs/mid_his_batch_ph:0", shape=(?, ?), dtype=int32) lr = {Tensor} Tensor("Inputs/Placeholder:0", shape=(), dtype=float64) mask = {Tensor} Tensor("Inputs/mask:0", shape=(?, ?), dtype=float32) seq_len_ph = {Tensor} Tensor("Inputs/seq_len_ph:0", shape=(?,), dtype=int32) target_ph = {Tensor} Tensor("Inputs/target_ph:0", shape=(?, ?), dtype=float32) noclk_cat_batch_ph = {Tensor} Tensor("Inputs/noclk_cat_batch_ph:0", shape=(?, ?, ?), dtype=int32) noclk_mid_batch_ph = {Tensor} Tensor("Inputs/noclk_mid_batch_ph:0", shape=(?, ?, ?), dtype=int32) use_negsampling = {bool} True ``` #### 4.2.2 構建embedding 然後是構建user, item的embedding lookup table,將輸入資料轉換為對應的embedding,就是把稀疏特徵轉換為稠密特徵。關於 embedding 層的原理和程式碼分析,本系列會有專文講解
。 後續的 U 是user_id的hash bucket size,I 是item_id的hash bucket size,C 是cat_id的hash bucket size。 注意 self.mid_his_batch_ph這樣的變數 儲存使用者的歷史行為序列, 大小為 [B, T],所以在進行 embedding_lookup 時,輸出大小為 [B, T, H/2]; ```python # Embedding layer with tf.name_scope('Embedding_layer'): # shape: [U, H/2], user_id的embedding weight. U是user_id的hash bucket size,即user count self.uid_embeddings_var = tf.get_variable("uid_embedding_var", [n_uid, EMBEDDING_DIM]) # 從uid embedding weight 中取出 uid embedding vector self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var, self.uid_batch_ph) # shape: [I, H/2], item_id的embedding weight. I是item_id的hash bucket size,即movie count self.mid_embeddings_var = tf.get_variable("mid_embedding_var", [n_mid, EMBEDDING_DIM]) # 從mid embedding weight 中取出 uid embedding vector self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_batch_ph) # 從mid embedding weight 中取出 mid history embedding vector,是正樣本 # 注意 self.mid_his_batch_ph這樣的變數 儲存使用者的歷史行為序列, 大小為 [B, T],所以在進行 embedding_lookup 時,輸出大小為 [B, T, H/2]; self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_his_batch_ph) # 從mid embedding weight 中取出 mid history embedding vector,是負樣本 if self.use_negsampling: self.noclk_mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.noclk_mid_batch_ph) # shape: [C, H/2], cate_id的embedding weight. C是cat_id的hash bucket size self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM]) # 從 cid embedding weight 中取出 cid history embedding vector,是正樣本 # 比如cat_embeddings_var 是(1601, 18),cat_batch_ph 是(?,),則cat_batch_embedded 就是 (?, 18) self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph) # 從 cid embedding weight 中取出 cid embedding vector,是正樣本 self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph) # 從 cid embedding weight 中取出 cid history embedding vector,是負樣本 if self.use_negsampling: self.noclk_cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.noclk_cat_batch_ph) ``` 具體各種shape可以參見下面執行時變數 ```python self = {Model_DIN_V2_Gru_Vec_attGru_Neg} cat_embeddings_var = {Va