行业报告 AI展会 数据标注 标注供求
数据标注数据集
主页 > 机器学习 > 正文

Attention!当推荐系统遇见注意力机制

当注意力机制都已经变成很tasteless的手法的时候,使用或者魔改注意力机制一定要专注讲好自己的故事:即『为什么要用Attention,为什么要魔改Attention』
 
现阶段从传统的CF,FM等方法到NFM,DeepFM等等,虽然开始用DNN来处理深度的特征交叉,还缺少的主要有两点:
 
用户历史行为的特征挖掘。
特征冗余度问题。二阶or高阶的特征基本都是枚举式的。
DIN和DIEN都是阿里针对CTR预估的模型,都主要是对用户历史行为数据的进一步挖掘的工作。CTR预估任务是,根据给定广告/物品、用户和大量的上下文情况等信息,对点击进行预测,所以对用户的兴趣理解,历史行为数据非常重要。
 
然后自从Transformer出现,BERT在NLP界屠榜,所以很自然在上的应用也开始升级。本篇博文将整理四篇关于Attention的文章,从普通的Attention一路升级到BERT。
 
DIN
论文:Deep Interest Network for Click-Through Rate Prediction
地址:https://arxiv.org/abs/1706.06978
 
DIN这篇文章的Attention故事出发点在于两点观察:
「Diversity」:多样性是指用户的兴趣是广泛的,一个用户会对多个物品,多个领域感兴趣
「Local activation」:部分对应是指只有部分历史数据与目前推荐的物品相关(如推荐零食物品就与用户以前买过什么装备无关)

 

那么怎么把这部分相关历史给动态的捕捉到呢?DIN的做法是,将用户的历史数据和当前的 item之间计算相似度,即计算Attention值,对每个用户的兴趣表示都赋予不同的权值,然后再加权求和。先看公式:

 

 
整个模型框架如上图所示,左边是base模型,主要是将特征one-hot或multi-hot(主要是对用户历史行为数据)后再embedding,值得注意的是,每个用户的历史点击个数是不相等的,但需要变成一个固定长的向量,所以对于multi-hot的特征会多做一个element-wise(即图中的+号),即不管用户的行为序列有多长,都会pooling成同一个维度。最后拼接之后用MLP预测最终的分数。但是这个base版本作者认为pooling的结果显然丢失了大量信息,很显然使用Attention能够提高用户行为特征的表达,所以右边的图就是加了Attention之后的版本。
 
加注意力的思路很简单。然后还有两个重要的Trick:
「1. Data Dependent Activation Function(Dice激活函数)」
 
原来一般的Relu激活函数在值大于0时y=x,小于0时直接输出为0,这样导致了许多节点的“死亡”,更新缓慢。因此Leaky Relu在左边小于0的部分也给一定的梯度即y=ax。但这样仍然是不够的,因为它们的默认分割点都是在0这个地方(比0大的左边或者右边),不合理,分割点应该由数据决定,所以需要Dice。

 

 
第一个式子是Leaky的改版,但是此时在Leaky Relu左边的y=ax上会多一个控制的参数p,而p是对数据进行均值归一化后(即利用数据的均值和方差进行调整)的结果,即把整个激活函数移动到了数据的均值处。
 
