介绍
工业界通用推荐系统包括两个阶段的流程,匹配(match)和排序(rank)。在匹配的过程中主要是根据用户的一些兴趣点,找到可能感兴趣的潜在商品集。由于整个商品集的海量性,对实时访问的用户去计算他对全部商品的感兴趣程度是不实际的,所以需要预先根据一些兴趣、特征策略等来寻找用户可能感兴趣的潜在商品集,在此基础上,再根据特定的模型算法来进行商品集兴趣分深层排序,效果指标往往通过点击率(Click Trough Rate)、转化率、时长等来量化,所以 rank 阶段的主要目的就在于预测一个用户在他感兴趣的商品中的 CTR 等,并且根据预测分值的大小进行排序,最终返回排序作为推荐系统的推荐结果。
本文将介绍三篇重点分别在 ranking,matching 和 feature enginerring 上的文章,借此来展示业界推荐系统方面的一些最新工作进展。
1. Ranking:
Behavior Sequence Transformer for E-commerce Recommendation in Alibaba
这篇文章主要关注在 rank 阶段的改进。以往在预测 CTR 场景下常见的深度学习方法是基于嵌入层与多层前馈神经网络的结合,将大量的低阶原始特征通过简单地嵌入层进行降维,然后降维后的低维特征再作为全连接网络的输入,最终通过网络输出对 CTR 点击率的概率预测(由于输出结果是一个点击概率,所以取值范围落在区间[0,1]上)。具有代表性的工作参考 WDL(wide and deep learning by Google)和 DIN(Deep Interest Neworks by Alibaba)。
但是上述方法存在一个比较明显的问题,直接输入原始特征实际上缺失了很多高阶的特征信息,比如高阶的交互项、序列信息等。这类特征在推荐场景下往往比较重要,比如年龄特征与性别特征的交互项可以刻画不同年龄层与不同性别的购买需求差异性。另一点,一个客户在购买了手机后很有可能会继续浏览手机配件等商品,就需要一些行为序列特征去捕捉这些信息。因此,从这些实例中可以获知,高阶的特征在进行点击率预测的任务十分重要。对比前面提到的两种方法,WDL 仅仅是简单地拼接 raw features,DIN 考虑了通过注意力机制来刻画商品与用户的历史兴趣的多峰表征,二者都没有考虑行为的序列特征。
为了解决这方面的问题,作者参考了在 NLP 领域机器翻译任务 transformer 模块。由于在机器翻译中,语言的序列信息是十分关键的,而 transformer 可以取得很好的效果,所以认为如果把客户的购买历史看做是一串文字,那么每种商品之间的序列相关性的信息也可以通过 transformer 捕捉到。
该论文整体网络结构如下,主要包含三个部分:嵌入层,转换层,前馈网络层。
序列信息的 item 特征作者又分为了两个部分,分别是 Sequence Item Features 和 Position Features。其中 Sequence Item Features 主要是商品的 item_id 和 category_id 这两个可以表征商品信息的特征,原因是考虑到提取序列信息的计算复杂性,输入全部的 item 特征效率很低。Position Feature 实际上是通过 postional embedding 的变换来获取的,在"Attention is all your need"中有提及。本文中考虑一个客户的浏览历史,时间间隔最能够体现浏览历史中的位置信息。
因此本文定义如下 position embedding:pos(v_i) = t(v_t) - t(v_i),其中 t(v_t) 代表的是推荐系统的推荐时间点,t(v_i)代表用户点击第 i 件商品的时间点。构造序列特征之后,通过进行序列特征的嵌入,代表浏览历史的长度,是嵌入空间的维度。
转换层:
Multi-Head Self-Attention(多头注意力模型)是 Transformer 的核心单元,理解 MH Attention 机制对于 Transformer 层原理掌握特别重要,并且在 Encoder 和 Decoder 中都有用到。MH Self-attention 最新应用可以替代一些基于 RNN 行为序列模型在推荐场景落地。另外在下文即将介绍的 AutoInt 中也是引入了 Multi-head Self-attention 核心思想,所以这里花一定篇幅对此进行原理讲解。
先从一个机器翻译的小例子来看:
”The animal didn't cross the street because it was too tired”
单词"it"表示什么呢,是"animal"还是"street",对机器算法来说识别这个不是一件简单的事情,算法在处理每个词的时候需要知道上下文信息。
较早一代 NLP 算法中 RNN、LSTM、SRU 等序列模型可以处理这种场景,基本做法是将句子分词,然后每个词转化为对应的词向量序列,经过 RNN 算法来处理词序列信息,在翻译这种场景仅用 RNN 很难做到高水准,这样出现了 RNN 的变种:Encoder-Decoder,也叫 Seq2Seq。如下图所示,在 Encoder 阶段,输入数据编码成一个上下文语义向量 c(语义向量 c 可以有多种表达方式,最简单的方法就是把 Encoder 的最后一个隐状态赋值给 c),Encoder 是一个 RNN。Decoder 是同样的 RNN(也可以不一样的结构),拿到 c 之后,对其进行解码操作,c 当做之前的初始状态 h0 输入到 Decoder 中,每一次得到一个最有可能的翻译结果,然后让所有单词的 cross entropy 达到最小。
RNN、LSTM 这种类型算法自然可以很好的处理序列信息,结构相对比较简单,本质上都是递归处理结构,缺点是无法做到并行,训练速度比较慢,另外它也是一种马尔科夫决策过程,无法很好的学习全局结构信息。那有没有改进的算法呢?Attention,对"Attention is All you need",这也是谷歌 17 年的一篇经典论文,后面在此基础上也诞生了各种优秀论文。
接着看上面翻译的小例子,这里的"it"表示"animal"还是"street"呢,联系上下文,就知道 it 很大概率指的是 animal。
下图直观地展示了 self-attention 机制,计算每个单词与其他单词之间的关联,这里用 attention score 来表示关联度,处理"it"时"the"、"animal"就有比较高的 attention score。这些 score 在 self-attention 中就是权重的概念,对输入 vector 加权然后喂入前馈神经网络中,得到新的表示,这样可以很好的获取上下文信息。
Q 是查询,K 是键,V 是数值。光从上面图中理解起来比较抽象,结合原论文以及代码,就比较容易理解了,先看看核心部分代码:
def scaled_dot_product_attention(q, k, v, mask):
"""
参数:
q: 请求的形状 == (..., seq_len_q, depth)
k: 主键的形状 == (..., seq_len_k, depth)
v: 数值的形状 == (..., seq_len_v, depth_v)
(..., seq_len_q, seq_len_k)。默认为None。
返回值:
输出,注意力权重
"""
matmul_qk = tf.matmul(q, k, transpose_b=True) # (..., seq_len_q, seq_len_k)
# 缩放 matmul_qk
dk = tf.cast(tf.shape(k)[-1], tf.float32)
scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)
# softmax 在最后一个轴(seq_len_k)上归一化,因此分数
# 相加等于1。
attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1) # (..., seq_len_q, seq_len_k)
output = tf.matmul(attention_weights, v) # (..., seq_len_q, depth_v)
return output, attention_weights
class MultiHeadAttention(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
self.num_heads = num_heads
self.d_model = d_model
assert d_model % self.num_heads == 0
self.depth = d_model // self.num_heads
self.wq = tf.keras.layers.Dense(d_model)
self.wk = tf.keras.layers.Dense(d_model)
self.wv = tf.keras.layers.Dense(d_model)
self.dense = tf.keras.layers.Dense(d_model)
def split_heads(self, x, batch_size):
"""分拆最后一个维度到 (num_heads, depth).
转置结果使得形状为 (batch_size, num_heads, seq_len, depth)
"""
x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
return tf.transpose(x, perm=[0, 2, 1, 3])
def call(self, v, k, q, mask):
batch_size = tf.shape(q)[0]
q = self.wq(q) # (batch_size, seq_len, d_model)
k = self.wk(k) # (batch_size, seq_len, d_model)
v = self.wv(v) # (batch_size, seq_len, d_model)
q = self.split_heads(q, batch_size) # (batch_size, num_heads, seq_len_q, depth)
k = self.split_heads(k, batch_size) # (batch_size, num_heads, seq_len_k, depth)
v = self.split_heads(v, batch_size) # (batch_size, num_heads, seq_len_v, depth)
# scaled_attention.shape == (batch_size, num_heads, seq_len_q, depth)
# attention_weights.shape == (batch_size, num_heads, seq_len_q, seq_len_k)
scaled_attention, attention_weights = scaled_dot_product_attention(
q, k, v, mask)
scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3]) # (batch_size, seq_len_q, num_heads, depth)
concat_attention = tf.reshape(scaled_attention,
(batch_size, -1, self.d_model)) # (batch_size, seq_len_q, d_model)
output = self.dense(concat_attention) # (batch_size, seq_len_q, d_model)
return output, attention_weights
代码是 Multi-head attention 类的构建方式,下面会提到 Multi-head attention 是什么,这里先介绍 self-attention 中的 attention score 怎么计算的,Q、K、V 怎么来的。其实是从输入 embeding 经过线性层变换得到了,有三个权重矩阵,WQ、WK、WV,输入 X 与这三个权重矩阵相乘得到,其实表示的意义大致相同,只是生成了不同的表达形式,也是神经网络里面一些思想,很难去说清楚为什么要这样做。WQ、WK、WV 这三个权重矩阵其实也是模型需要训练的参数矩阵。Attention score 生成大概可以分为以下几个步骤:
1、输入词生成 embedding 向量,经过线性层变换得到对应 Q、K、V 向量。
2、计算输入词 Query Vector 与其他相关词 Key Vector 点积,再对 score 进行归一化,再通过 softmax 来获取权重(attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1) )。
3、将 softmax 结果与 Value Vector 相乘得到加权的权重向量 attention score(output = tf.matmul(attention_weights, v))
投影矩阵,是一个共用的嵌入矩阵。再结合一层全连接神经网络的映射来加强非线性形式,最终的 self_attention 模块的形式为:
再来看看 multi-head attention:
简单来说,multi-head attention 就是多个 self-attention 的集成,为什么要这么做呢,论文中说是将模型分为多个头,形成多个子空间,这样可以让模型从不同角度去学习多方面不同的信息,再去融合,具体是不是真的可以让模型学习"不同子空间的特征",这里还没有深入作研究。其实通过增加模型 layer 是不是也可以学习到更多信息,当然相对 multi-head 来说性能肯定比不上,因为 multi-head 是并行的,这也是论文中提到的一个优势,那论文里面提到的 multi-head 其他优点可以增加网络空间,怎么去考量,其实也存在一定玄学在里面。
前馈网络层:
将其他特征与转换器的输出拼接后输入三层前馈神经网络,目的是为了处理其他特征的有效信息。由于 CTR 问题是一个预测点击概率的问题,所以作者选择一个 sigmoid 函数作为输出层,损失函数的选择也是标准的交叉熵损失:
模型评估:
本文选择 WDL 和 DIN 作为对比模型,构建了一个 WDL+seq 的基准模型,基准模型的序列信息是通过简单地将历史信息直接进行嵌入得到的。本文的网络结构和 WDL 的区别在于转换层的搭建,与 DIN 区别则是在处理序列信息上思路不同。
作者选择 AUC 来作为模型评估的度量,并且也考量了 A/B 测试的 CTR 结果和推荐系统的响应时间(RT response time)。
从上述的结果可以看出,文章提出的 BST 网络具有更好的效果。
BST 的主要工作在于改进 raw feature 的重新组合问题以及进行高阶特征提取,在推荐系统中针对特征工程的工作也有很多。有一类基于 WDL 的工作,主要考虑在于将 item 的特征或者用户的特征进行交互项的构建,比如 DeepFM, Cross networks 等,大多借鉴了 Factorization Machines。这类方法的问题主要在于特征的构建效率比较低,而且高阶交互项没有办法提取。
因此另一类基于深度学习的方法通过复杂网络结构来提取高阶特征,这类方法的问题在于获取到的特征解释性较差,一些任务场景中需要特征的解释性与实际意义来提升工作效率。同时考虑了特征的解释性与高阶复杂性的结果,AutoInt 有相对较好的效果,但是也受制于任务种类和应用场景。AutoInt 也是基于 multihear self-attention 机制,重点考虑对于用户信息、商品信息这类原始特征的处理(下文有介绍),AutoInt 实际上可以作用在"other features"中,来得到更高质量的信息。
但本文依旧是选择手动来构建"other features"中的 cross features 交互部分。AutoInt 的缺陷在于无法提取序列信息。DIN 确实也考虑利用用户行为序列的信息,但是处理办法与本文不同,DIN 是通过注意力机制来刻画当前推荐商品与历史商品之间的多峰表征,而本文则是利用注意力机制构建转换器将序列数据进行提取与嵌入。
总而言之,本文通过对序列数据特征的合理构建与利用,可以明显地提升推荐效果,淘宝将其投入在实际的生产场景中,也取得了较好的线上效果。
2. Matching:
Multi-Interest Network with Dynamic Routing for Recommendation at Tmall
本工作的主要目的在于在 matching 阶段进行特征工程的工作,实现提取更加结构化,使模型能够得到更多信息增益的特征。获取更高质量的特征就可以进而促进整个推荐系统的效果改善。
Matching 阶段需要获取客户感兴趣的商品,或者客户对某种商品感兴趣的概率。所以在这个阶段,如何量化客户的“兴趣”更重要。多数文章的做法一般是把用户的一些 profile 特征或者 history 信息简单地作为一个单向量输入。这种做法忽略了存在多种兴趣的可能性:首先,有未利用的信息,在对整个模型而言会带来相应的信息损失;其次,用一个向量来代表所有不同兴趣本身就比较难,等价于把每个用户所有不同的兴趣压缩在一起,因此将用户的所有兴趣信息混合在一起会导致在 matching 阶段匹配到不准确的商品类型或者商品个体。而且文中选择对用户的每个不同类型兴趣都会独立计算,这样能够保证 matching 阶段的待推荐商品的准确度。
文中提出了先利用类似聚类的思想,先在用户的历史信息中分析提取客户的多兴趣的形式,并且每个用户的总兴趣数允许是不同的。通过构建的多兴趣网络(MIND)可以实现这个目标,利用胶囊网络对用户行为的 embeded features 再提取就可以得到较好的兴趣类。基于这些兴趣类,就可以在整个商品池中通过近似最近邻查找的方式找出该客户最可能感兴趣的 N 个商品作为 candidates,随后放入 ranking 阶段进行推荐方面的预测。
网络结构如下图所示:
整体网络结构就是通过将商品集合中的特征通过嵌入,映射到低维的特征空间,再将不同商品的嵌入特征连接起来输入到多兴趣提取层,通过学习胶囊网络中的参数,得到较好的模型后,再将胶囊网络提取的用户行为信息与用户的画像信息进行连接输入给全连接层作为最后的特征整合。
在训练阶段,有几个主要的与其他工作不同之处:
通过构建合适的 label-attention 层来加强 capsules 网络的类内聚合性,原因是利用了商品类别作为查询,兴趣胶囊作为键值,如下图所示。输出的形式如下:
其中当 p 为 0 是,代表 capsule 选择最均匀的特征信息;如果 p 逐渐增大,那么 capsule 会选择相似度高的信息。如果 p 无穷大,那么最终就会变为一个最大 Attention 方法,并且会忽略其他的值。文中就是选择这种最大 attention 的方法。
在训练过程中的模型参数初始化过程中,为了防止每个 capsules 的训练出很类似的效果,在动态路由算法的基础上,做了小量的修改和调整。本文中,作者选择希望训练得到的胶囊落在同一个向量空间中,但是希望不同的 bilinear mapping 能够将兴趣映射到不同的向量空间中,所以选择 routing logit 为:
代表商品 i 的嵌入特征,代表兴趣的向量。
由于 Bilinear 矩阵是在各对胶囊之间共享的,为了避免矩阵训练得到单一结果,所以应该避免直接将所有权重初始化为 0,而应该从一个随机分布中抽样产生。本文选择从正态分布中产生这些随机初始值。
因为不同用户所用的兴趣胶囊个数可能是不同的,所以文中允许了动态的兴趣个数来提高网络的可塑性与准确性。
,由于多数人们的兴趣胶囊比较少量,这种可变个数的兴趣胶囊设定可以节省很多资源。
整个 B2I Dynamic Routing 伪代码如下:
在模型最终训练完成之后,整个 MIND 网络拿掉 label-aware 层就可以当做一个用户行为的表征映射。在运行阶段,用户的行为序列以及用户的画像特征全部输入到中,就可以得到多个向量特征。这些特征进一步用来选出前 N 个备选商品,用于推荐系统的下一阶段的 ranking 工作。
与其他工作的联系与区别:
YouTube DNN:两种方法都采用了 deep learning 的优势,通过深度学习可以获取较好的高阶特征表示。但是 YouTube 仅仅使用一个向量来表示用户行为,本文中的 MIND 使用多个向量来表示一个用户。所以 MIND 可以看做 DNN 的一种泛化模型。
DIN:二者都利用到了用户的不同的性却特征。DIN 仅仅使用了商品层面上的注意力机制,简单来说就是对商品 ID 的一些相似度计算;而本文利用了动态路由算法产生的兴趣网络来提取兴趣层面的信息。DIN 将 ranking 阶段融合在一起,而 MIND 实际上不是端到端的,他通过将两部分拆解开来,为整个系统提供了更好的稳健性。
模型评估:
CTR 预测:
胶囊网络的兴趣提取示例:
从第一个表格我们可以看出,所有与 Multi-interest 结合的模型对比起原始模型或者其他模型都得到了更好的效果。利用多兴趣网络确实可以得到不同客户的多种兴趣表征,能够筛选出更加有效的待推荐商品,从而提升推荐系统的准确度。在天猫的数据上,这种提升更加明显,因为大多数的人在购物时候的兴趣是由多种类型的。对于不同数据之间客户群体兴趣类别个数的比较,可以根据每个数据最好的超参数 K 的比较结果来确定。
在 CTR 预测上,MIND 也有明显的效果提升。
对于多兴趣特征的提取有效性,可以从图 5,图 6 中分析。图 5 中,上下两块代表了两个 user,每一行代表针对每个 user 训练得到的 interest,横轴上代表的是 user 的挑选商品行为,可以很明显的看出每种 Interest 所关联的商品类型是高度相近的,这反映了 interest capsules 得到的 multiple interest 都具有较好的聚合性。图 6 则表示在系统服务阶段,根据第一个 user 的 interest 来进行待推荐商品的选择。可以看到其中的商品相似度是很高的,最后一行代表的是 Youtube DNN 的商品相似度,对比发现,multi-interest 分出的多种兴趣效果十分明显。
3. Feature Engineering:
AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks
在整个推荐系统中,特征工程方面最常遇到两个问题:
输入特征的维度过高而且稀疏性很强;
有效的点击率预测都要基于高度准确的交叉特征,人为地构造交叉特征是一件十分耗时耗力的操作。
因此,寻找一种高效的特征提取方法是提升推荐系统表现效果的关键步骤。本文利用了深度学习的抽象特征提取的优势,通过 Multi-head attention 层进行高阶的特征组合。这种方法对于数值型特征与分类特征都很适用。
之前的工作也有考虑过交叉特征的文章,有的将全部的特征进行外积展开得到一组非常高稀疏性的特征,这类特征在进行模型训练的过程中十分容易造成过拟合的现象。寻找有意义的高阶交叉特征一般都是需要有经验的人进行人为构建,但是穷举出所有潜在的可能性是很难的,所以需要借助深度学习类的方法来进行有效的特征的低维表示学习。
深度学习类的方法虽然可以提取出高阶的特征,但是也有很大的局限性:
全连接神经网络已经被证实过在学习交互特征的过程中效率很低;
对提取出来的特征很难具有解释性;
本文提出的这种 AutoInt 方法就是针对上述两个问题对深度学习网络进行调整加以改进。
首先将数值变量与分类变量都进行低维嵌入,得到一个低维空间的向量表达,也是为了解决分类变量与数值变量之间没有良定义的内积这类算子的问题。随后将低维嵌入的特征向量输入到本文提出的 Interacting Layer,目的是为了得到具有交互性的新的特征。在每一层的 Interacting Layer 里,每一个特征都会通过 multi-head attention 机制和其他特征进行变换组合,这就是产生交叉特征的原理。而且由于 multi-head attention 可以把一个特征向量映射到多个不同的空间中,因此在经过 multi-head attention 变换后的特征的组合方式也是具有多种形式的。在经过一层的 Interacting Lyaer,特征经过了一阶的交叉组合,多次叠加 Interacting Layer 就可以得到高阶复杂的特征交叉组合。对于特征的解释性,由于注意力机制模块主要是利用变量之间的相似度,因此高度相似的特征进行组合时很容易得到较好的解释。
整个网络的结构如下:
本文主要的核心模块 Interacting Layer 的结构如下:
实际上是以关注的某个特征作为 query,其他的特征向量作为 key, value,并且允许多种不同类型的相似度计算从而得到不同的 attention value,这也是 multi-head 的主要特点。
最后把所有的向量拼接起来作为学习到的组合特征,最后经过一个 ReLU 变换与之前的 feature 重新结合即可得到 Interacting Layer 的输出:
最终的预测输出为:
由于本文的主要贡献在于,更加快速有效的高阶特征提取工作以及特征的可解释性,所以从这两个角度来看本文方法的效果。
通过表格可以看出 AutoInt 在大多数场景下都有更好的效果,对于 Interacting Layer 的层数选取也是依据实验结果得到的启发式结论:
当 Interacting Layer 过多的时候不仅会增加模型复杂度,模型的预测效果也会受到影响。最后是模型的特征解释:
在这个数据任务当中,Interacting Layer 确实捕捉到了有意义的组合特征:,左图中红色方框部分。右图我们给出了数据本身的相关性的热力图,从中可以发现,注意力机制基本能够将高度相关的特征进行组合并且给以较高的权重,这样的话一层 Interacting Layer 之后输出的特征实际上就具有了较好的解释性。
总结
以上这些是今年来在推荐系统方面比较有意义的工作,任何模型的效果都强依赖数据、特征、样本质量。论文思想实际上也是来源于人的本身行为习惯生活经验等,例如购物时候行为序列的重要性等。通过思考如何让计算机模拟或者关注人们在日常行为的一些关键模式,得到特征的有效转变和映射,让整个模型能够有更好的表现。所谓网络的构建不同也无非在于不同应用场景,特征的利用方式不同。
另外推荐领域一些最新论文其实也是参考了 NLP 一些新的思路,例如上面介绍的 BST、autoint 都引入了 Transformer 的核心模块 Multi-head Self-attention,在此基础上做各种变换,实现各种玩法。看懂论文需要先彻底熟悉 Multi-head Self-attention 的原理,可以结合源论文与代码去看,用开源数据集去跑流程,打印中间结构,熟悉论文思想的同时也可以提升工程实现能力。
从各自的出发点来看,三篇文章都可以相互弥补:
对于 MIND 而言,虽然已经考量了顾客在浏览商品的时候会有固定类别的兴趣偏好,但是仅仅考虑了历史行为中的聚类信息而没有利用行为的序列或者趋势信息,这种序列信息往往在下一步的对未来行为的推荐中会更有作用。另外文中也提及了,这种聚类的方式还有待改进,通过结合其他方法能够提升类别的聚合性,进一步提升类别的准确程度。而 BST 本身恰恰就是利用原始特征在 ranking 模块通过网络结构来获取序列行为信息。但 BST 本身的劣势在于,如果原始特征维度很大,人工的构建交叉特征很不现实。
此时,AutoInt 就可以出面帮助解决对于 raw features,如何快速高效构建 cross features 的问题。通过 AutoInt 构建出来的特征是否在其他的分类器或者模型上依旧能够带来提升,这是有待进一步实验研究的问题。另外,如何针对推荐系统的冷启动问题或者不同客户群体进行推荐的策略也可以基于以上工作加以改进。最后,这些光鲜亮丽的论文思想听上去很是高大上,在实际场景下真正复现论文里面的效果还是比较难的,需要结合数据场景作各种优化,模型结构优化,还要具备生产环境下复杂模型上线的能力,这些都是需要努力去攻克的。