CTR深度学习模型之 DIEN(Deep Interest Evolution Network) 的理解与示例

本文介绍阿里提出的兴趣进化网络(Deep Interest Evolution Network,以下简称DIEN。

前一篇文章介绍的 DIN 模型如下图所示:

CTR深度学习模型之 DIEN(Deep Interest Evolution Network) 的理解与示例_第1张图片

DIN 直接把用户的某个行为作为用户的兴趣,在实际的电商场景中这并不完全准确。

用户的兴趣受到环境因素与自身因素的影响,会不断的变化。例如用户会在一段时间内关注手机相关的商品,另一段时间内又会关注书籍相关的商品,而手机与书籍之间的关系就比较小了。本文的介绍的 DIEN 模型就是要捕捉用户兴趣的演变过程。DIEN 仍然采用了 attention 机制,捕捉与候选广告相关的兴趣发展路径。

模型概览

DIEN 模型结构如下图所示:

CTR深度学习模型之 DIEN(Deep Interest Evolution Network) 的理解与示例_第2张图片

与 DIN 相比,DIEN 将 User behaviors 的部分加以调整,变成了上图中绿色区域的Behavior Layer、黄色区域的Interest Extractor Layer、粉色区域的Interest Evolving Layer三个部分。

Behavior Layer

Behavior Layer 主要就是对用户行为数据进行 embedding,在此不做过多讲解。

Interest Extractor Layer

Interest Extractor Layer ,也就是兴趣提取层。用户行为是用户兴趣的载体,兴趣也会随着用户采取某个行动之后改变。因此要对兴趣建模,首先要对行为之间的依赖关系进行建模。考虑到效率与性能,文中使用 GRU 对行为之间的依赖关系进行建模。GRU 的公式如下:
u t = σ ( W u i t + U u h t − 1 + b u ) , r t = σ ( W r i t + U r h t − 1 + b r ) , h ~ t = t a n h ( W h i t + r t ∘ U h h t − 1 + b h ) , h t = ( 1 − u t ) ∘ h t − 1 + u t ∘ h ~ t , u_t = \sigma(W^ui_t+U^uh_{t-1}+b^u), \\ r_t = \sigma(W^ri_t+U^rh_{t-1}+b^r), \\ \tilde{h}_t = tan h(W^hi_t+r_t \circ U^h h_{t-1} + b^h), \\ h_t = (1-u_t) \circ h_{t-1} + u_t \circ \tilde{h}_t, ut=σ(Wuit+Uuht1+bu),rt=σ(Writ+Urht1+br),h~t=tanh(Whit+rtUhht1+bh),ht=(1ut)ht1+uth~t,
由于 GRU 的特性,候选广告商品是否被点击的这个 label 在反向梯度传播的过程中仅仅能够对 GRU 的最终状态形成有效的监督作用,而对于中间的行为序列隐含状态缺少有效的监督信息。因此,为了使得中间的隐含能够有效的学习兴趣信息,使用下一个时刻的行为来监督当前隐含状态的学习。为此,选择真实的下一个商品作为正样本,选择其他商品作为负样本,进而计算一个二分类的辅助损失函数:
L a u x = − 1 N ( ∑ i = 1 N ∑ t log ⁡ σ ( h t i , e b i [ t + 1 ] ) + log ⁡ ( 1 − σ ( h t i , e ^ b i [ t + 1 ] ) ) ) \begin{aligned} L_{a u x}=-& \frac{1}{N}\left(\sum_{i=1}^{N} \sum_{t} \log \sigma\left(\mathbf{h}_{t}^{i}, \mathbf{e}_{b}^{i}[t+1]\right)\right.\\ &\left.+\log \left(1-\sigma\left(\mathbf{h}_{t}^{i}, \hat{\mathbf{e}}_{b}^{i}[t+1]\right)\right)\right) \end{aligned} Laux=N1(i=1Ntlogσ(hti,ebi[t+1])+log(1σ(hti,e^bi[t+1])))
其中 h t i h_t^i hti 表示第 t 个隐含状态, e b i e_b^i ebi 表示有真实点击行为的正样本, e ^ b i \hat e_b^i e^bi 表示没有点击行为的负样本,其中 σ \sigma σ 表示将向量内积的结果输入到 sigmoid 函数:
σ ( a , b ) = 1 1 + e x p ( − a ⋅ b ) \sigma(a, b) = \frac{1}{1+exp^{(-a \cdot b)}} σ(a,b)=1+exp(ab)1
而全局的损失函数则变为:
L = L t a r g e t + α L a u x L = L_{target} + \alpha L_{aux} L=Ltarget+αLaux
其中, α 是平衡最终损失与兴趣表示的超参数。引入损失函数的好处有:

  1. 有助于 GRU 每个隐含状态学习到兴趣表示。
  2. 有助于降低长序列建模中梯度反向传播的难度。
  3. 辅助损失函数还有助于嵌入层学习更多的语义信息,得到更好的嵌入矩阵。

总的来说,兴趣提取层的作用是挖掘行为序列中商品之间的联系,对用户的兴趣进行提取和表达。但是,每个 GRU 的隐含状态还无法与候选广告商品产生某种联系,这部分工作是交给Interest Evolving Layer 来完成的。

Interest Evolving Layer

Interest Evolving Layer,如上图中红色区域所示,结合了注意力机制的局部激活能力以及GRU的序列学习能力来实现对与候选广告商品相关的兴趣演化过程建模。

具体而言,需要对用户兴趣与候选商品计算 attention:
a t = e x p ( h t W e a ) ∑ j = 1 T e x p ( h j W e a ) a_t = \frac{exp(h_tWe_a)}{\sum_{j=1}^Texp(h_jWe_a)} at=j=1Texp(hjWea)exp(htWea)
式子中的 α t \alpha_t αt 表示相似度系数(注意力得分), e a e_a ea 表示候选商品的 embedding 向量。在得到相似度系数之后,可以将这个系数嵌入第二个GRU中,至于如何结合到 GRU 中,论文给了三种方式:

1,GRU with attentional input(AIGRU)

这种方法直接将第二个 GRU 的输入与相似度系数相乘,即:
i t ′ = h t × a t i'_t = h_t \times a_t it=ht×at
但是这种方法效果不好,因为即使用系数 0 来表示候选商品与该兴趣完全无关,这个隐含状态仍然会影响到之后的兴趣演变过程的学习。

2,Attention based GRU(AGRU)

在问答领域,AGRU 可以有效的提取 query 中的关键信息。具体的,AGRU 利用相似度系数来代替 GRU 的 update gate,通过相似度系数来改变隐藏层的输出:
h t ′ = ( 1 − a t ) × h t − 1 ′ + a t × h ~ t ′ h'_t = (1-a_t)\times h'_{t-1}+a_t \times \tilde{h}_t' ht=(1at)×ht1+at×h~t
虽然 AGRU 可以控制隐藏层的输出,但是它使用了一个标量来代替原来公式中的向量,忽视了不同维度之间的差异。

3,GRU with attention update gate(AUGRU)

DIEN 的论文中结合了上面的两种方法,提出了 AUGRU:
u ~ t ′ = a t × u t ′ h t ′ = ( 1 − u ~ t ′ ) ∘ h t − 1 ′ + u ~ t ′ ∘ h ~ t ′ \tilde{u}_t' = a_t \times u_t' \\ h_t' = (1-\tilde{u}_t') \circ h_{t-1}' + \tilde{u}_t' \circ \tilde{h}_t' u~t=at×utht=(1u~t)ht1+u~th~t
文中将连续 50 天点击的广告作为样本,用户点击目标商品之前 14 天的点击行为作为历史行为序列。其中前 49 天的样本作为训练集,第 50 天的样本作为测试集。实验表明,AUGRU 效果最佳。

