本专栏文章会在本博客和知乎专栏——Sunny.Xia的深度学习同步更新,对于评论博主若未能够及时回复的,可以知乎私信。未经本人允许,请勿转载,谢谢。
一、什么是DIN?
DIN全称为deep interest network,在网络中引入了注意力机制,本质上可以理解为weighted-sum pooling。通过权重的方式,将用户兴趣的多样性以及当前候选商品仅与用户一部分兴趣有关这一特点考虑了进来。建议大家去看一下论文原文,此外博主还参考了下面三篇博客。文章内容侧重点各不相同,大家可以综合看看以便加深理解。网上的论文有两种版本,一种是arXiv上的版本,另一种是正式发布在KDD 2018的版本,一定要去看后一篇,比较详细完整。
论文请戳链接 https://github.com/ruozhichen/deep_learning_papers。
阿里巴巴DIN模型详解
推荐系统中的注意力机制——阿里深度兴趣网络(DIN)
DIN 深度兴趣网络介绍以及源码浅析
1、Base模型
原本的深度模型,就简单的对用户历史行为习惯的embedding向量做了一个sum pooling,这样就不可避免地会遗漏掉重要信息。就好比我之前购买了牙刷、短袖、手机、裤子,当我浏览衣服类的商品时,起重要作用的肯定是短袖、裤子这类的消费记录,但base model就把这些行为给等同化了。
2、DIN
DIN则在网络中加入了attention机制,利用用户行为的Embedding向量和广告的Embedding向量,计算每个历史商品的权重值,根据权重采取weighted-sum pooling。每个商品对最终结果的贡献程度也不一样,这样就把用户历史行为习惯和目标商品关联起来。
二、源码解析
关于DIN其他细节就不再展开了,论文以及上述博客链接将的很详细,我也不再赘述。
在论文中作者给出了demo地址:
https://github.com/zhougr1993/DeepInterestNetwork
不过Readme中,作者建议看另一个代码实现:
https://github.com/mouna99/dien
本文会参考后者中的DIN部分进行着重讲解。
1、ATTENTION
def din_attention(query, facts, attention_size, mask, stag='null', mode='SUM', softmax_stag=1, time_major=False, return_alphas=False):""":param query: (B,H):param facts: (B,T,H):param attention_size::param mask: (B,T):param stag::param mode::return:"""queries = tf.tile(query, [1, tf.shape(facts)[1]]) # (B,H)->(B,T*H)queries = tf.reshape(queries, tf.shape(facts)) # (B,T,H)din_all = tf.concat([queries, facts, queries-facts, queries*facts], axis=-1) # (B,T,4H)d_layer_1_all = tf.layers.dense(din_all, 80, activation=tf.nn.sigmoid, name='f1_att' + stag) # (B,T,80)d_layer_2_all = tf.layers.dense(d_layer_1_all, 40, activation=tf.nn.sigmoid, name='f2_att' + stag) # (B,T,40)d_layer_3_all = tf.layers.dense(d_layer_2_all, 1, activation=None, name='f3_att' + stag) # (B,T,1)d_layer_3_all = tf.reshape(d_layer_3_all, [-1, 1, tf.shape(facts)[1]]) # (B,1,T)scores = d_layer_3_all# Mask# key_masks = tf.sequence_mask(facts_length, tf.shape(facts)[1]) # [B, T]mask = tf.equal(mask, tf.ones_like(mask))key_masks = tf.expand_dims(mask, 1) # [B, 1, T]paddings = tf.ones_like(scores) * (-2 ** 32 + 1)scores = tf.where(key_masks, scores, paddings) # [B, 1, T]# Scale# scores = scores / (facts.get_shape().as_list()[-1] ** 0.5)# Activationif softmax_stag:scores = tf.nn.softmax(scores) # [B, 1, T]# Weighted sumif mode == 'SUM':output = tf.matmul(scores, facts) # [B, 1, H]# output = tf.reshape(output, [-1, tf.shape(facts)[-1]])else:scores = tf.reshape(scores, [-1, tf.shape(facts)[1]]) # (B,T)output = facts * tf.expand_dims(scores, -1) # (B,T,H) * (B,T,1)output = tf.reshape(output, tf.shape(facts)) # (B,T,H)return output
代码里我对各变量的大小进行了注释,B、T、H含义如下:
B:batch_size
T:用户历史行为序列的最大长度
H:embedding的维度
query:即框架图中的候选广告
facts:用户历史行为序列,即用户过去点击过的广告。
attention_size:代码里没用到,可以忽略
mask:大小为B*T,由于每个用户的历史行为序列长度不是固定的,所以用mask来进行标记。对于每一行,第1~j(j<=T)个元素为True,第j+1~T个元素为False。
由于query大小只有B*H,所以先将其扩展为B*T*H大小的queries后,与facts、queries-facts、queries*facts拼接后din_all作为三层深度网络的输入,最后输出B*T*1大小的d_layer_3_all,再通过reshape变为B*1*T的scores。
到目前为止,我们还不能将socres直接送到softmax获取每个历史行为对应的权重,因为每个用户的历史序列长度不是固定的,这就需要对那些多出来的项进行填充。paddings的作用就是对那些填充的行为,赋予一个很大的负值(-2^31+1),输入到softmax后,这类填充的负值输出的结果就几乎为0,从而不会对原本的历史行为权重产生影响。
scores = tf.nn.softmax(scores)获得的即每个历史行为对应的权重。
若mode==‘SUM’,则scores与facts矩阵相乘后得到的output,即为weighted-sum后的embedding向量。
或者不进行加权,只是将每个历史行为的embedding乘以对应的权重,返回的依旧是(B,T,H)大小的值。
2、DIN
class Model_DIN(Model):def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling=False):super(Model_DIN, self).__init__(n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE,ATTENTION_SIZE,use_negsampling)# Attention layerwith tf.name_scope('Attention_layer'):attention_output = din_attention(self.item_eb, self.item_his_eb, ATTENTION_SIZE, self.mask)att_fea = tf.reduce_sum(attention_output, 1)tf.summary.histogram('att_fea', att_fea)inp = tf.concat([self.uid_batch_embedded, self.item_eb, self.item_his_eb_sum, self.item_eb * self.item_his_eb_sum, att_fea], -1)# Fully connected layerself.build_fcn_net(inp, use_dice=True)
DIN部分就简单了,首先通过din_attention获取大小为(B,1,H),即weighted-sum pooling后的用户历史行为embedding。
这里att_fea = tf.reduce_sum(attention_output, 1)其实就是加权和,如果din_attention传递的mode不是‘SUM’,则经过这一步最终也是变为了(B,H)大小的embedding向量。再与其他embedding拼接后,作为全连接网络的输入。