【王喆-推荐系统】模型篇-(task4)Embedding+MLP模型

举报
野猪佩奇996 发表于 2022/01/23 00:33:30 2022/01/23
【摘要】 学习总结 (1)这次task的模型看似没啥新东西(embedding+MLP),但是对于tensorflow不熟悉,还有需要注意特征处理:类别型特征 Embedding 化,数值型特征直接输入 MLP。...

学习总结

(1)这次task的模型看似没啥新东西(embedding+MLP),但是对于tensorflow不熟悉,还有需要注意特征处理:类别型特征 Embedding 化,数值型特征直接输入 MLP。下一篇task是用pytorch实现的版本。
(2)Embedding+MLP 主要是由 Embedding 部分和 MLP 部分这两部分组成,使用 Embedding 层是为了将类别型特征转换成 Embedding 向量,MLP 部分是通过多层神经网络拟合优化目标。具体来说,以微软的 Deep Crossing 为例,模型一共分为 5 层:对于类别特征,先利用 Embedding 层进行特征稠密化,再利用 Stacking 层连接其他特征,输入 MLP 的多层结构,最后用 Scoring 层预估结果。
在这里插入图片描述

一、Embedding+MLP 模型的结构

2016年微软提出的深度学习Deep crossing模型就是这种结构——Deep Crossing 从下到上可以分为 5 层,分别是 Feature 层、Embedding 层、Stacking 层、MLP 层和 Scoring 层(如下图)。用于广告推荐。
在这里插入图片描述

1.1 Feature层和embedding层

Feature层即输入特征层,是模型的input部分,如上图的Feature#1是向上连接到embedding层,而Feature #2是直接连到stacking层。

原因:前者代表类别型特征经过one-hot编码后生成的特征向量,该特征过于稀疏,不适合直接输入网络中进行学习(所以先接入embedding层转为稠密向量);而后者是数值型特征。

注意:Embedding 层并不是全部连接起来的,而是每一个特征对应一个 Embedding 层,不同 Embedding 层之间互不干涉。回顾embedding层内部结构,Embeding 层的结构就是 Word2vec 模型中从输入神经元到隐层神经元的部分,这部分就是一个从输入层到隐层之间的全连接网络。
在这里插入图片描述

1.2 Stacking层

Stacking 层中文名是堆叠层,也叫连接(Concatenate)层作用是把不同的 Embedding 特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量。

1.3 MLP层

这层用的最简单的MLP层。在图 1 中指的是 Multiple Residual Units 层,中文叫多层残差网络。微软在实现 Deep Crossing 时针对特定的问题选择了残差神经元。

事实上,神经元的选择有非常多种,比如我们之前在深度学习基础知识中介绍的,以 Sigmoid 函数为激活函数的神经元,以及使用 tanh、ReLU 等其他激活函数的神经元。我们具体选择哪种是一个调参的问题,一般来说,ReLU 最经常使用在隐层神经元上,Sigmoid 则多使用在输出神经元,实践中也可以选择性地尝试其他神经元,根据效果作出最后的决定。

MLP 层的作用是让特征向量不同维度之间做充分的交叉,让模型能够抓取到更多的非线性特征和组合特征的信息,这就使深度学习模型在表达能力上较传统机器学习模型大为增强。

1.4 Scoring 层

Scoring 层,它也被称为输出层。
虽然深度学习模型的结构可以非常复杂,但最终我们要预测的目标就是一个分类的概率。
如果是点击率预估,就是一个二分类问题,那我们就可以采用逻辑回归作为输出层神经元;
如果是类似图像分类这样的多分类问题,往往在输出层采用 softmax 这样的多分类模型。

二、tensorflow代码实战

首先是特征选择,基于“类别型特征 Embedding 化,数值型特征直接输入 MLP”的原则,我们选择 movieId、userId、movieGenre、userGenre 作为 Embedding 化的特征,选择物品和用户的统计型特征作为直接输入 MLP 的数值型特征:
在这里插入图片描述
然后是模型结构选择:这里选择了一个三层的 MLP 结构,其中前两层是 128 维的全连接层。我们这里采用好评 / 差评标签作为样本标签,因此要解决的是一个类 CTR 预估的二分类问题,对于二分类问题,我们最后一层采用单个 sigmoid 神经元作为输出层就可以了。

