總述:博主前些天對DIN網絡進行了論文翻譯,在翻譯後,又對源碼進行了研究,最後將DIN網絡的重點進行了歸納,可以總結出這樣幾個關鍵點來
1:DIN中的attention方法:利用本地激活層,用於自適應學習用戶的歷史興趣和當前要預估的item的權重
2:評價指標:gauc
3:Dice激活函數:數據自使用激活函數,類似於prelu
4:自適應正則:改變了以往l2正則需要耗費大量計算資源的現狀
DIN網絡實際上可以看做是一個匹配網絡,因爲其在attention層的時候有這樣一個操作,假設是用戶過去7天的下載item列表這個特徵,一共下載了30個item,那麼他會把這30個item和目標item融合在一起,使得每個下載item都和目標item有一定的關係,最後再學習權重,達到論文中描述的那樣
接下來博主會重點關注每一個點,進行詳解
看這篇文章前,建議先看看 DIN 網絡的原論文,或者他的翻譯
https://arxiv.org/pdf/1706.06978.pdf
https://blog.csdn.net/a1066196847/article/details/99467276
一、DIN中的attention方法
1:先看看論文中是怎麼描述這個的
其中{e1,e2,...,eH}是用戶U的長度爲H的行爲的嵌入向量列表,vA是廣告A的嵌入向量,廣告A就是我們的目標item。通過這種方式,vU(A)因不同的廣告而異。 a(·)是前饋網絡,以輸出作爲激活權重,如圖2所示。除了兩個輸入嵌入向量之外,a(·)加上輸出他們的item進入後續網絡,這是一個顯性知識,以幫助相關性建模。
方程(3)的局部激活單元分享了在NMT任務[1]類似的想法。 然而不同從傳統的attention方法, 這個公式裏面和方程3是相當的。旨在保留用戶興趣。也就是說,放棄了a(.)輸出上的softmax歸一化。 相反,在一定程度上被視爲近似值激活用戶興趣的強度。 例如,如果一個用戶的歷史行爲包含90%的衣服和10%電子產品。 有兩個T恤和手機,T恤的候選廣告激活屬於衣服和衣服的大部分歷史行爲可能會比手機獲得更大的vU值(更高的興趣強度)。傳統的attention方法由於對輸出a(.) 進行一定後程度的縮放拉伸,失去了這一特性。
我們已經嘗試過LSTM來模擬用戶的歷史行爲數據順序的方式。 但它沒有任何改善。 不同來自NLP任務中受語法約束的文本,用戶歷史行爲的順序可能包含多個item可以同時帶來利益。 快速跳躍和突然結束這些興趣導致用戶行爲的序列數據似乎是吵雜的。 可能的方向是設計特殊結構來建模這些數據以順序的方式。 我們留待將來研究。
2:再來看看代碼是怎麼實現的
def attention(queries, keys, keys_length):
'''
queries: [B, H] 前面的B代表的是batch_size,取值爲32,H是128,代表向量維度。代表的是預估item
keys: [B, T, H] T是一個batch中,當前特徵最大的長度,每個樣本代表一個樣本的特徵
keys_length: [B]
'''
# H
queries_hidden_units = queries.get_shape().as_list()[-1] #每個query詞的隱藏層神經元是多少,也就是H
# tf.tile爲複製函數,1代表在B上保持一致,tf.shape(keys)[1] 代表在H上覆制這麼多次
# 那麼queries最終shape爲(B, H*T)
queries = tf.tile(queries, [1, tf.shape(keys)[1]])
# queries.shape(B, T, H) 其中每個元素(T,H)代表T行H列,其中每個樣本中,每一行的數據都是一樣的
queries = tf.reshape(queries, [-1, tf.shape(keys)[1], queries_hidden_units])
# 下面4個變量的shape都是(B, T, H),按照最後一個維度concat,所以shape是(B, T, H*4)
# 在這塊就將特徵中的每個item和目標item連接在了一起
din_all = tf.concat([queries, keys, queries-keys, queries*keys], axis=-1)
# (B, T, 80)
d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, name='f1_att', reuse=tf.AUTO_REUSE)
# (B, T, 40)
d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=tf.nn.sigmoid, name='f2_att', reuse=tf.AUTO_REUSE)
# (B, T, 1)
d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att', reuse=tf.AUTO_REUSE)
# (B, 1, T)
# 每一個樣本都是 [1,T] 的維度,和原始特徵的維度一樣,但是這時候每個item已經是特徵中的一個item和目標item混在一起的數值了
d_layer_3_all = tf.reshape(d_layer_3_all, [-1, 1, tf.shape(keys)[1]])
outputs = d_layer_3_all
# Mask,每一行都有T個數字,keys_length長度爲B,假設第1 2個數字是5,6,那麼key_masks第1 2行的前5 6個數字爲True
key_masks = tf.sequence_mask(keys_length, tf.shape(keys)[1]) # [B, T]
key_masks = tf.expand_dims(key_masks, 1) # [B, 1, T]
# 創建一個和outputs的shape保持一致的變量,值全爲1,再乘以(-2 ** 32 + 1),所以每個值都是(-2 ** 32 + 1)
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
outputs = tf.where(key_masks, outputs, paddings) # [B, 1, T]
# Scale
outputs = outputs / (keys.get_shape().as_list()[-1] ** 0.5) # T,根據特徵數目來做拉伸
# Activation
outputs = tf.nn.softmax(outputs) # [B, 1, T]
# Weighted sum
outputs = tf.matmul(outputs, keys) # [B, 1, H]
return outputs
代碼中註釋的很詳細,已經把 attention 的實現辦法描述了出來
3:在論文中還有一個圖,描述了 attention 方法
在文章中是這樣介紹這個圖的:圖2:網絡架構。 左側部分說明了基本模型的網絡(嵌入和MLP)。 嵌入cate_id,shop_id和goods_id屬於一個商品的特徵,然後被連接起來代表用戶行爲中的一個被訪問商品。 右邊的部分是我們的提出的DIN模型。 它引入了一個本地激活單元,用戶興趣的表示可以自適應地變化給出不同的候選廣告。
如果你理解了上面的源碼部分,那這個圖就很好理解了
下面的Goods1到GoodsN實際上是一個特徵,這個特徵可以是用戶過去7天的購買item列表(從圖中可以看出這個用戶7天購買了N個商品),然後把每個item的本身vec向量、屬性向量、以及一些其他信息concat在一起,形成一個新向量,比如說是128維度的,就是代碼中的 keys,右邊的Condidate就是代碼中的 queries。我們假設batch_size是1吧,這樣好介紹些,那麼代碼中的T的大小就是N,因爲整個batch最大的特徵個數就是N。接着就首先把這個 queries 的向量(假設是64維度)和 之前的128維度concat在一起,那麼形成 N,192 的矩陣,然後對這個矩陣做多個 MLP,最終形成 N,1 -> 1,N 的維度,接下來就簡單了。。這樣一套操作還不能算是完成的attention,只能剛好算是上面圖中最右邊的(或者說是第三列)那個圖中的矩形裏面的操作了,,然後上面的 Activation Weight 就是這N個item的權重,這個權重就相當於論文中說的給每個不同的Item以不同的權重。
二:評價指標:gauc
如果看源代碼的話,就會發現數據中不止有一個要預估的item,而是有兩個,其中一個是正label,一個是隨機採的負label,最終算 gauc 的時候也是根據每個樣本對這兩個 label 進行打分的大小關係來算的。
接下來我會從源碼入手
def _eval(sess, model):
auc_sum = 0.0
score_arr = []
# _表示第幾個batch的數據,uij表示這個batch的所有數據
# 格式是:192403 63001 801 [738 157 571 707 714 只取了前5個] 102
# uid itemid ->正樣本 itemid ->負樣本 特徵序列 整個batch中最大的特徵長度
for _, uij in DataInputTest(test_set, test_batch_size):
# auc_的shape是(1,)表示在一個batch(32)中logit分數(logit分數 = 正樣本打分 - 負樣本打分)大於0的比例
# score_ batch(32,2) 前面一列是正樣本的打分 後面是負樣本的打分
auc_, score_ = model.eval(sess, uij)
score_arr += _auc_arr(score_)
# auc_ * len(uij[0]):一個batch中有多少樣本是正樣本分數大於負樣本分數(len(uij[0])的長度是一個batch的長度)
# 進行累加到auc_sum中,就可以記錄出整個測試集中有多少樣本是正樣本分數大於負樣本分數
auc_sum += auc_ * len(uij[0])
# 最終再一除就完事了
test_gauc = auc_sum / len(test_set)
Auc = calc_auc(score_arr)
global best_auc
if best_auc < test_gauc:
best_auc = test_gauc
model.save(sess, 'save_path/ckpt')
return test_gauc, Auc
三:Dice激活函數
我們首先來看看論文中是如何講解的
如何來理解8這個公式:假設s大於0的話,那麼p(s)就位1,那麼這個公式自然成了前面的 s if s>0,反之亦然
其中s是激活函數f(·)輸入的一個維度,和p(s)= I(s> 0)是一個控制f(s)在f(s)= s和f(s)=αs的兩個通道之間切換的媒介。 α在第二個通道是一個學習參數。 這裏我們將p(s)稱爲控制功能。 圖3的左側部分描繪了控制功能PReLU。 PReLU採用值爲0的硬糾正點當每層的輸入遵循不同時,可能不適合分佈。 考慮到這一點,我們設計了一種新穎的數據自適應激活函數命名爲Dice,
也就是說,相對於 PRele,Dice對 p(s) 做了一點改動,控制功能繪製在圖3的右側。在公式中,E [s]和V ar [s]是每個小批量的輸入的均值和方差。 在公式中,E [s]和V ar [s]通過移動平均值E [s]和V ar [s]來計算數據。 ε是一個小常數,在我們的實踐中設定爲10-8。
在開源的代碼中都可以找到,之後有時間我會給出這部分的源碼介紹
四、自適應正則
先來看看論文是怎麼介紹的:
過擬合是工業網絡的關鍵挑戰。例如,添加了細粒度的特徵,例如維度爲6億的goods_ids的特徵(包括如表1所示,用戶的visit_goods_ids和廣告的goods_id,模型性能在沒有正則化的訓練中,第一個時期之後迅速下降,如圖中所示的深綠色線圖4在後面的6.5節中。直接應用傳統的正則化方法是不切實際的,例如ℓ2和ℓ1正則化訓練網絡,輸入稀疏,數以億計參數。以ℓ2正則化爲例。只有每個小批量數據中出現的非零稀疏特徵,在基於SGD的優化方法的場景中進行更新。但是,當加入l2正則化時它需要計算每個參數的L2範數小批量,這導致非常繁重的計算,並且是不可接受的參數擴展到數億。
在本文中,我們介紹了一種有效的小批量感知正則化器,它只計算每個小批量出現的稀疏特徵的參數,並且使得計算正則成爲可能。 實際上,它是一種嵌入字典,並且爲CTR網絡貢獻了大部分參數併產生了繁重計算的難度。 設W∈RD×K表示整個參數嵌入字典,以D爲維度嵌入向量和K作爲特徵空間的維數。在樣本上擴展W上的ℓ2正則化
wj 代表第 j 個向量,I(xj , 0)代表了是否第 i 個實例(樣本)有 j 這個特徵,nj 代表了在整個樣本中 j 特徵的出現次數,在一個小批量中,上述公式可以被寫成
K代表有K個特徵,B代表整個batch。I(xj , 0) 代表了整個 batch 是否有 j 特徵。讓 αmj = max(x,y)∈Bm
那麼上公式可以改寫成
那麼特徵 j 的參數,或者說是一個向量可以用下面的方式來進行更新
在開源的代碼中找不到這部分源碼,之後有時間我會實現出這部分的源碼
參考部分:
原論文:https://arxiv.org/pdf/1706.06978.pdf
已經論文裏面給出來的官方源碼實現:https://github.com/zhougr1993/DeepInterestNetwork