datawhale深度推荐模型组队学习Task01-DeepCrossing模型

序言:
DeepCrossing模型是由微软提出的应用于Bing搜索广告推荐的模型,作为datawhale深度推荐模型的开篇,DeepCrossing模型结构并不复杂,原理也很简单,可谓是相当新手友好了。

1 模型原理

DeepCrossing模型的结构设计是明确以问题为导向的:
问题1:推荐系统中存在大量的类别特征,例如广告ID等,这些特征编码之后会形成大型稀疏矩阵,不利于神经网络学习。
解决办法:采用Embedding将稀疏向量稠密化。
问题二:如何让特征自动交叉组合。
解决办法:采用多层残差网络。神经网络的一大特点就是可以将输入特征进行层层的交叉组合,网络越深则特征组合越复杂,能够学习到的就越多。然而网络层数太深就会存在梯度消失的问题,导致网络的学习能力下降。残差网络能够跳过中间层,因此相较于一般的神经网络可以有更大的深度。
问题三:如何在输出层达成问题设定的优化目标。
解决办法:在最终输出之前加入Scoring层,用来拟合优化目标,在CTR预估中通常采用逻辑回归来输出概率值。
将以上方法串联起来,就得到了DeepCrossing模型的结构:将输入特征划分为数值型特征和类别型特征,对类别型特征做Embedding处理,然后将处理后的类别型特征与数值型特征拼接起来,一起放进残差网络中进行学习,学习的结果放入Scoring层拟合设定的目标,最终就得到了预期的输出结果。
在这里插入图片描述

2 模型构建

2.1 构建数据集

采用已经处理过的criteo部分数据,数据集包括39个特征,其中I1 ~ I13为数值型特征,C1 ~ C26为类别特征,目标是预测CTR。以下是训练集的部分数据:
在这里插入图片描述
因为DeepCrossing模型要对类别型特征做Embedding处理,而数值型特征不做处理,因此在构建数据集时要分离开数值型特征和类别特征:

# 类别型特征
cate_feas = [col for col in train_df.columns if col[0]=='C']
# 数值型特征
num_feas = [col for col in train_df.columns if col[0]=='I']
# 构造训练数据
X_train = [train_df[num_feas].values.astype('float32'), train_df[cate_feas].values.astype('float32')]
y_train = train_df['Label'].values.astype('int32')

X_valid = [valid_df[num_feas].values.astype('float32'), valid_df[cate_feas].values.astype('float32')]
y_valid = valid_df['Label'].values.astype('int32')

模型中要对每一个类别型特征都做Embedding处理,其作用是把稀疏的类别编码矩阵稠密化。假设一个类别特征中包含1000个类别,经过one-hot编码后就得到一个长度为1000的稀疏向量[0, …, 0, 1, 0, …, 0],我们将Embedding的输出维度设置为8,那么Embedding就能将这个稀疏向量转化为长度为8的稠密向量。因此,Embedding层有三个重要参数:

  • Input_dim:输入维度。就是类别特征中所包含的类别个数,在上述假设中就是1000。
  • Input_length:输入长度。这取决于类别特征的输入形式,在上述假设中采用one-hot编码,那么这个类别特征的每一行数据就是长度为1000的向量,所以输入长度是1000。在这个问题中,预处理criteo数据集时采用的是LabelEncoder编码,每一行数据就只是一个表示类别的数值,所以输入长度是1。
  • output_dim:输出维度。这个值是自己设定的,表示你希望将输入数据压缩到多少维,在上述假设中输出维度就是8。

为了方便后续构建模型,采用字典来保存数值型特征和类别型特征的输入维度及输出维度:

# 构建数值型(稠密)特征字典
def denseFeature(feat):
    return {'feat': feat}
# 构建类别型(稀疏)特征字典,其中feat_num表示类别特征的输入维度,embed_dim表示输出维度
def sparseFeature(feat, feat_num, embed_dim=4):
    return {'feat': feat, 'feat_num': feat_num, 'embed_dim': embed_dim}
# 将数值型特征和类别型特征共同保存在列表中
feature_columns = [[denseFeature(feat) for feat in num_feas]] + \
                    [[sparseFeature(feat, len(data_df[feat].unique()), embed_dim=embed_dim)
                        for feat in cate_feas]]

2.2 构建残差模块

DeepCrossing采用残差网络来进行特征之间的交叉组合,一个残差网络是由多个残差模块堆叠而成的。
残差模块的结构很简单,一个常规的残差网络由两个Dense层组成。对于输入数据x,在进入残差模块后就兵分两路,一路直达终点,另一路经过Dense-ReLU-Dense得到x1,将x和x1相加后输入ReLU,就构成了一个残差模块。注意x要与x1相加,因此要保证x1的维度与x的维度相同,也即是第二个Dense层的节点个数要与x的维度相同。
在这里插入图片描述
在tf.keras中可以通过继承Layer类来自定义网络层:

#构建残差模块
class ResidualLayer(Layer):
    def __init__(self, hidden_unit, dim_stack):
        super(ResidualLayer, self).__init__()
        self.layer1 = Dense(units=hidden_unit, activation='relu')
        self.layer2 = Dense(units=dim_stack, activation=None)
        self.relu = ReLU()
        
    def call(self, inputs, **kwargs):
        x = inputs
        x = self.layer1(x)
        x = self.layer2(x)
        outputs = self.relu(x + inputs)
        return outputs

2.3 构建DeepCrossing模型

同样的,通过继承Model类来自定义模型:

#构建Deep Crossing模型
class DeepCrossing(keras.Model):
    def __init__(self, feature_columns, hidden_units, dropout_rate=0, embed_reg=1e-4):
        super(DeepCrossing, self).__init__()
        #分别取出数值型特征和类别特征
        self.dense_feas, self.sparse_feas = feature_columns
        #对类别型特征中的每个特征都定义一个Embedding层
        self.embed_layers = {
            'embed_' + str(i): Embedding(input_dim=feat['feat_num'],
                                             input_length=1,
                                             output_dim=feat['embed_dim'],
                                             embeddings_initializer='random_uniform',
                                             embeddings_regularizer=l2(embed_reg))
            for i, feat in enumerate(self.sparse_feas)
        }
        # 所有类别型特征经过Embedding后拼接起来得到总的维度,也就是所有类别特征Embedding层的输出维度之和
        embed_dim = sum([feat['embed_dim'] for feat in self.sparse_feas])
        # 将数值型特征和Embedding后的类别型特征拼接起来即是残差网络的输入,于是残差网络的输入维度就是数值型特征长度和embed_dim之和
        dim_stack = len(self.dense_feas) + embed_dim
        # 设置残差网络
        self.resnet = [ResidualLayer(unit, dim_stack) for unit in hidden_units]
        # 设置Dropout层
        self.res_dropout = Dropout(dropout_rate)
        self.dense = Dense(1)
    
    def call(self, inputs):
    	# 分别取出数值型输入数据和类别型输入数据
        dense_inputs, sparse_inputs = inputs
        # 将类别型输入数据输入Embedding层
        sparse_embed = tf.concat([self.embed_layers['embed_{}'.format(i)](sparse_inputs[:, i]) for i in range(sparse_inputs.shape[1])], axis=-1)
        # 拼接数值型输入数据和Embedding处理后的类别型数据
        stack = tf.concat([sparse_embed, dense_inputs], axis=-1)
        r = stack
        # 将所有数据输入残差网络
        for res in self.resnet:
            r = res(r)
        r = self.res_dropout(r)
        # 采用逻辑回归作为Scoring层,输出点击率值
        outputs = tf.nn.sigmoid(self.dense(r))
        return outputs
    
    def summary(self):
        dense_inputs = Input(shape=(len(self.dense_feas), ), dtype=tf.float32)
        sparse_inputs = Input(shape=(len(self.sparse_feas), ), dtype=tf.float32)
        keras.Model(inputs=[dense_inputs, sparse_inputs], outputs=self.call([dense_inputs, sparse_inputs])).summary()

2.4 模型训练

这里就是熟悉的常规步骤了:创建模型-编译-训练。

# 设置模型参数
embed_dim = 8
dropout_rate = 0.5
hidden_units = [256, 128, 64]
learning_rate = 0.001
batch_size = 1024
epochs = 10

# 创建并编译模型
mirrored_strategy = tf.distribute.MirroredStrategy()
with mirrored_strategy.scope():
    model = DeepCrossing(feature_columns, hidden_units)
    model.summary()
    model.compile(loss=binary_crossentropy, optimizer=Adam(learning_rate=learning_rate), metrics=['AUC'])

# 训练模型
history = model.fit(X_train, y_train, epochs=epochs, callbacks=[EarlyStopping(monitor='val_auc', patience=2, restore_best_weights=True)],
          batch_size=batch_size, validation_data=(X_valid, y_valid), verbose=2)

最终得到训练集和验证集曲线如下:
在这里插入图片描述

3 总结

总的来说,DeepCrossing的模型虽然结构简单,但却基本上完整地解决了深度学习在推荐系统中的应用问题,特别是采用残差网络自动完成特征的深度交叉与组合,无需人工构造特征,能够方便地完成端到端的模型训练。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值