关于细节,比如为什么要选三层的 MLP 结构,为什么要选 sigmoid 作为激活函数等等。其实,我们对模型层数和每个层内维度的选择是一个超参数调优的问题,这里的选择不能保证最优,我们需要在实战中需要根据模型的效果进行超参数的搜索,找到最适合的模型参数。

2.1 载入训练数据

定义好训练数据的路径 TRAIN_DATA_URL 了,然后根据你自己训练数据的本地路径,替换代码中的路径。

import tensorflow as tf
TRAIN_DATA_URL = "file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/sampledata/modelSamples.csv"
samples_file_path = tf.keras.utils.get_file("modelSamples.csv", TRAIN_DATA_URL)

  
 
  • 1
  • 2
  • 3

利用 TensorFlow 自带的 CSV 数据集的接口载入训练数据(如下代码)。
两个比较重要的参数:
(1) label_name,它指定了 CSV 数据集中的标签列。
(2) batch_size,它指定了训练过程中,一次输入几条训练数据进行梯度下降训练。
载入训练数据之后,我们把它们分割成了测试集和训练集。

# load sample as tf dataset
def get_dataset(file_path):
    dataset = tf.data.experimental.make_csv_dataset(
        file_path,
        batch_size=12,
        label_name='label',
        na_value="0",
        num_epochs=1,
        ignore_errors=True)
    return dataset


# split as test dataset and training dataset
train_dataset = get_dataset(training_samples_file_path)
test_dataset = get_dataset(test_samples_file_path)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里可以回顾下:
mini-batch:外层for为训练周期,内层for迭代mini-batch。
1)epoch:将所有的训练样本都进行了一次前向传递和反向传播,是一个epoch。
2)Batch-size:每次训练所用的样本数量
3)Iteration:内层for执行的次数。
ex:共有10000个样本,batch-size=1000,即每次拿1000个样本进行训练,则Iteration=10次。

2.2 载入类别型特征

# genre features vocabulary
genre_vocab = ['Film-Noir', 'Action', 'Adventure', 'Horror', 'Romance', 'War', 'Comedy', 'Western', 'Documentary',
               'Sci-Fi', 'Drama', 'Thriller',
               'Crime', 'Fantasy', 'Animation', 'IMAX', 'Mystery', 'Children', 'Musical']

