今天开始了推荐系统的第五个模型Deep Interest Network(简称DIN)的学习,这是阿里巴巴在2018年发表的一篇论文,主要应用场景是阿里巴巴的电商广告推荐业务,论文主要的假设为,在电商网站,用户的兴趣是广泛且多样化的,他们的兴趣可能会由相关的广告信息而局部激活。具体的论文地址在此
https://arxiv.org/abs/1706.06978
在电商网站平台上,每天都有成千上百万的用户前来访问各类商品,带来了很多重要的用户行为数据。这些丰富的用户历史行为背后隐含着用户多样化的兴趣。比如,我自己近期浏览过裙子、手机壳、钙片和眼罩,当我看到一个腰靠的广告,我可能对这个腰靠感兴趣。这样的兴趣从我的历史浏览记录表里面可以近似推出,然而,并非历史访问列表里面的所有物品推演出的兴趣都同等程度地受腰靠广告的激活。
在工业界的算法应用领域,如在线广告,点击率预测是一个常见课题。随着深度学习的进一步发展,相关类似的问题都遵循类似的套路:第一步,将大规模稀疏特征通过分域embedding的方式映射为低维稠密特征;第二步,将embedding特征分组转换为固定长度的向量;第三步,将得到的向量直接结在一起,输入多层感知机,以此来学习特征之间的非线性关系。
DIN提出以上传统做法有两个问题,(1)每个用户的历史访问行为的序列长度是不一样的,用统一化的固定长度向量来表示不同用户的特征,相对而言,比较程式化(2)针对具体的某一个广告,用户的历史访问行为和当下要预测的这个广告的相关性是不一样的,用同等的特征交叉加权也显得过于粗放。
相应地,论文提出了他们的解决方案:在经典的推荐系统的网络结构中,构造局部激活单元。局部激活单元可以自适应地利用用户历史行为序列,学习到用户对当下广告的兴趣。与此同时,论文还提出了两大技术,mini-batch aware正则化和基于数据自适应的激活函数。
Deep Interest Network
特征表达:与用户有具体目的的搜索引擎不同,电商网站的推荐系统问题往往是在用户逛网站,且没有特别明确目的的情况产生的。前者,我们可以用搜索的关键词和已有的商品或者类目进行相似度的匹配,而后者,我们需要用合适的特征来提取用户当前的兴趣程度。以下为阿里巴巴的在线广告系统的特征情况:
值得注意的是,其他用户的基本信息特征是用one-hot表达,而此处用户的历史行为特征(如历史访问过的商品id、商店id、商品品类id等)的表示都是multi-hot的形式。举例:用户访问过的类别={书、桌子}可以编码为[0,...,0,1,...,1,0,...,0]。同时,在这个特征集合中,没有加入人为交叉特征,而是将特征交叉这块给深度神经网络去学习。
基本模型:之前提到的用embedding+MLP的常见网络结构如下所示
(1)Embedding层:该层的主要作用是将高维稀疏向量的输入转化为低维稠密向量。如果某一向量
(2)池化层和连接层:由于不同的用户的行为的数量是不一样的,因而,通常的做法是使用池化层,来得到一个固定长度的向量。
(3)多层感知机:全连接层
(4)损失函数:目标函数用最大似然函数表达
DIN网络结构
从上面的网络结构图来看,大部分网络层的设置是一样的,差异在于DIN在Embedding层和池化层之间引入了局部激活单元。具体地,该激活单元是作用于用户的行为上的,本质是个自适应加权的sum pooling,来得到给定一个特定广告
如此设计使得不同的广告对于最终用户兴趣表达
具体代码:
def DIN(feature_columns, behavior_feature_list, behavior_seq_feature_list):
# 构建Input层
input_layer_dict = build_input_layers(feature_columns)
# 将Input层转化成列表的形式作为model的输入
input_layers = list(input_layer_dict.values())
# 筛选出特征中的sparse特征和dense特征,方便单独处理
sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), feature_columns))
dense_feature_columns = list(filter(lambda x: isinstance(x, DenseFeat), feature_columns))
# 获取dense
dnn_dense_input = []
for fc in dense_feature_columns:
dnn_dense_input.append(input_layer_dict[fc.name])
# 将所有的dense特征拼接
dnn_dense_input = concat_input_list(dnn_dense_input)
# 构建embedding字典
embedding_layer_dict = build_embedding_layers(feature_columns, input_layer_dict)
# 因为这里最终需要将embedding拼接后直接输入到全连接层(Dense)中, 所以需要Flatten
dnn_sparse_embed_input = concat_embedding_list(sparse_feature_columns, input_layer_dict, embedding_layer_dict, flatten=True)
# 将所有sparse特征的embedding进行拼接
dnn_sparse_input = concat_input_list(dnn_sparse_embed_input)
# 获取当前的行为特征(movie)的embedding,这里有可能有多个行为产生了行为序列,所以需要使用列表将其放在一起
query_embed_list = embedding_lookup(behavior_feature_list, input_layer_dict, embedding_layer_dict)
# 获取行为序列(movie_id序列, hist_movie_id) 对应的embedding,这里有可能有多个行为产生了行为序列,所以需要使用列表将其放在一起
keys_embed_list = embedding_lookup(behavior_seq_feature_list, input_layer_dict, embedding_layer_dict)
# 使用注意力机制将历史movie_id序列进行池化
dnn_seq_input_list = []
for i in range(len(keys_embed_list)):
seq_emb = AttentionPoolingLayer()([query_embed_list[i], keys_embed_list[i]])
dnn_seq_input_list.append(seq_emb)
# 将多个行为序列attention poolint 之后的embedding进行拼接
dnn_seq_input = concat_input_list(dnn_seq_input_list)
# 将dense特征,sparse特征,及通过注意力加权的序列特征拼接
dnn_input = Concatenate(axis=1)([dnn_dense_input, dnn_sparse_input, dnn_seq_input])
# 获取最终dnn的logits
dnn_logits = get_dnn_logits(dnn_input, activation='prelu')
model = Model(input_layers, dnn_logits)
return model
参考资料:
- [DataWhale学习资料](https://github.com/datawhalechina/team-learning-rs/blob/master/DeepRecommendationModel/DIN.md)
- [Deep Interest Network论文] (https://arxiv.org/pdf/1706.06978.pdf)