在本系列的第十八篇( https://www.jianshu.com/p/73b6f5d00f46 )中,我们介绍了阿里的深度兴趣网络(Deep Interest Network,以下简称DIN),时隔一年,阿里再次升级其模型,提出了深度兴趣进化网络(Deep Interest Evolution Network,以下简称DIEN,论文地址: https://arxiv.org/pdf/1809.03672.pdf ),并将其应用于淘宝的广告系统中,获得了20.7%的CTR的提升。本篇,我们一同来探秘DIEN的原理及实现。
1、背景
在大多数非搜索电商场景下,用户并不会实时表达目前的兴趣偏好。因此通过设计模型来捕获用户的动态变化的兴趣,是提升CTR预估效果的关键。阿里之前的DIN模型将用户的历史行为来表示用户的兴趣,并强调了用户兴趣的多样性和动态变化性,因此通过attention-based model来捕获和目标物品相关的兴趣。虽然DIN模型将用户的历史行为来表示兴趣,但存在两个缺点:
1)用户的兴趣是不断进化的,而DIN抽取的用户兴趣之间是独立无关联的,没有捕获到兴趣的动态进化性
2)通过用户的显式的行为来表达用户隐含的兴趣,这一准确性无法得到保证。
基于以上两点,阿里提出了深度兴趣演化网络DIEN来CTR预估的性能。DIEN模型的主要贡献点在于:
1)模型关注电商系统中兴趣演化的过程,并提出了新的网络结果来建模兴趣进化的过程,这个模型能够更精确的表达用户兴趣,同时带来更高的CTR预估准确率。
2)设计了兴趣抽取层,并通过计算一个辅助loss,来提升兴趣表达的准确性。
3)设计了兴趣进化层,来更加准确的表达用户兴趣的动态变化性。
接下来,我们来一起看一下DIEN模型的原理。
2、DIEN模型原理
2.1 模型总体结构
我们先来对比一下DIN和DIEN的结构。
DIN的模型结构如下:
DIN
DIEN的模型结构如下:
DIEN
可以看到,DIN和DIEN的最底层都是Embedding Layer,User profile, target AD和context feature的处理方式是一致的。不同的是,DIEN将user behavior组织成了序列数据的形式,并把简单的使用外积完成的activation unit变成了一个attention-based GRU网络。
2.2 兴趣抽取层Interest Extractor Layer
兴趣抽取层Interest Extractor Layer的主要目标是从embedding数据中提取出interest。但一个用户在某一时间的interest不仅与当前的behavior有关,也与之前的behavior相关,所以作者们使用GRU单元来提取interest。GRU单元的表达式如下:
GRU表达式
这里我们可以认为ht是提取出的用户兴趣,但是这个地方兴趣是否表示的合理呢?文中别出心裁的增加了一个辅助loss,来提升兴趣表达的准确性:
这里,作者设计了一个二分类模型来计算兴趣抽取的准确性,我们将用户下一时刻真实的行为e(t+1)作为正例,负采样得到的行为作为负例e(t+1)',分别与抽取出的兴趣h(t)结合输入到设计的辅助网络中,得到预测结果,并通过logloss计算一个辅助的损失:
2.3 兴趣进化层Interest Evolution Layer
兴趣进化层Interest Evolution Layer的主要目标是刻画用户兴趣的进化过程。举个简单的例子:
以用户对衣服的interest为例,随着季节和时尚风潮的不断变化,用户的interest也会不断变化。这种变化会直接影响用户的点击决策。建模用户兴趣的进化过程有两方面的好处:
1)追踪用户的interest可以使我们学习final interest的表达时包含更多的历史信息。
2)可以根据interest的变化趋势更好地进行CTR预测。
而interest在变化过程中遵循如下规律:
1)interest drift:用户在某一段时间的interest会有一定的集中性。比如用户可能在一段时间内不断买书,在另一段时间内不断买衣服。
2)interest individual:一种interest有自己的发展趋势,不同种类的interest之间很少相互影响,例如买书和买衣服的interest基本互不相关。
为了利用这两个时序特征,我们需要再增加一层GRU的变种,并加上attention机制以找到与target AD相关的interest。
attention的计算方式如下:
而Attention和GRU结合起来的机制有很多,文中介绍了一下三种:
GRU with attentional input (AIGRU)
这种方式将attention直接作用于输入,无需修改GRU的结构:
Attention based GRU(AGRU)
这种方式需要修改GRU的结构,此时hidden state的输出变为:
GRU with attentional update gate (AUGRU)
这种方式需要修改GRU的结构,此时hidden state的输出变为:
2.4 模型试验
文章在公共数据和自己的数据集上都做了实验,并选取了不同的对比模型:
离线实验的结果如下:
DIEN使用了辅助loss和AUGRU结构,而BaseModel + GRU + AUGRU与DIEN的不同之处就是没有增加辅助loss。可以看到,DIEN的实验效果远好于其他模型。
3、DIEN模型实现
本文模型的实现参考代码是: https://github.com/mouna99/dien
本文代码的地址为: https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-DIEN-Demo
本文数据的地址为: https://github.com/mouna99/dien
3.1 数据介绍
根据github中提供的数据,解压后的文件如下:
uid_voc.pkl: 用户名对应的id
mid_voc.pkl: item对应的id
cat_voc.pkl:category对应的id
item-info:item对应的category信息
reviews-info:用于进行负采样的数据
local_train_splitByUser:训练数据,一行格式为:label、用户名、目标item、 目标item类别、历史item、历史item对应类别。
local_test_splitByUser:测试数据,格式同训练数据
3.2 代码实现
本文的代码主要包含以下几个文件:
rnn.py:对tensorflow中原始的rnn进行修改,目的是将attention同rnn进行结合。
vecAttGruCell.py: 对GRU源码进行修改,将attention加入其中,设计AUGRU结构
data_iterator.py:数据迭代器,用于数据的不断输入
utils.py:一些辅助函数,如dice激活函数、attention score计算等
model.py:DIEN模型文件
train.py:模型的入口,用于训练数据、保存模型和测试数据
好了,接下来我们介绍一些关键的代码:
输入数据介绍
输入的数据有用户id、target的item id、target item对应的cateid、用户历史行为的item id list、用户历史行为item对应的cate id list、历史行为的长度、历史行为的mask、目标值、负采样的数据。
对于每一个用户的历史行为,代码中选取了5个样本作为负样本。
self.mid_his_batch_ph = tf.placeholder(tf.int32,[None,None],name='mid_his_batch_ph')
self.cat_his_batch_ph = tf.placeholder(tf.int32,[None,None],name='cat_his_batch_ph')
self.uid_batch_ph = tf.placeholder(tf.int32,[None,],name='uid_batch_ph')
self.mid_batch_ph = tf.placeholder(tf.int32,[None,],name='mid_batch_ph')
self.cat_batch_ph = tf.placeholder(tf.int32,[None,],name='cat_batch_ph')
self.mask = tf.placeholder(tf.float32,[None,None],name='mask')
self.seq_len_ph = tf.placeholder(tf.int32,[None],name='seq_len_ph')
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')
self.noclk_cat_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_cat_batch_ph')
输入数据转换为对应的embedding
接下来,输入数据将转换为对应的embedding:
with tf.name_scope("Embedding_layer"):
self.uid_embeddings_var = tf.get_variable("uid_embedding_var",[n_uid,EMBEDDING_DIM])
tf.summary.histogram('uid_embeddings_var', self.uid_embeddings_var)
self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var,self.uid_batch_ph)
self.mid_embeddings_var = tf.get_variable("mid_embedding_var",[n_mid,EMBEDDING_DIM])
tf.summary.histogram('mid_embeddings_var',self.mid_embeddings_var)
self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,self.mid_batch_ph)
self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,self.mid_his_batch_ph)
if self.use_negsampling:
self.noclk_mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,
self.noclk_mid_batch_ph)
self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM])
tf.summary.histogram('cat_embeddings_var', self.cat_embeddings_var)
self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph)
self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph)
if self.use_negsampling:
self.noclk_cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var,
self.noclk_cat_batch_ph)
接下来,将item的id对应的embedding 以及 item对应的cateid的embedding进行拼接,共同作为item的embedding.:
self.item_eb = tf.concat([self.mid_batch_embedded,self.cat_batch_embedded],1)
self.item_his_eb = tf.concat([self.mid_his_batch_embedded,self.cat_his_batch_embedded],2)
if self.use_negsampling:
self.noclk_item_his_eb = tf.concat(
[self.noclk_mid_his_batch_embedded[:, :, 0, :], self.noclk_cat_his_batch_embedded[:, :, 0, :]], -1)
self.noclk_item_his_eb = tf.reshape(self.noclk_item_his_eb,
[-1, tf.shape(self.noclk_mid_his_batch_embedded)[1], EMBEDDING_DIM * 2]) # 负采样的item选第一个
self.noclk_his_eb = tf.concat([self.noclk_mid_his_batch_embedded, self.noclk_cat_his_batch_embedded], -1)
第一层GRU
接下来,我们要将用户行为历史的item embedding输入到dynamic rnn中,同时计算辅助loss:
with tf.name_scope('rnn_1'):
rnn_outputs,_ = dynamic_rnn(GRUCell(HIDDEN_SIZE),inputs = self.item_his_eb,sequence_length=self.seq_len_ph,dtype=tf.float32,scope='gru1')
tf.summary.histogram("GRU_outputs",rnn_outputs)
aux_loss_1 = self.auxiliary_loss(rnn_outputs[:,:-1,:],self.item_his_eb[:,1:,:],self.noclk_item_his_eb[:,1:,:],self.mask[:,1:],stag="gru")
self.aux_loss = aux_loss_1
辅助loss的计算其实是一个二分类模型,代码如下:
def auxiliary_loss(self,h_states,click_seq,noclick_seq,mask,stag=None):
mask = tf.cast(mask,tf.float32)
click_input = tf.concat([h_states,click_seq],-1)
noclick_input = tf.concat([h_states,noclick_seq],-1)
click_prop_ = self.auxiliary_net(click_input,stag=stag)[:,:,0]
noclick_prop_ = self.auxiliary_net(noclick_input,stag=stag)[:,:,0]
click_loss_ = -tf.reshape(tf.log(click_prop_),[-1,tf.shape(click_seq)[1]]) * mask
noclick_loss_ = - tf.reshape(tf.log(1.0 - noclick_prop_), [-1, tf.shape(noclick_seq)[1]]) * mask
loss_ = tf.reduce_mean(click_loss_ + noclick_loss_)
return loss_
def auxiliary_net(self,input,stag='auxiliary_net'):
bn1 = tf.layers.batch_normalization(inputs=input, name='bn1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.layers.dense(bn1, 100, activation=None, name='f1' + stag, reuse=tf.AUTO_REUSE)
dnn1 = tf.nn.sigmoid(dnn1)
dnn2 = tf.layers.dense(dnn1, 50, activation=None, name='f2' + stag, reuse=tf.AUTO_REUSE)
dnn2 = tf.nn.sigmoid(dnn2)
dnn3 = tf.layers.dense(dnn2, 2, activation=None, name='f3' + stag, reuse=tf.AUTO_REUSE)
y_hat = tf.nn.softmax(dnn3) + 0.00000001
return y_hat
AUGRU
我们首先需要计算attention的score,然后将其作为GRU的一部分输入:
with tf.name_scope('Attention_layer_1'):
att_outputs,alphas = din_fcn_attention(self.item_eb,rnn_outputs,ATTENTION_SIZE,self.mask,
softmax_stag=1,stag='1_1',mode='LIST',return_alphas=True)
tf.summary.histogram('alpha_outputs',alphas)
接下来,就是AUGRU的结构,这里我们需要设计一个新的VecAttGRUCell结构,相比于GRUCell,修改的地方如下:
上图中左侧是GRU的源码,右侧是VecAttGRUCell的代码,我们主要修改了call函数中的代码,在GRU中,hidden state的计算为:
new_h = u * state + (1 - u) * c
AUGRU中,hidden state的计算为:
u = (1.0 - att_score) * u new_h = u * state + (1 - u) * c
代码中给出的hidden state计算可能与文中有些出入,不过核心的思想都是,对于attention score大的,保存的当前的c就多一些。
设计好了新的GRU Cell,我们就能计算兴趣的进化过程:
with tf.name_scope('rnn_2'):
rnn_outputs2,final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE),inputs=rnn_outputs,
att_scores=tf.expand_dims(alphas,-1),
sequence_length = self.seq_len_ph,dtype=tf.float32,
scope="gru2"
)
tf.summary.histogram("GRU2_Final_State",final_state2)
得到兴趣进化的结果final_state2之后,需要与其他的embedding进行拼接,得到全联接层的输入:
inp = tf.concat([self.uid_batch_embedded,self.item_eb,self.item_his_eb_sum,self.item_eb * self.item_his_eb_sum,final_state2],1)
全联接层得到最终输出
最后我们通过一个多层神经网络,得到最终的ctr预估值:
def build_fcn_net(self,inp,use_dice=False):
bn1 = tf.layers.batch_normalization(inputs=inp,name='bn1')
dnn1 = tf.layers.dense(bn1,200,activation=None,name='f1')
if use_dice:
dnn1 = dice(dnn1,name='dice_1')
else:
dnn1 = prelu(dnn1,'prelu1')
dnn2 = tf.layers.dense(dnn1,80,activation=None,name='f2')
if use_dice:
dnn2 = dice(dnn2,name='dice_2')
else:
dnn2 = prelu(dnn2,name='prelu2')
dnn3 = tf.layers.dense(dnn2,2,activation=None,name='f3')
self.y_hat = tf.nn.softmax(dnn3) + 0.00000001
with tf.name_scope('Metrics'):
ctr_loss = -tf.reduce_mean(tf.log(self.y_hat) * self.target_ph)
self.loss = ctr_loss
if self.use_negsampling:
self.loss += self.aux_loss
tf.summary.scalar('loss',self.loss)
self.optimizer = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.loss)
self.accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.round(self.y_hat),self.target_ph),tf.float32))
tf.summary.scalar('accuracy',self.accuracy)
self.merged = tf.summary.merge_all()
这样,一个DIEN的模型就设计好了,其中的细节还是很多的,希望大家都能动手实现一下!
参考文献
1、 https://blog.csdn.net/friyal/article/details/83115900
本文由石晓文 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。
以上所述就是小编给大家介绍的《推荐系统遇上深度学习(二十四)--深度兴趣进化网络DIEN原理及实战!》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ## 进化的倒计时
- JavaScript异步进化史
- 革命与进化:全同态加密
- BackSwap银行木马进化分析
- Dridex的进化史
- ThreadLocal 的进化:TransmittableThreadLocal
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning XML with DOM and Ajax
Sas Jacobs / Apress / 2006-06-05 / USD 39.99
Don't waste time on 1,000-page tomes full of syntax; this book is all you need to get ahead in XML development. Renowned web developer Sas Jacobs presents an essential guide to XML. Beginning XML with......一起来看看 《Beginning XML with DOM and Ajax》 这本书的介绍吧!