GENRE_FEATURES = {
    'userGenre1': genre_vocab,
    'userGenre2': genre_vocab,
    'userGenre3': genre_vocab,
    'userGenre4': genre_vocab,
    'userGenre5': genre_vocab,
    'movieGenre1': genre_vocab,
    'movieGenre2': genre_vocab,
    'movieGenre3': genre_vocab
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

类别型特征主要有这三类,分别是 genreuserIdmovieId
注意:类别型特征 Embedding 化,数值型特征直接输入 MLP。

在载入 genre 类特征时,我们采用了 tf.feature_column.categorical_column_with_vocabulary_list 方法把字符串型的特征转换成了 One-hot 特征。在这个转换过程中我们需要用到一个词表,开头就定义好了包含所有 genre 类别的词表 genre_vocab(如上面代码)。

在转换 userIdmovieId 特征时,我们又使用了 tf.feature_column.categorical_column_with_identity 方法把 ID 转换成 One-hot 特征,这个方法不用词表,它会直接把 ID 值对应的那个维度置为 1。比如,我们输入这个方法的 movieId 是 340,总的 movie 数量是 1001,使用这个方法,就会把这个 1001 维的 One-hot movieId 向量的第 340 维置为 1,剩余的维度都为 0。

# all categorical features

# genre类别型特征转为one-hot特征,此处用到词表
categorical_columns = []
for feature, vocab in GENRE_FEATURES.items():
    cat_col = tf.feature_column.categorical_column_with_vocabulary_list(
        key=feature, vocabulary_list=vocab)
    emb_col = tf.feature_column.embedding_column(cat_col, 10)
    categorical_columns.append(emb_col)
    
# 用户id和电影id转为one-hot特征,此处不用词表
# movie id embedding feature
movie_col = tf.feature_column.categorical_column_with_identity(key='movieId', num_buckets=1001)
movie_emb_col = tf.feature_column.embedding_column(movie_col, 10)
categorical_columns.append(movie_emb_col)

# user id embedding feature
user_col = tf.feature_column.categorical_column_with_identity(key='userId', num_buckets=30001)
# 为了将得到的one-hot转为稠密向量,所以要加一层embedding
user_emb_col = tf.feature_column.embedding_column(user_col, 10)
categorical_columns.append(user_emb_col)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

为了把稀疏的 One-hot 特征转换成稠密的 Embedding 向量,还需要在 One-hot 特征外包裹一层 Embedding 层,可以看到 tf.feature_column.embedding_column(movie_col, 10) 方法完成了这样的操作,它在把 movie one-hot 向量映射到了一个 10 维的 Embedding 层上。

2.3 数值型特征的处理

直接把特征值输入到 MLP 内,然后把特征逐个声明为 tf.feature_column.numeric_column 就可以了:

# all numerical features
numerical_columns = [tf.feature_column.numeric_column('releaseYear'),
                     tf.feature_column.numeric_column('movieRatingCount'),
                     tf.feature_column.numeric_column('movieAvgRating'),
                     tf.feature_column.numeric_column('movieRatingStddev'),
                     tf.feature_column.numeric_column('userRatingCount'),
                     tf.feature_column.numeric_column('userAvgRating'),
                     tf.feature_column.numeric_column('userRatingStddev')]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.4 模型结构

直接利用 DenseFeatures 把类别型 Embedding 特征和数值型特征连接在一起形成稠密特征向量,然后依次经过两层 128 维的全连接层,最后通过 sigmoid 输出神经元产生最终预估值。

# embedding + MLP model architecture
model = tf.keras.Sequential([
    # 把embedding特征和数值型特征拼接起来形成稠密向量
    tf.keras.layers.DenseFeatures(numerical_columns + categorical_columns),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid'),
])

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

采用好评 / 差评标签作为样本标签,因此要解决的是一个类 CTR 预估的二分类问题,对于二分类问题,我们最后一层采用单个 sigmoid 神经元作为输出层就可以了。

2.5 定义模型相关的参数

需要设置模型的损失函数,梯度反向传播的优化方法,以及模型评估所用的指标。
损失函数:二分类问题最常用的二分类交叉熵;
优化方法:很流行的 adam;
评估指标:准确度 accuracy 作为模型评估的指标。

# compile the model, set loss function, optimizer and evaluation metrics
model.compile(
    loss='binary_crossentropy',
    optimizer='adam',
    metrics=['accuracy', tf.keras.metrics.AUC(curve='ROC'), tf.keras.metrics.AUC(curve='PR')])

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

2.6 模型的训练和评估

epochs,它代表了模型训练的轮数,一轮代表着使用所有训练数据训练一遍,epochs=10 代表着训练 10 遍。
mini-batch:外层for为训练周期,内层for迭代mini-batch。
1)epoch:将所有的训练样本都进行了一次前向传递和反向传播,是一个epoch。
2)Batch-size:每次训练所用的样本数量
3)Iteration:内层for执行的次数。
ex:共有10000个样本,batch-size=1000,即每次拿1000个样本进行训练,则Iteration=10次。

# train the model
model.fit(train_dataset, epochs=5)

# evaluate the model
test_loss, test_accuracy, test_roc_auc, test_pr_auc = model.evaluate(test_dataset)
print('\n\nTest Loss {}, Test Accuracy {}, Test ROC AUC {}, Test PR AUC {}'.format(test_loss, test_accuracy,
                                                                                   test_roc_auc, test_pr_auc))

# print some predict results
predictions = model.predict(test_dataset)
for prediction, goodRating in zip(predictions[:12], list(test_dataset)[0][1][:12]):
    print("Predicted good rating: {:.2%}".format(prediction[0]),
          " | Actual rating label: ",
          ("Good Rating" if bool(goodRating) else "Bad Rating"))

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

理论上来说,我们应该在模型 accuracy 不再变高时停止训练,据此来确定最佳的 epochs 取值。但如果模型收敛的时间确实过长,我们也可以设置一个 epochs 最大值,让模型提前终止训练。

