論文地址: https://arxiv.org/abs/1706.06978
DIN是近年來較為有代表性的一篇廣告推薦CTR預(yù)估的文章辞州,其中使用的attention機(jī)制也為使用序列特征提供了新的思路讯嫂。本文分析了DIN的核心思想。鑒于DIN源代碼的變量命名過于隨意,這里也提供了部分源代碼的注釋诚些,僅供參考飞傀。
論文分析
核心思想:用戶的興趣是多元化的(diversity),并且對(duì)于特定的廣告诬烹,用戶不同的興趣會(huì)產(chǎn)生不同的影響(local activation)砸烦。
-
舉個(gè)DIN論文上的例子:
一位年輕的寶媽,在淘寶上點(diǎn)擊了一個(gè)新款包包的廣告(說明她對(duì)這一廣告很感興趣)绞吁。我們需要研究是什么因素造成了她會(huì)點(diǎn)擊幢痘,以便今后給她投放類似的廣告。一般來說家破,用戶行為特征(user bahavior features)(如用戶近期瀏覽/點(diǎn)擊/購買過的商品等)具有決定性的因素颜说。用戶行為特征一般是指某個(gè)用戶對(duì)于多個(gè)商品的行為序列购岗。不同的用戶,其用戶行為序列的長度和內(nèi)容都有很大差異(這就是diversity)门粪。對(duì)于這位寶媽喊积,假設(shè)其用戶序列中包含了她最近瀏覽的新款托特包,皮革手袋玄妈,以及鍋碗瓢盆等乾吻。很明顯,和鍋碗瓢盆相比拟蜻,前兩個(gè)商品對(duì)于她點(diǎn)擊這一新款包包廣告這一行為更具有決定性因素(這就是local activation)绎签。那么對(duì)于其他的廣告,如廚房清潔劑廣告酝锅,則其用戶行為序列中瀏覽的鍋碗瓢盆部分會(huì)成為預(yù)測該寶媽是否會(huì)點(diǎn)擊該廣告(即CTR預(yù)測)的決定性因素诡必。換句話說,預(yù)測某用戶對(duì)于某廣告的CTR時(shí)屈张,不能對(duì)用戶行為序列中所有商品都一視同仁擒权,而是要考慮目標(biāo)廣告的具體內(nèi)容與用戶行為序列的結(jié)合。
-
現(xiàn)存的Embedding+MLP模型的問題
現(xiàn)有的CTR預(yù)測模型阁谆,如FNN碳抄,Wide&Deep,Deep Crossing场绿,PNN等剖效,其主要結(jié)構(gòu)都是使用FM等方式實(shí)現(xiàn)Embedding,將大規(guī)模稀疏的Web數(shù)據(jù)轉(zhuǎn)化為稠密的vector焰盗。由于輸入特征中的用戶行為序列一般為multi-hot編碼璧尸,因此不同用戶數(shù)據(jù)embedding后的vector長度數(shù)不同的。一般會(huì)將這些vector通過一個(gè)pooling層(sum pooling 或mean pooling)得到長度固定的vector熬拒,并輸入到后續(xù)的MLP中訓(xùn)練爷光。這一過程的問題是,所有用戶的特征都用一個(gè)定長的vector來表示澎粟,并沒有考慮到用戶序列特征和目標(biāo)廣告之間的關(guān)系蛀序。DIN論文中使用的base model就是這樣一種模型,如圖1所示:
-
那么如何將兩者結(jié)合起來呢活烙?論文設(shè)計(jì)的DIN模型徐裸,可以自適應(yīng)地在計(jì)算用戶興趣向量時(shí)考慮到用戶歷史行為與候選廣告之間的關(guān)系。(原文:Instead pf expressing all user's diverse interests with the ssame vector, DIN adaptively calculates the representation vector of user interests by taking into consideration the relevance of historical behaviors w.r.t. candidate ad. ) 這種用戶興趣向量啸盏,對(duì)于不同的候選廣告(candidate ad)來說是不同的重贺。
至于如何在網(wǎng)絡(luò)結(jié)構(gòu)中實(shí)現(xiàn),DIN引入了一種local activation unit,來計(jì)算用戶行為特征和候選廣告之間的關(guān)系气笙,如圖2所示:
圖2. DIN model
對(duì)于候選廣告次企, 根據(jù)local activation unit計(jì)算出的用戶興趣向量為:
其中為代表用戶
的行為序列的embedding向量,長度為H健民,
為廣告
的embedding 向量抒巢。在這種計(jì)算方式下,最終的用戶
的興趣向量會(huì)根據(jù)不同的廣告
而變化秉犹,這里
表示一個(gè)feed-forward network蛉谜,其輸出作為local activation的權(quán)值,與用戶向量相乘崇堵,如圖2中的標(biāo)注所示型诚。
Local activation借鑒了NMT(Neural Machine Translation)中的attention機(jī)制。不同的是鸳劳,傳統(tǒng)attention層會(huì)做歸一化處理狰贯,因此式(1)中的權(quán)值需要滿足。但是在DIN中則沒有此限制赏廓,
的值被視為對(duì)用戶興趣強(qiáng)度值的近似涵紊,這樣對(duì)于不同的廣告A,的取值范圍不同幔摸,更能夠體現(xiàn)出local activation的意義摸柄。
部分源碼解讀
開源代碼地址(重點(diǎn)看/din/model.py):https://github.com/zhougr1993/DeepInterestNetwork/blob/master/din/model.py
DIN的開放源代碼中的變量命名非常的難以理解(i, j, h, y等等不知道什么意思)。這里經(jīng)過我的實(shí)驗(yàn)和推(xia)理(cai)既忆,總結(jié)了部分代碼的注釋驱负。個(gè)人理解,僅供參考患雇。
- 首先是
/din/model.py
中的開始部分
import tensorflow as tf
from Dice import dice
class Model(object):
def __init__(self, user_count, item_count, cate_count, cate_list,\
predict_batch_size, predict_ads_num):
self.u = tf.placeholder(tf.int32, [None,])
# shape: [B], user id跃脊。 (B:batch size)
self.i = tf.placeholder(tf.int32, [None,])
# shape: [B] i: 正樣本的item
self.j = tf.placeholder(tf.int32, [None,])
# shape: [B] j: 負(fù)樣本的item
self.y = tf.placeholder(tf.float32, [None,])
# shape: [B], y: label
self.hist_i = tf.placeholder(tf.int32, [None, None])
# shape: [B, T] #用戶行為特征(User Behavior)中的item序列。T為序列長度
self.sl = tf.placeholder(tf.int32, [None,])
# shape: [B]; sl:sequence length苛吱,User Behavior中序列的真實(shí)序列長度(酪术?)
self.lr = tf.placeholder(tf.float64, [])
# learning rate
hidden_units = 128
user_emb_w = tf.get_variable("user_emb_w", [user_count, hidden_units])
# shape: [U, H], user_id的embedding weight. U是user_id的hash bucket size
item_emb_w = tf.get_variable("item_emb_w", [item_count, hidden_units // 2]) #[I, H//2]
# shape: [I, H//2], item_id的embedding weight. I是item_id的hash bucket size
item_b = tf.get_variable("item_b", [item_count],
initializer=tf.constant_initializer(0.0))
# shape: [I], bias
cate_emb_w = tf.get_variable("cate_emb_w", [cate_count, hidden_units // 2])
# shape: [C, H//2], cate_id的embedding weight.
cate_list = tf.convert_to_tensor(cate_list, dtype=tf.int64)
# shape: [C, H//2]
ic = tf.gather(cate_list, self.i)
# 從cate_list中取出正樣本的cate
i_emb = tf.concat(values = [
tf.nn.embedding_lookup(item_emb_w, self.i),
tf.nn.embedding_lookup(cate_emb_w, ic),
], axis=1)
# 正樣本的embedding,正樣本包括item和cate
i_b = tf.gather(item_b, self.i)
jc = tf.gather(cate_list, self.j)
# 從cate_list中取出負(fù)樣本的cate
j_emb = tf.concat([
tf.nn.embedding_lookup(item_emb_w, self.j),
tf.nn.embedding_lookup(cate_emb_w, jc),
], axis=1)
# 負(fù)樣本的embedding翠储,負(fù)樣本包括item和cate
j_b = tf.gather(item_b, self.j) #偏置b
hc = tf.gather(cate_list, self.hist_i)
# 用戶行為序列(User Behavior)中的cate序列
h_emb = tf.concat([tf.nn.embedding_lookup(item_emb_w, self.hist_i),
tf.nn.embedding_lookup(cate_emb_w, hc),
], axis=2)
#用戶行為序列(User Behavior)的embedding绘雁,包括item序列和cate序列
hist_i = attention(i_emb, h_emb, self.sl) #attention操作
#-- attention end ---
- 接著跳到
/din/model.py
中的Line199看attention部分
def attention(queries, keys, keys_length):
'''
queries: shape: [B, H], 即i_emb
keys: shape: [B, T, H], 即h_emb
keys_length: shape: [B], 即self.sl
B:batch size; T: 用戶序列的長度;H:embedding size
'''
queries_hidden_units = queries.get_shape().as_list()[-1]
# shape: [H]
queries = tf.tile(queries, [1, tf.shape(keys)[1]])
# [B,H] -> T*[B,H]
queries = tf.reshape(queries, [-1, tf.shape(keys)[1], queries_hidden_units])
# T*[B,H] ->[B, T, H]
din_all = tf.concat([queries, keys, queries-keys, queries*keys], axis=-1)
# attention操作彰亥,輸出維度為[B, T, 4*H]
d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, \
name='f1_att', reuse=tf.AUTO_REUSE) # [B, T, 80]
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, 40]
d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, \
name='f3_att', reuse=tf.AUTO_REUSE) # [B, T, 1]
d_layer_3_all = tf.reshape(d_layer_3_all, [-1, 1, tf.shape(keys)[1]]) #[B, 1, T]
outputs = d_layer_3_all # attention的輸出, [B, 1, T]
# Mask
key_masks = tf.sequence_mask(keys_length, tf.shape(keys)[1]) # [B, T]
key_masks = tf.expand_dims(key_masks, 1) # [B, 1, T]
paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
# padding的mask后補(bǔ)一個(gè)很小的負(fù)數(shù),這樣softmax之后就會(huì)接近0.
outputs = tf.where(key_masks, outputs, paddings)
# [B, 1, T] padding操作衰齐,將每個(gè)樣本序列中空缺的商品都賦值為(-2 ** 32 + 1)
# Scale
outputs = outputs / (keys.get_shape().as_list()[-1] ** 0.5)
# Activation
outputs = tf.nn.softmax(outputs)
# [B, 1, T] #這里的output是attention計(jì)算出來的權(quán)重任斋,即論文公式(3)里的w,
# Weighted sum
outputs = tf.matmul(outputs, keys)
# [B, 1, H]
return outputs
如果有疑問或指正,歡迎在評(píng)論區(qū)提出7峡帷瘟檩!