总述:博主前些天对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
阿里巴巴线上使用的深度学习兴趣网络 DIN (三) - 论文翻译_learner_ctr的博客-CSDN博客
一、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 的实现办法描述了出来
上面函数中,有几个tf的api,例如 tf.tile的实际作用,在下面做下展示
import tensorflow as tf
tf.reset_default_graph()
queries=tf.constant(
[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
)
queries1 = tf.tile(queries, [1, 5])
queries2 = tf.reshape(queries1, [-1, 5, 3])
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
print("queries")
print(sess.run(queries))
print("queries1")
print(sess.run(queries1))
print("queries2")
print(sess.run(queries2))
queries
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
queries1
[[ 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3]
[ 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6]
[ 7 8 9 7 8 9 7 8 9 7 8 9 7 8 9]
[10 11 12 10 11 12 10 11 12 10 11 12 10 11 12]]
queries2
[[[ 1 2 3]
[ 1 2 3]
[ 1 2 3]
[ 1 2 3]
[ 1 2 3]]
[[ 4 5 6]
[ 4 5 6]
[ 4 5 6]
[ 4 5 6]
[ 4 5 6]]
[[ 7 8 9]
[ 7 8 9]
[ 7 8 9]
[ 7 8 9]
[ 7 8 9]]
[[10 11 12]
[10 11 12]
[10 11 12]
[10 11 12]
[10 11 12]]]
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