Deep Crossing
如果说AutoRec模型是将深度学习的思想应用于推荐系统的初步尝试,那么微软于2016年提出的Depp Crossing模型[2]就是一次深度学习架构在推荐系统中的完整应用。
本文提出了一种深度交叉模型,该模型是一种深度神经网络,它能自动地将特征组合在一起,生成更优的模型。深度交叉的输入可以是一组单独的特征,可以是密集的,也可以是稀疏的。重要的交叉特征由网络隐含地发现,网络由embedding层和stacking层以及cascade of Residual Units组成。
传统的机器学习算法充分利用所有的输入特征来预测和对新实例进行分类。但是,仅使用原始的特征很少会提供最佳的结果。因此,无论是在工业界还是学术界,都存在着大量的工程工作来对原始特征进行改造。特征改造一种主要方式是基于多种特征的组合来构造函数并将函数的输出当作后续学习器的输入。这些组合特征有时称为cross features或者multi-way features.
将特征进行组合能发挥强大的功能,但是与此同时将特征进行组合却需要高昂的成本代价。随着特征数量的增加,管理,维护变得充满挑战。在网络规模的应用程序中,由于庞大的搜索空间以及给定数十亿个样本的训练和评估周期中的缓慢转换,寻找额外的组合特征来改进现有模型是一项艰巨的任务。
深度学习可以从单个特征中进行学习,而无需人工干预。在计算机视觉以及自然语言处理中已经发挥了它的功能。
Deep Crossing将深度学习的成功扩展到更普遍的环境中,其中各个特征具有不同的性质。更具体地说,它采用了诸如文本,分类,ID和数字特征之类的单个特征,并根据特定任务自动搜索最佳组合。此外,Deep Crossing旨在处理Web规模的应用程序和数据大小。值得注意的是,尽管Deep Crossing的输出是一个没有针对这些特征的明确表示的模型,但Deep Crossing在学习过程中确实以某种方式生成了组合特征。
eg:
微软使用的特征如下表所示,这些特征可以分为三类:一类是可以被处理成one-hot或者multi-hot向量的类别型特征,包括用户搜索词(query)、广告关键词(keyword)、广告标题(title)、落地页(landing page)、匹配类型(match type);一类是数值型特征,微软称其为计数型特征,包括点击率、预估点击率(click prediction);一类是需要进一步处理的特征,包括广告计划(campaign)、曝光计划(impression)、点击样例(click)等。
特征表示
类别型特征可以通过one-hot或multi-hot编码生成特征向量,数值型特征则可以直接拼接进特征向量中,在生成所有输入特征的向量表达后,Deep Crossing模型利用该特征向量进行CTR预估。深度学习网络的特点是可以根据需求灵活地对网络结构进行调整,从而达成从原始特征向量到最终的优化目标的端到端的训练目的。
模型结构
下图为Deep Crossing的模型结构,模型的输入是一系列个体特征。模型有四种类型的层,包括Embedding层,Stacking层,Residual Unit层和Scoring层。目标函数是log损失函数,也可以使用softmax函数或其他函数。
N是样本数,yi是每个样本的标签,pi是下图中单节点Scoring层的输出,在本例中是一个Sigmoid函数。
输入层:
def build_input_layers(feature_columns):
"""
构建输入层
param feature_columns: 数据集中的所有特征对应的特征标记之
"""
# 构建Input层字典,并以dense和sparse两类字典的形式返回
dense_input_dict, sparse_input_dict = {}, {}
for fc in feature_columns:
if isinstance(fc, SparseFeat):
sparse_input_dict[fc.name] = Input(shape=(1, ), name=fc.name)
elif isinstance(fc, DenseFeat):
dense_input_dict[fc.name] = Input(shape=(fc.dimension, ), name=fc.name)
return dense_input_dict, sparse_input_dict
Embedding层:Embedding层将稀疏的类别型特征转换成稠密的Embedding向量。Embedding层由单层神经网络构成
def build_embedding_layers(feature_columns, input_layers_dict, is_linear):
# 定义一个embedding层对应的字典
embedding_layers_dict = dict()
# 将特征中的sparse特征筛选出来
sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), feature_columns)) if feature_columns else []
# 如果是用于线性部分的embedding层,其维度为1,否则维度就是自己定义的embedding维度
if is_linear:
for fc in sparse_feature_columns:
embedding_layers_dict[fc.name] = Embedding(fc.vocabulary_size + 1, 1, name='1d_emb_' + fc.name)
else:
for fc in sparse_feature_columns:
embedding_layers_dict[fc.name] = Embedding(fc.vocabulary_size + 1, fc.embedding_dim, name='kd_emb_' + fc.name)
return embedding_layers_dict
Stacking层:Stacking层的作用是将Embedding的输出特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量
dense_dnn_list = list(dense_input_dict.values())
dense_dnn_inputs = Concatenate(axis=1)(dense_dnn_list) # B x n (n表示数值特征的数量)
# 因为需要将其与dense特征拼接到一起所以需要Flatten,不进行Flatten的Embedding层输出的维度为:Bx1xdim
sparse_dnn_list = concat_embedding_list(dnn_feature_columns, sparse_input_dict, embedding_layer_dict, flatten=True)
sparse_dnn_inputs = Concatenate(axis=1)(sparse_dnn_list) # B x m*dim (n表示类别特征的数量,dim表示embedding的维度)
# 将dense特征和Sparse特征拼接到一起
dnn_inputs = Concatenate(axis=1)([dense_dnn_inputs, sparse_dnn_inputs]) # B x (n + m*dim)
Multiple Residual Units层:该层的主要结构是多层感知机,相比标准的以感知机为基本单元的神经网络,Deep Crossing模型采用了多层残差网络作为MLP的具体实现。在推荐模型中的应用,也是残差网络首次在图像识别领域之外的成功推广。通过多层残差网络对特征向量各个维度进行充分的交叉组合,使模型能够抓取到更多的非线性特征和组合特征的信息,进而使深度学习模型在表达能力上较传统机器学习模型大为增强。
class ResidualBlock(Layer):
def __init__(self, units): # units表示的是DNN隐藏层神经元数量
super(ResidualBlock, self).__init__()
self.units = units
def build(self, input_shape):
out_dim = input_shape[-1]
self.dnn1 = Dense(self.units, activation='relu')
self.dnn2 = Dense(out_dim, activation='relu') # 保证输入的维度和输出的维度一致才能进行残差连接
def call(self, inputs):
x = inputs
x = self.dnn1(x)
x = self.dnn2(x)
x = Activation('relu')(x + inputs) # 残差操作
return x
Scoring层:Scoring层作为输出层,就是为了拟合优化目标而存在的。对于CTR预估这类二分类问题,Scoring层往往使用的是逻辑回归模型,而对于图像分类等多分类问题,Scoring层往往采用softmax模型。
# block_nums表示DNN残差块的数量
def get_dnn_logits(dnn_inputs, block_nums=3):
dnn_out = dnn_inputs
for i in range(block_nums):
dnn_out = ResidualBlock(64)(dnn_out)
# 将dnn的输出转化成logits
dnn_logits = Dense(1, activation='sigmoid')(dnn_out)
return dnn_logits