模型示例

例子是在 tensorflow2 的环境中使用了 deepctr 实现的 DIN 模型,deepctr 安装方式如下:

pip install deepctr[gpu]

在其 github 仓库中提供了一个 demo,其代码以及关键部分的注释如下:

import numpy as np

from deepctr.models import DIEN
from deepctr.feature_column import SparseFeat, VarLenSparseFeat, DenseFeat,get_feature_names


def get_xy_fd(use_neg=False):
    # 对基础特征进行 embedding
    feature_columns = [SparseFeat('user',vocabulary_size=3,embedding_dim=10),
                        SparseFeat('gender', vocabulary_size=2,embedding_dim=4), 
                        SparseFeat('item_id', vocabulary_size=3,embedding_dim=8), 
                        SparseFeat('cate_id', vocabulary_size=2,embedding_dim=4),
                        DenseFeat('pay_score', 1)]
    
    # 指定历史行为序列对应的特征
    behavior_feature_list = ["item_id", "cate_id"]
    
    # 构造 ['item_id', 'cate_id'] 这两个属性历史序列数据的数据结构: hist_item_id, hist_cate_id
    # 由于历史行为是不定长数据序列,需要用 VarLenSparseFeat 封装起来,并指定序列的最大长度为 4 
    # 注意,对于长度不足4的部分会用0来填充,因此 vocabulary_size 应该在原来的基础上 + 1
    # 详细内容参考:https://deepctr-doc.readthedocs.io/en/latest/Examples.html#multi-value-input-movielens
    feature_columns += [
        VarLenSparseFeat(SparseFeat('hist_item_id', vocabulary_size=3 + 1, embedding_dim=8, embedding_name='item_id'),
                         maxlen=4, length_name="seq_length"),
        VarLenSparseFeat(SparseFeat('hist_cate_id', 2 + 1, embedding_dim=4, embedding_name='cate_id'), maxlen=4,
                         length_name="seq_length")]

    # 基础特征数据
    uid = np.array([0, 1, 2])
    ugender = np.array([0, 1, 0])
    iid = np.array([1, 2, 3])
    cate_id = np.array([1, 2, 2]) 
    score = np.array([0.1, 0.2, 0.3])

    # 构造历史行为序列数据
    # 构造长度为 4 的 item_id 序列,不足的部分用0填充
    hist_iid = np.array([[1, 2, 3, 0], [3, 2, 1, 0], [1, 2, 0, 0]])
    # 构造长度为 4 的 cate_id 序列,不足的部分用0填充
    hist_cate_id = np.array([[1, 2, 2, 0], [2, 2, 1, 0], [1, 2, 0, 0]])
    # 行为序列长度
    behavior_length = np.array([3, 3, 2])
    
    
    # 构造实际的输入数据
    feature_dict = {
     'user': uid, 'gender': ugender, 'item_id': iid, 'cate_id': cate_id,
                    'hist_item_id': hist_iid, 'hist_cate_id': hist_cate_id,
                    'pay_score': score, "seq_length": behavior_length}
    
    # 使用负采样
    if use_neg:
        feature_dict['neg_hist_item_id'] = np.array([[1, 2, 3, 0], [1, 2, 3, 0], [1, 2, 0, 0]])
        feature_dict['neg_hist_cate_id'] = np.array([[1, 2, 2, 0], [1, 2, 2, 0], [1, 2, 0, 0]])
        feature_columns += [
            VarLenSparseFeat(SparseFeat('neg_hist_item_id', vocabulary_size=3 + 1, embedding_dim=8, embedding_name='item_id'),
                             maxlen=4, length_name="seq_length"),
            VarLenSparseFeat(SparseFeat('neg_hist_cate_id', 2 + 1, embedding_dim=4, embedding_name='cate_id'),
                             maxlen=4, length_name="seq_length")]

    x = {
     name:feature_dict[name] for name in get_feature_names(feature_columns)}
    y = np.array([1, 0, 1])
    return x, y, feature_columns, behavior_feature_list


