公衆號
關注公衆號:推薦算法工程師,輸入"進羣",加入交流羣,和小夥伴們一起討論機器學習,深度學習,推薦算法.
前言
Deep Interest Network(DIN) 是蓋坤大神領導的阿里媽媽的精準定向檢索及基礎算法團隊提出的,發表在18年的ACM SIGKDD會議上。這篇文章討論的是電子商業中的CTR預估問題.重點是對用戶的歷史行爲數據進行分析和挖掘.
論文指出,很多CTR預估的模型,比如Deep Cross Network,Wide&Deep Learning等,採用的都是一種相似的模型架構,首先使用Embedding&MLP 從高維稀疏輸入中提取低維稠密vectors,然後使用Sum/Average Pooling等技巧獲得定長vectors.然後使用MLP進行預測.但是這樣做存在一些問題.
首先是將原始的高維稀疏向量壓縮爲低維稠密向量後,定長向量sum pooling會導致信息損失;另外,低維向量的表達能力有限,而用戶的興趣多種多樣(不是幾十種這種量級的多種多樣),爲了提高定長vector的表達能力需要進行維度擴展,然而這容易帶來維度災難等問題,網站物品越來越多的時候總不能一直提高定長vector的維度吧.
然後作者注意到,用戶是否會點擊推薦給他的物品,僅取決於歷史行爲的一部分,並稱之爲local activation.因此,計算推薦物品的點擊率時,只有部分物品代表的興趣分佈真正在起作用.DIN正是通過使用attention機制,對不同推薦的物品,獲得用戶不同的特徵表示,從而進行更加精確的CTR預估.
論文鏈接:https://arxiv.org/pdf/1706.06978.pdf
1.模型分析
1.1 Baseline
首先說一下,論文中使用Goods表示用戶歷史中的廣告/商品,使用Ad代表候選廣告/商品。
首先看下對照組設置的模型,其中user profile表示的意思如下:
顯而易見,首先對各類數據進行稀疏編碼,然後使用MLP獲得稠密向量,把各類特徵串聯起來,後面再來個MLP。
這裏User Behavior使用multi-hot編碼,因爲用戶不太可能只對其中一個廣告感興趣。要注意的是User Profile和Goods id以及Context Features的稠密向量並不一定是同一緯度。用戶的特徵向量怎麼來的呢?將某個用戶歷史中點擊過的物品的稠密向量進行Sum Pooling,就是直接求和,通過它們來代表當前用戶。
1.2 DIN
上圖就是DIN模型,就是比Baseline多了一個Attention機制,下面我們重點講講這個attention的細節。
顯而易見,所謂attention,就是給用戶歷史記錄中不同廣告賦予不同的權重,DIN中這個權重是一個和候選Ad相關的函數,也就是上圖右上角的Activation Unit:將Inputs from user和Inputs from Candidate Ad這兩部分特徵做交互,每個Goods都需要和Candidate Ad通過這個Activation Unit來獲得該Goods的權重。
然後和baseline一樣,使用Sum Pooling獲得定長vector作爲用戶的representation,其中權重函數a就是上圖中的Activation Unit:
但是同樣是定長的user vector,候選廣告不同時,DIN中user vector是不同的,而baseline中是相同的,顯然DIN獲得的用戶representation更具有靈活性,也更加“精確”。
DIN中的Attention部分, 簡單地說,就是和Goods和Candidate Ad有關的權重函數,賦予不同Goods不同權重,從而獲得更好的用戶representation。
2. 訓練技巧
2.1 Dice激活函數
PRelu是一種常用的激活函數:
但作者認爲,不應該所有的突變點都選爲0,而是應該依賴數據分佈。因此,對PRelu進行改進,提出Dice激活函數,其中s是激活函數輸入中的其中一維,即對每維進行去均值求方差操作:
[1]中認爲,去均值化操作使得突變點爲s的均值,實現了data dependent的想法;而使用sigmoid可以得到0-1間的概率值,從而權衡s和αs。
2.2 Mini-batch 正則化
我們知道,電商中的商品數據符合長尾分佈,只有少量商品多次出現,大量商品只出現幾次或不出現。因此User Behaviors很稀疏是肯定的,由於輸入是高維繫數特徵,而模型又很複雜,參數太多,相當於複雜模型而數據不足,這種情況很容易過擬合。
而對大體量的擁有上億參數和非常稀疏的訓練網絡來說,直接應用L1,L2這種傳統正則化方法是不使用的。比如L2正則化,在一個mini-batch中,只有非零特徵對應的參數纔會被更新,然而所有的參數都需要計算L2正則。
因此作者提出進行Mini-batch Aware正則化,只計算每個mini-batch中非零特徵的相關參數:
上述公式可以轉化爲:
此時出現次數越多的非零特徵對應的乘法權重越大。而上述公式可近似爲:
此時amj取值0或1,這種進一步的近似類似從sum pooling到max pooling的轉化,論文並沒有給出原因和證明,我想是爲了簡化訓練過程吧。
3.實戰
3.1 數據集
論文中使用了淘寶和亞馬遜的數據集,數據集都太大了。可以下載一個亞馬遜的小數據集進行實驗:
echo "begin download data"
mkdir raw_data && cd raw_data
wget -c http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz
gzip -d reviews_Electronics_5.json.gz
wget -c http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/meta_Electronics.json.gz
gzip -d meta_Electronics.json.gz
echo "download data successful"
試驗中使用了這個小數據集的迷你版本:reviews_Electronics_5.json和meta_Electronics.json,來自參考代碼。其中reviews_Electronics_5.json:
meta_Electronics.json:
由於兩個數據可能來自不同的部門,因此爲了獲得完整的reviewerID,asin,和categories等條目,需要取兩者的公共部分。詳見代碼。
3.2 代碼分析
代碼使用的是jupyter notebook風格,各變量的形狀我都標註在後面了。attention和Dice部分對照論文一看就懂。大概講一下整個架構,也就是Model函數,分爲四部分:首先是candidate ad的embedding:
# get item embedding vectors for input
i_c = tf.gather(cate_list, self.item) # obtain category of every item
item_emb = tf.concat(values=[
tf.nn.embedding_lookup(item_emb_w, self.item), # [B, H/2]
tf.nn.embedding_lookup(cate_emb_w, i_c) # [B, H/2]
], axis=1) # [B, H]
然後是user behaviors的embedding:
# get hist embedding vectors for input
h_c = tf.gather(cate_list, self.hist)
hist_emb = tf.concat(values=[
tf.nn.embedding_lookup(item_emb_w, self.hist), # [B, T, H/2]
tf.nn.embedding_lookup(cate_emb_w, h_c) # [B, T, H/2]
], axis=2) # [B, T, H]
上述實現我覺得有些問題。self.hist中除用戶歷史商品外其他都是0,也就是說幾乎所有的用戶都擁有第0個商品的embedding,由於在之前的DataInput函數預處理時恰好將包含0商品的記錄全刪掉了,此時0商品的embedding相當於對所有用戶都添加了一個噪聲,再加上attention機制,影響應該不大,但仍然有些問題。
由behaviors中各goods的embedding獲得user embedding:
# get user embedding vectors based on user hist vectors and item(to predict) vectors
att_hist_emb = attention(item_emb, hist_emb, self.sl) # [B, 1, H]
att_hist_emb = tf.layers.batch_normalization(inputs=att_hist_emb) # [B, 1, H]
att_hist_emb = tf.reshape(att_hist_emb, [-1, hidden_units]) # [B, H]
att_hist_emb = tf.layers.dense(att_hist_emb, hidden_units) # [B, H]
user_emb = att_hist_emb
然後是後面的MLP預測部分:
base_i = tf.concat([user_emb, item_emb], axis=-1) # [B, 2*H]
base_i = tf.layers.batch_normalization(base_i, name='base_i', reuse=tf.AUTO_REUSE) # [B, 2*H]
d_layer_1_i = tf.layers.dense(base_i, 80, activation=tf.nn.sigmoid, name='f1', reuse=tf.AUTO_REUSE) # [B, 80]
d_layer_1_i = dice(d_layer_1_i, name='dice1')
d_layer_2_i = tf.layers.dense(d_layer_1_i, 40, activation=tf.nn.sigmoid, name='f2', reuse=tf.AUTO_REUSE) # [B, 40]
d_layer_2_i = dice(d_layer_2_i, name='dice2')
d_layer_3_i = tf.layers.dense(d_layer_2_i, 1, activation=None, name='f3', reuse=tf.AUTO_REUSE) # [B, 1]
# 特徵平鋪
d_layer_3_i = tf.reshape(d_layer_3_i, [-1]) # [B,]
i_b = tf.gather(item_emb_b, self.item) # obtain bias of every item, [B,]
self.pre = i_b+d_layer_3_i # [B]
論文開源代碼:https://github.com/zhougr1993/DeepInterestNetwork
參考代碼:https://github.com/Crawler-y/Deep-Interest-Network
完整代碼和數據:https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20And%20Deep%20Learning/Attention/DIN
參考
[1] https://juejin.im/post/5b5591156fb9a04fe91a7a52
[2] https://www.jianshu.com/p/a356a135a0d2