1.用户兴趣建模
随着深度学习的普及,Embedding&MLP的范式成为了排序模型经典范式,成为各个业务模型Base model的首选。
在排序模型中通常的特征类型有user profile 、item feature、context feature,user behavior feature, 其中第一类特征是静态的,很粗粒度的刻画了不同用户,第二、三类特征对于不同用户是相同的,只有第四类特征即用户兴趣,是实时更新的且”千人千面“,所以通常说的个性化也是通过这个特征来进行表达的,即用户兴趣是用户个性化的刻画。
用户历史行为序列是重要的 user 侧特征,但由于其因人而异,而模型结构则较难处理变长序列,故常用做法是把用户行为序列对应的 embedding list 通过pooling 操作压缩成固定维向量,作为用户历史兴趣表征,机智一点的做法可以通过给序列中不同的行为进行加权后求pooling(例如根据time decay)。但不管如何,这种pooling求和的方式显得过于粗糙,会造成有效信息的损失,这也是后续一系列用户行为建模方法的改进动机:如何更有效的学习到用户兴趣的embedding表达。
2.(KDD’ 18)DIN:对用户序列做一个attention加权
前面提到过pooling求和的一种改进方式是加权求和,比如根据行为发生距离现在的时间decay,越近越获得更大的权重,这是一种直觉上的想法,但是现实的序列往往是这样的(摘自王喆的知乎)
这大概率是一个女生的行为序列,现在要打分的target item是那件粉红色的大衣,女生喜欢买衣服包包,也喜欢化妆品,甚至还为自己男朋友挑选过球衣球鞋,那么你在买大衣的时候,真的要把给男朋友买球鞋的偏好考虑进来么?具体到本文的例子中,在预测大衣的CTR这件事情上,用户浏览过杯子,跟用户浏览过另一件大衣这两个行为的重要程度是一样的吗?显然,我们更应该关注的是和target item相关的行为,这就要求我们需要一个对应用户行为序列大小的权重序列,以表示原行为序列中对要打分的target item的“”关联程度“,如何获得这个权重序列?简单,用attention就行了。这就是DIN的核心思想,简单而又巧妙。
具体模型长这样,其实相比于经典的Embedding&MLP范式,唯一的区别在于对用户行为序列进行了一个重加权,加权是通过一个子attention模块(上图右边的小方框)来获取权重的,之后再做sum pooling,和其他特征concat到一起送到后面的FC中。
以上就是DIN这篇paper中最核心的部分,当然论文还介绍了一些其他的算法方面的内容,只不过和用户兴趣建模无关,这里不再赘述,感兴趣可以参考原论文:
- 用GAUC这个离线metric替代AUC
- 用Dice方法替代经典的PReLU激活函数
- 介绍一种Adaptive的正则化方法
3.query attention框架以及一些小trick
其实看过DIN源码的应该都知道,DIN中的target attention其实是几种attention的结合(加法attention,乘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)
outputs = tf.where(key_masks, outputs, paddings)
# Scale
outputs = outputs / (keys.get_shape().as_list()[-1] ** 0.5)
# Activation
outputs = tf.nn.softmax(outputs)
# Weighted sum
outputs = tf.matmul(outputs, keys)
return outputs
作为一个通用的query attention框架,在实际用的过程中,其实也可以将query由target item换成其他需要关注的东西,比如前面说的decay,一般来说效果是要好于直接拿decay做加权的。
另外原论文中提到的dice激活函数替代prelu一般在比较深的层也能取得较大的提升,可以多多尝试。
参考:
1.https://zhuanlan.zhihu.com/p/151403447
2.https://zhuanlan.zhihu.com/p/51623339