学习总结
(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
了,然后根据你自己训练数据的本地路径,替换代码中的路径。
利用 TensorFlow 自带的 CSV 数据集的接口载入训练数据(如下代码)。
两个比较重要的参数:
(1) label_name
,它指定了 CSV 数据集中的标签列。
(2) batch_size
,它指定了训练过程中,一次输入几条训练数据进行梯度下降训练。
载入训练数据之后,我们把它们分割成了测试集和训练集。
这里可以回顾下:
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
、userId
和 movieId
。
注意:类别型特征 Embedding 化,数值型特征直接输入 MLP。
在载入 genre
类特征时,我们采用了 tf.feature_column.categorical_column_with_vocabulary_list
方法把字符串型的特征转换成了 One-hot 特征。在这个转换过程中我们需要用到一个词表,开头就定义好了包含所有 genre 类别的词表 genre_vocab
(如上面代码)。
在转换 userId
和 movieId
特征时,我们又使用了 tf.feature_column.categorical_column_with_identity
方法把 ID 转换成 One-hot 特征,这个方法不用词表,它会直接把 ID 值对应的那个维度置为 1。比如,我们输入这个方法的 movieId 是 340,总的 movie 数量是 1001,使用这个方法,就会把这个 1001 维的 One-hot movieId 向量的第 340 维置为 1,剩余的维度都为 0。
为了把稀疏的 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
就可以了:
2.4 模型结构
直接利用 DenseFeatures
把类别型 Embedding 特征和数值型特征连接在一起形成稠密特征向量,然后依次经过两层 128 维的全连接层,最后通过 sigmoid 输出神经元产生最终预估值。
采用好评 / 差评标签作为样本标签,因此要解决的是一个类 CTR 预估的二分类问题,对于二分类问题,我们最后一层采用单个 sigmoid 神经元作为输出层就可以了。
2.5 定义模型相关的参数
需要设置模型的损失函数,梯度反向传播的优化方法,以及模型评估所用的指标。
损失函数:二分类问题最常用的二分类交叉熵;
优化方法:很流行的 adam;
评估指标:准确度 accuracy 作为模型评估的指标。
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次。
理论上来说,我们应该在模型 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