优点:根据数据分布灵活调整阶跃变化点,具有BN的优点
缺点:BN复杂度,比较耗时
def dice(_x, axis=-1, epsilon=0.000000001, name=''):
  #Data Adaptive Activation Function
  with tf.variable_scope(name_or_scope='', reuse=tf.AUTO_REUSE):
    alphas = tf.get_variable('alpha'+name, _x.get_shape()[-1],                                  
                         initializer=tf.constant_initializer(0.0),                         
                         dtype=tf.float32)
    beta = tf.get_variable('beta'+name, _x.get_shape()[-1],                                  
                         initializer=tf.constant_initializer(0.0),                         
                         dtype=tf.float32)
  input_shape = list(_x.get_shape())
 
  reduction_axes = list(range(len(input_shape)))
  del reduction_axes[axis]
  broadcast_shape = [1] * len(input_shape)
  broadcast_shape[axis] = input_shape[axis]
                                                                                                                                                                            
  # case: train mode (uses stats of the current batch)
  #计算batch的均值和方差
  mean = tf.reduce_mean(_x, axis=reduction_axes)
  brodcast_mean = tf.reshape(mean, broadcast_shape)
  std = tf.reduce_mean(tf.square(_x - brodcast_mean) + epsilon, axis=reduction_axes)
  std = tf.sqrt(std)
  brodcast_std = tf.reshape(std, broadcast_shape)
  x_normed = tf.layers.batch_normalization(_x, center=False, scale=False, name=name, reuse=tf.AUTO_REUSE)
  # x_normed = (_x - brodcast_mean) / (brodcast_std + epsilon)
  x_p = tf.sigmoid(beta * x_normed)
 
  return alphas * (1.0 - x_p) * _x + x_p * _x #根据原文中给的公式计算
 
def parametric_relu(_x):
  #PRELU激活函数,形式上和leakReLU很像,只是它的alpha可学习
  #alpha=0,退化成ReLU。alpha不更新,退化成Leak
  with tf.variable_scope(name_or_scope='', reuse=tf.AUTO_REUSE):
    alphas = tf.get_variable('alpha', _x.get_shape()[-1],
                         initializer=tf.constant_initializer(0.0),
                         dtype=tf.float32)
  pos = tf.nn.relu(_x)
  neg = alphas * (_x - abs(_x)) * 0.5 #用alpha控制
 
  return pos + neg
完整的源码笔记:https://github.com/nakaizura/Source-Code-Notebook/tree/master/DIN
 
「2. Adaptive Regularization(自适应正则)」
这个方法提出的动机是输入的数据长尾分布,非常稀疏维度高应该怎么防止过拟合。直接L1、L2、Dropout效果不佳,直接丢弃又损失了信息可能加重过拟合,怎么办?自适应的正则方法,按照出现的频率调整正则化的强化,即频率高的,正则化强度小,频率低的,正则化强度高。也就是说会惩罚那些出现频率低的item。
 

 

DIN的设计对工业界的帮助更大,因为上线的时候受制于内存所以User Embedding不能很大,那么显然无法很好的表示用户特征,想表示用户的多兴趣(多峰)就很难了。此时DIN基于用户历史行为再加入Attention就很好的缓解了这个问题。
缺点:用到了历史行为数据,但是忽略了序列关系。
 
DINE
论文:Deep Interest Evolution Network for Click-Through Rate Prediction
地址:https://arxiv.org/abs/1809.03672
 
升级DIN,改进DIN中存在两个缺点:
用户兴趣应该是不断进化的。DIN抽取的用户兴趣是固定的,没有捕获到兴趣的这种进化性
如何保证通过用户的显式的行为得到的兴趣是有效的
所以DIEN中主要开发了兴趣抽取层Interest Extractor Layer、兴趣进化层Interest Evolution Layer以解决上面两个缺点。

 