三、作业

在我们实现的 Embedding+MLP 模型中,也有用户 Embedding 层和物品 Embedding 层。你觉得从这两个 Embedding 层中,抽取出来的用户和物品 Embedding,能直接用来计算用户和物品之间的相似度吗?为什么?

【答】无法直接计算相似度. user embedding 和 item embedding 虽然输入数据来源自同一个数据集,但是本身并不在一个向量空间内。

具体:项目里,用户embedding就是通过平均这个用户评论过的高分电影的embedding得到的。所以他们肯定是在一个向量空间里。只要是利用用户历史的item embedding生成的用户embedding,可以说都是在一个向量空间内,这些生成方式包括但不限于average pooling,sum pooling,attention等等。
【注意】但是如果用户 Embedding 和物品 Embedding 是分别独立生成的,或者说是通过一个模型中没有直接关系的两个 Embedidng 层生成的,那么它们就不在一个向量空间内了。注意啦,这个时候,我们不能直接求用户和物品之间的相似度,只能求用户 - 用户的相似度,和物品 - 物品的相似度。

四、课后答疑

(1)这几个模型改写为pytorch版本的代码,在进行category features的embedding的时候,是需要先将这些features变为one hot向量,再将这些one hot的向量变成embedding向量吗?但这样的话有些问题,这样数据大小就爆炸了,比如movie id 的features(19000个数据),从(19000,) -> (19000, 30001) -> (19000, 30001, 10)。

【答】按照embedding层的构造,是一定要把id转换成onehot的,否则id这个数字本身怎么能输入到网络中呢?
参数爆炸这个事情也是肯定的,这也是为什么说embedding layer是最费时,费空间的部分,一个神经网络有可能超过90%的参数和时间都花在训练embedding层上。

(2)对于数值型特征都有均值和标准差,比如电影评分均值和电影评分标准差两个特征。在实际工作中,对于数值型特征都会额外加上均值和标准差两个特征吗?

【答】不一定,关于特征选择,不要纠结于这样的问题,自己去尝试。

(3)通过学习code,推荐模型篇的Embedding都是通过tensorflow直接生成。请问在实际应用中,深度学习的模型的Embedding和线上服务篇生成的Embedding (Item2Vec, Graph Embedding)一般都是独立的吗?线上服务篇生成的用于Recall?模型篇生成的用于Sorting?

【答】模型篇中应该没有直接生成embedding,而是直接生成的预测分数,所以模型篇主要用于ranking,之前讲过的item2vec等方法生成的embedding,可以用于召回,当然也可以作为ranking的一部分特征。

(4)在划分训练集测试集,怎么就直接使用
test_dataset = raw_samples_data.take(1000)train_dataset = raw_samples_data.skip(1000)
这里的take和skip的意思是选1000条样本作为训练,然后选1000条样本作为测试集吗?
难道不需要按照时间划分吗?

【答】我们在设计特征的时候避免了特征穿越(也就是说当前t时刻的预测,考虑且仅考虑了t时刻以前的特征,这里随机划分应该也不为过。),所以随机划分的问题也不太大,但确实还是存在这一些样本乱序的问题,不能说完全避免了数据穿越。

按时间划分也有问题,比如一个用户的行为历史可能完全出现在样本集,或者完全出现在测试集,这样就很难预测了,因为模型没有包含任何这个用户的id型特征。

最好的方式其实是离线replay或者按用户行为序列的分用户时间切割。

(5)在实战中由于数据量庞大,用tf搭建的模型是否需要进行分布式训练?一般是如何分布式训练?
【答】tensorflow的分布式训练也是一个非常大的话题。涉及到parameter server模式的训练方式和分布式环境的部署。
推荐参考官方资料 https://www.tensorflow.org/guide/distributed_training

下一篇是用pytorch实现的版本,有很多细节要注意。

Reference

(1)《深度学习推荐系统实战》,王喆
(2)王喆大佬的github:https://github.com/wzhe06

文章来源: andyguo.blog.csdn.net,作者:山顶夕景,版权归原作者所有,如需转载,请联系作者。

原文链接:andyguo.blog.csdn.net/article/details/121094641

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。