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

NewBeeNLP出品

作者@上杉翔二

悠闲会 · 信息检索

当注意力机制都已经变成很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

  • 也可以直接在公众号后台回复『0033』获取

DIN这篇文章的Attention故事出发点在于两点观察:

  • 「Diversity」:多样性是指用户的兴趣是广泛的,一个用户会对多个物品,多个领域感兴趣

  • 「Local activation」:部分对应是指只有部分历史数据与目前推荐的物品相关(如推荐零食物品就与用户以前买过什么装备无关)

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

其中 代表用户的特征向量, 代表用户兴趣的特征向量(用户历史行为), 代表物品的特征向量。 是用户兴趣和候选物品的相关性 ,对齐了用户兴趣,实际上就是解决了Local Activation问题。然后用户特征向量就是所有加权后的历史行为和了。

整个模型框架如上图所示,左边是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就很好的缓解了这个问题。

  • 缺点:用到了历史行为数据,但是忽略了序列关系。

DIEN

  • 论文:Deep Interest Evolution Network for Click-Through Rate Prediction

  • 地址:https://arxiv.org/abs/1809.03672

  • 也可以直接在公众号后台回复『0034』获取

升级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权重,如上图红色的部分。注意力有三种变体可以选择:

  • GRU with attentional input (AIGRU):将注意力作为输入

  • Attention based GRU(AGRU):用注意力代替GRU的更新门

  • GRU with attentional update gate (AUGRU):对更新门加权

BST

  • 论文:Behavior Sequence Transformer for E-commerce Recommendation in Alibaba

  • 地址:https://arxiv.org/abs/1905.06874

  • 也可以直接在公众号后台回复『0035』获取

把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

  • 也可以直接在公众号后台回复『0037』获取

有了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

  • 也可以直接在公众号后台回复『0036』获取 

这篇阿里ResSys'19文章的重点在于推荐后的重排序。贡献主要是利用Transformer+用户个性化重排:

  • 用户和列表中物品的交互能更有倾向性

  • Transformer的self-attention可以有效捕捉特征间的交互

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

再把目前item在列表中的位置编码加进去:

再用Transformer来捕捉交互得到score就是重排的结果了。

注意力的其他玩法

推荐系统也算是很大的领域了,所以关于注意力的玩法也有很多,所以重点决定是为什么要用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,效果也自然是变好了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值