Interest Extractor Layer
兴趣抽取层的主要目标是提取出兴趣序列,而用户在某一时刻的兴趣是具有时序关系的,所以设计了GRU with attentional update gate (AUGRU,这个是Evolution Layer中加入注意力后的形态),增强在兴趣变化中相关兴趣的影响,减弱不相关兴趣的影响。同时为了判定兴趣是否表示的合理,又增加了一个辅助loss,来提升兴趣表达的准确性:

 

 
如上图中左边的小块是辅助网络,输入用户下一时刻真实的行为e(t+1)作为正例,负采样的行为为负例e(t+1)',分别与GRU抽取出的兴趣h(t)到辅助网络中即可,以充分的提升用户兴趣的表达。
 
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]
        #计算loss
        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, in_, stag='auxiliary_net'):
        bn1 = tf.layers.batch_normalization(inputs=in_, 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
 
Interest Evolution Layer
兴趣进化层目标是刻画用户兴趣的进化过程,这里的Attention故事是:
interest drift:用户的兴趣具有偏向性。
interest individual:用户的兴趣之间具有独立性。
 
所以需要注意力机制去增强在兴趣变化中相关兴趣的影响,减弱不相关兴趣的影响,即给 GRU计算Attention权重,如上图红色的部分。注意力有三种变体可以选择:

 

 
BST
论文:Behavior Sequence Transformer for E-commerce Recommendation in Alibaba
地址:https://arxiv.org/abs/1905.06874

 

把Attention升级成Transformer之后真的简单粗暴,Transformer[1]博主已经整理过了,不再赘述。BST把特征Embedding之后直接送进去就ok。主要看看输入的几个特征吧:
 
图最左边的other feature分为四个部分,用户特征、商品特征、上下文特征、交叉特征
item里面的蓝色是位置编码,红色是行为序列中的物品
位置编码和Transformer里面不一样的是,会把表示位置的时间戳直接映射成向量而不是sin函数,最后就是一般的损失函数:

 

 
BERT4Rec
论文:BERT4Rec: Sequential Recommendation with Bidirectional Encoder Representations from Transformer
地址:https://arxiv.org/abs/1904.06690
 
有了Transformer,升级成BERT[2]就很自然了。BERT首用于NLP,那么正好,用户的行为序列很像文本序列,于是BERT4Rec吧。

 

模型结构如上图所示,输入是[v1,v2,...,vt]的序列,同时最后一个vt被mask掉,然后强制Transformer结合上下文预测vt,那么对于用户的行为序列,很自然就能得到最后的输出为推荐的结果。
 
有两个Trick需要注意:
输入不是所有的用户行为序列。因为不同用户的行为序列长度可能相差太大了,所以采用最近的N个行为序列做输入
在模型训练的时候并不是只mask最后一个,而是和BERT本身一样,随机mask,然后预测masked的部分
 
PRM
论文:Personalized Re-ranking for Recommendation
地址:https://arxiv.org/abs/1904.06813
 
这篇阿里ResSys'19文章的重点在于推荐后的重排序。贡献主要是利用Transformer+用户个性化重排:
用户和列表中物品的交互能更有倾向性
Transformer的self-attention可以有效捕捉特征间的交互

 

具体模型架如上图,得到initial list之后把每个item的特征x和用户偏好p拼起来,这两个特征都是预训练得到的(比如用任意一个CRT的模型结合用户的历史行为,物品等等的信息进行训练就行):

 

 
注意力的其他玩法
推荐系统也算是很大的领域了,所以关于注意力的玩法也有很多,所以重点决定是为什么要用Attention。比如
要融合各种特征(attention或者co-attention,cross-attention)
特征是否分级(Hierarchical,level-attention)
特征间是否存在交互(interactive attention)
特征是否群组关系(Group-Attention)
是否存在动态的变化(dynamic attention,可能一般的用法会按时间线算多次以捕捉这种动态性)
然后或许仅仅算Attention已经不够了,那么魔改升级Attention变成High-order-Attention,Channel-wise-Attention,Spatial-Attention等等.....还有其他的注意力变体[3]博主以前也整理过了,就不再多说。
 
目前Attention的升级已经逐步暴力,从self-Attention到Transformer到BERT,效果也自然是变好了。
 
声明:文章收集于网络,版权归原作者所有,为传播信息而发,如有侵权,请联系小编删除,谢谢!
 
 

微信公众号

声明:本站部分作品是由网友自主投稿和发布、编辑整理上传,对此类作品本站仅提供交流平台,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,不为其版权负责。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。

网友评论:

发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
SEM推广服务

Copyright©2005-2026 Sykv.com 可思数据 版权所有    京ICP备14056871号

关于我们   免责声明   广告合作   版权声明   联系我们   原创投稿   网站地图  

可思数据 数据标注行业联盟

扫码入群
扫码关注

微信公众号

返回顶部