if __name__ == "__main__":
    if tf.__version__ >= '2.0.0':
        tf.compat.v1.disable_eager_execution()
    USE_NEG = True
    x, y, feature_columns, behavior_feature_list = get_xy_fd(use_neg=USE_NEG)

    model = DIEN(feature_columns, behavior_feature_list,
                 dnn_hidden_units=[4, 4, 4], dnn_dropout=0.6, gru_type="AUGRU", use_negsampling=USE_NEG)

    model.compile('adam', 'binary_crossentropy',
                  metrics=['binary_crossentropy'])
    history = model.fit(x, y, verbose=1, epochs=10, validation_split=0.33)

DIEN 模型至少需要传入两个参数,一个是 dnn_feature_columns , 用于对所有输入数据进行 embedding;另一个是 history_feature_list,用于指定历史行为序列特征的名字,例如 [“item_id”, “cate_id”]。

要特别注意的地方是:

1,特征 f 的历史行为序列名为 hist_f 。例如要使用 ‘item_id’, ‘cate_id’ 这两个特征的历史行为序列数据,那么在构造输入数据时,其命名应该加上前缀“hist_” ,即 ‘hist_item_id’, ‘hist_cate_id’。

2,使用负样本的情况下,负样本序列数据则需要加上前缀“neg_hist_” ,即 ‘neg_hist_item_id’, ‘neg_hist_cate_id’。

3,必须给定 seq_length ,即每个序列的长度作为输入数据。

带来的启发

1,将监督学习引入 GRU 的训练,有助于中间的隐含状态学习到更多信息。

2,以 AUGRU 提供了一种有效将 attention 机制得到的相似度得分融入 GRU 的方式。

3,辅助 Loss 的设计虽然增加了训练时间,但是有助于梯度的反向传播,并且不会对预测任务增加计算量。

参考文章:

详解阿里之Deep Interest Evolution Network(AAAI 2019)

从DIN到DIEN看阿里CTR算法的进化脉络

Deep Interest and Evolution Network for CTR

你可能感兴趣的:(CTR,相关,深度学习相关,推荐系统入门,DIEN,CTR,tensorflow)