学习开源地址:datawhale
深度推荐学习笔记01 DeepCrossing
1. Deepcrossing在推荐系统中的应用
deepcrossing模型是在传统神经网络的基础上加入embedding、残差连接等思想发展而来,结构较为简单 ,于2016年由微软提出,完整地解决了特征工程、稀疏向量 稠密化、多层神经网络进行优化目标拟合等一系列深度学习再推荐系统的应用问题。
deepcrossing在推荐系统的典型应用场景为搜索引擎广告推荐:预测对某一广告,用户是否会点击(点击率预测/CTR预估)。在这种场景下,模型的输入一般有广告id等类别型特征以及广告预算等数值型特征。对于类别型特征,我们需要进行one-hot编码处理,而数值型特征一般需要进行归一化处理。下面说明deepcrossing如何利用这些处理好的特征向量进行CTR预估。
2.deepcrossing模型结构及原理
为了完成端到端的训练, DeepCrossing模型要在内部网络结构中解决如下问题:
1.离散类特征编码后过于稀疏, 不利于直接输入神经网络训练, 需要解决稀疏特征向量稠密化的问题
2.如何解决特征自动交叉组合的问题
3.如何在输出层中达成问题设定的优化目标
各层作用如下。
2.1embedding layer
将稀疏的类别特征转成稠密的embedding向量,embedding的维度会远小于原始的稀疏特征向量。一般来说,数值型特征不用embedding,直接到stacking layer
2.2stacking layer
把不同的embedding特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量。该层通常也被称为连接层,拼接的结果作为DNN的输入。
通过concatnate层进行拼接如下:
#将所有的dense特征拼接到一起
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)
2.3multiple residual units layer
该层的主要结构是MLP,但deepcrossing采用了残差网络进行连接。通过多层残差网络对特征向量各个维度充分的交叉组合,使得模型能够抓取更多的非线性特征和组合特征信息,增加模型的表达能力。残差网络结构如下图所示:
deep crossing模型使用稍微修改过的残差单元,它不使用卷积内核,改为了两层神经网络。残差单元是通过两层relu变换再将原输入特征相加回来实现的,代码如下:
# DNN残差块的定义
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
2.4 scoring layer
输出层,为了拟合优化目标而存在。对于CTR预估二分类问题,scoring往往采用逻辑回归,模型通过叠加多个残差块加深网络的深度,最后将结果转换成一个概率值输出。
# 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
3.deepcrossing layer的优异性
相比于FM,FFM只具备二阶特征交叉能力的模型,deepcrossing可以通过调整神经网络的深度进行特征之间的"深度交叉",这也是deepcrossing名称的由来。
4.代码实现
def DeepCrossing(dnn_feature_columns):
# 构建输入层,即所有特征对应的Input()层,这里使用字典的形式返回,方便后续构建模型
dense_input_dict, sparse_input_dict = build_input_layers(dnn_feature_columns)
# 构建模型的输入层,模型的输入层不能是字典的形式,应该将字典的形式转换成列表的形式
# 注意:这里实际的输入与Input()层的对应,是通过模型输入时候的字典数据的key与对应name的Input层
input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
# 构建维度为k的embedding层,这里使用字典的形式返回,方便后面搭建模型
embedding_layer_dict = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
#将所有的dense特征拼接到一起
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)
# 输入到dnn中,需要提前定义需要几个残差块
output_layer = get_dnn_logits(dnn_inputs, block_nums=3)
model = Model(input_layers, output_layer)
return model