[論文解讀] 阿里DIEN整體程式碼結構
阿新 • • 發佈:2020-11-03
# [論文解讀] 阿里DIEN整體程式碼結構
[toc]
## 0x00 摘要
DIEN是阿里深度興趣進化網路(Deep Interest Evolution Network)的縮寫。
本文將分析DIEN原始碼整體思路。因為DIEN是在DIN基礎上演化,所以程式碼有大部分重複。
本文采用的是 https://github.com/mouna99/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 總體架構
首先還是要從論文中摘取架構圖進行說明。
![](https://img2020.cnblogs.com/blog/1850883/202011/1850883-20201101203713603-1401210995.jpg)
深度興趣進化網路分為幾層,從下到上依次是:
- 行為序列層(Behavior Layer):主要作用是將使用者瀏覽過的商品轉換成對應的embedding,並且按照瀏覽時間做排序,即把原始的id類行為序列特徵轉換成Embedding行為序列;
- 興趣抽取層(Interest Extractor Layer):主要作用是通過模擬使用者的興趣遷移過程,基於行為序列提取使用者興趣序列;
- 興趣進化層(Interest Evolving Layer):主要作用是通過在興趣抽取層基礎上加入Attention機制,模擬與當前目標廣告相關的興趣進化過程,對與目標物品相關的興趣演化過程進行建模;
- 將興趣表示和ad、user profile、context的embedding向量進行拼接。最後使用MLP完成最後的預測;
## 0x03 總體程式碼
DIEN程式碼是從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 == '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,是正樣本
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