DeepFM模型理论和实践

2023-10-18 20:20
文章标签 实践 模型 理论 deepfm

本文主要是介绍DeepFM模型理论和实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、背景

特征组合的挑战
对于一个基于CTR预估的推荐系统,最重要的是学习到用户点击行为背后隐含的特征组合。在不同的推荐场景中,低阶组合特征或者高阶组合特征可能都会对最终的CTR产生影响。

之前介绍的因子分解机(Factorization Machines, FM)通过对于每一维特征的隐变量内积来提取特征组合。最终的结果也非常好。但是,虽然理论上来讲FM可以对高阶特征组合进行建模,但实际上因为计算复杂度的原因一般都只用到了二阶特征组合

那么对于高阶的特征组合来说,我们很自然的想法,通过多层的神经网络即DNN去解决。

DNN的局限
下面的图片来自于张俊林教授在AI大会上所使用的PPT。我们之前也介绍过了,对于离散特征的处理,我们使用的是将特征转换成为one-hot的形式,但是将One-hot类型的特征输入到DNN中,会导致网络参数太多:

如何解决这个问题呢,类似于FFM中的思想,将特征分为不同的field:

再加两层的全链接层,让Dense Vector进行组合,那么高阶特征的组合就出来了?但是全连接都是wx+b的形式,无法产生高阶特征啊?因为全连接层后面还跟着激活函数比如sigmoid/relu 等,给wx+b 的线性变换加入了非线性。比如原始特征是2维的(x1,x2), 那么在这个二维平面上所有的特征,都能w1x1+w2x2线性表示,那什么是非线性呐?比如x1*x2, x1^2,x1^3*x2 这些特征都是不能被(x1,x2) 线性表示的,所以称之为非线性特征。怎么实现非线性呐?答案就是在每一个全连接层的神经元中加入一个激活函数,将w1x1+w2x2 经过sigmoid函数之后得到的指数函数的形式就是非线性的,也就将得到了高阶特征,在隐藏层中同时包含了低阶特征和高阶组合特征。低阶特征不是被转化为指数函数形式的高阶特征了,为啥还有低阶特征,因为在DNN通常采用relu激活函数f(x)=max(0,x), 如果 w1x1+w2x2中的w2=0的情况下,f(x)不就等于x嘛(x>0)。

但是低阶和高阶特征组合隐含地体现在隐藏层中,如果我们希望把低阶特征组合单独建模,然后融合高阶特征组合。

即将DNN与FM进行一个合理的融合:

二者的融合总的来说有两种形式,一是串行结构,二是并行结构

而我们今天要讲到的DeepFM,就是并行结构中的一种典型代表。

2、DeepFM模型

我们先来看一下DeepFM的模型结构:

Ref:多目标排序模型在腾讯QQ看点推荐中的应用实践 - 知乎

上图中红色箭头所表示的链接权重恒定为1(weight-1 connection),在训练过程中不更新,可以认为是把节点的值直接拷贝到后一层,再参与后一层节点的运算操作。

DeepFM包含两部分:神经网络部分与因子分解机部分,分别负责低阶特征的提取和高阶特征的提取。这两部分共享同样的输入。DeepFM的预测结果可以写为:

FM部分

FM部分的详细结构如下,其中Inner Product 是做向量的內积。

FM部分是一个因子分解机。关于因子分解机可以参阅文章[Rendle, 2010] Steffen Rendle. Factorization machines. In ICDM, 2010.。因为引入了隐变量的原因,对于几乎不出现或者很少出现的隐变量,FM也可以很好的学习。

FM的输出公式为:

深度部分

深度部分是一个前馈神经网络。与图像或者语音这类输入不同,图像语音的输入一般是连续而且密集的,然而用于CTR的输入一般是及其稀疏的。因此需要重新设计网络结构。具体实现中为,在第一层隐含层之前,引入一个嵌入层来完成将输入向量压缩到低维稠密向量。

嵌入层(embedding layer)的结构如上图所示。当前网络结构有两个有趣的特性:

       1)尽管不同field的输入长度不同,但是embedding之后向量的长度均为K。

       2)在FM里得到的隐变量Vik现在作为了嵌入层网络的权重。

这里的第二点如何理解呢,假设我们的k=5,首先,对于输入的一条记录,同一个field 只有一个位置是1,那么在由输入得到dense vector的过程中,输入层只有一个神经元起作用,得到的dense vector其实就是输入层到embedding层该神经元相连的五条线的权重,即vi1,vi2,vi3,vi4,vi5。这五个值组合起来就是我们在FM中所提到的Vi。在FM部分和DNN部分,这一块是共享权重的,对同一个特征来说,得到的Vi是相同的。

有关模型具体如何操作,我们可以通过代码来进一步加深认识。

3、相关知识

我们先来讲两个代码中会用到的相关知识吧,代码是参考的github上星数最多的DeepFM实现代码。

Gini Normalization
代码中将CTR预估问题设定为一个二分类问题,绘制了Gini Normalization来评价不同模型的效果。这个是什么东西,不太懂,百度了很多,发现了一个比较通俗易懂的介绍。

假设我们有下面两组结果,分别表示预测值和实际值:

predictions = [0.9, 0.3, 0.8, 0.75, 0.65, 0.6, 0.78, 0.7, 0.05, 0.4, 0.4, 0.05, 0.5, 0.1, 0.1]
actual = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]

然后我们将预测值按照从小到大排列,并根据索引序对实际值进行排序:

Sorted Actual Values [0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1]

然后,我们可以画出如下的图片:

接下来我们将数据Normalization到0,1之间。并画出45度线。

橙色区域的面积,就是我们得到的Normalization的Gini系数。

这里,由于我们是将预测概率从小到大排的,所以我们希望实际值中的0尽可能出现在前面,因此Normalization的Gini系数越大,分类效果越好。

embedding_lookup
在tensorflow中有个embedding_lookup函数,我们可以直接根据一个序号来得到一个词或者一个特征的embedding值,那么他内部其实是包含一个网络结构的,如下图所示:

假设我们想要找到2的embedding值,这个值其实是输入层第二个神经元与embedding层连线的权重值。

之前有大佬跟我探讨word2vec输入的问题,现在也算是有个比较明确的答案,输入其实就是one-hot Embedding,而word2vec要学习的是new Embedding。

4、代码解析

好,一贯的风格,先来介绍几个地址:
原代码地址:https://github.com/ChenglongChen/tensorflow-DeepFM
本文代码地址:https://github.com/princewen/tensorflow_practice/tree/master/Basic-DeepFM-model
数据下载地址:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction

好了,话不多说,我们来看看代码目录吧,接下来,我们将主要对网络的构建进行介绍,而对数据的处理,流程的控制部分,相信大家根据代码就可以看懂。

项目结构
项目结构如下:

其实还应该有一个存放data的路径。config.py保存了我们模型的一些配置。DataReader对数据进行处理,得到模型可以使用的输入。DeepFM是我们构建的模型。main是项目的入口。metrics是计算normalized gini系数的代码。

模型输入

模型的输入主要有下面几个部分:

self.feat_index = tf.placeholder(tf.int32,shape=[None,None],name='feat_index')
self.feat_value = tf.placeholder(tf.float32,shape=[None,None],name='feat_value')self.label = tf.placeholder(tf.float32,shape=[None,1],name='label')
self.dropout_keep_fm = tf.placeholder(tf.float32,shape=[None],name='dropout_keep_fm')
self.dropout_keep_deep = tf.placeholder(tf.float32,shape=[None],name='dropout_deep_deep')

feat_index是特征的一个序号,主要用于通过embedding_lookup选择我们的embedding。feat_value是对应的特征值,如果是离散特征的话,就是1,如果不是离散特征的话,就保留原来的特征值。label是实际值。还定义了两个dropout来防止过拟合。

权重构建
权重的设定主要有两部分,第一部分是从输入到embedding中的权重,其实也就是我们的dense vector。另一部分就是深度神经网络每一层的权重。第二部分很好理解,我们主要来看看第一部分:

#embeddings
weights['feature_embeddings'] = tf.Variable(tf.random_normal([self.feature_size,self.embedding_size],0.0,0.01),name='feature_embeddings')
weights['feature_bias'] = tf.Variable(tf.random_normal([self.feature_size,1],0.0,1.0),name='feature_bias')

weights['feature_embeddings'] 存放的每一个值其实就是FM中的vik,所以它是F * K的。其中,F代表feture的大小(将离散特征转换成one-hot之后的特征总量),K代表dense vector的大小。

weights['feature_bias']是FM中的一次项的权重。

Embedding part
这个部分很简单啦,是根据feat_index选择对应的weights['feature_embeddings']中的embedding值,然后再与对应的feat_value相乘就可以了:

# model
self.embeddings = tf.nn.embedding_lookup(self.weights['feature_embeddings'],self.feat_index) # N * F * K
feat_value = tf.reshape(self.feat_value,shape=[-1,self.field_size,1])
self.embeddings = tf.multiply(self.embeddings,feat_value)

FM part
首先来回顾一下我们之前对FM的化简公式,之前去今日头条面试还问到过公式的推导。

注意这里\sum v_{i,f}x_{i} 是一个K维向量,平方是Multiply()是将数组/矩阵的对应元素相乘得到也是一个K维向量,\sum v_{i,f}^{2}x_{i}^{2}也是一个K维向量,做差之后得到一个K维向量,所以上式就变成了\sum_{f}^{K} (vector)对一个K维向量每个元素求和最终得到一个数。Ref:用Keras实现一个DeepFM_神经网络特征是离散数据 keras_蕉叉熵的博客-CSDN博客

所以我们的二次项可以根据化简公式轻松的得到,再加上我们的一次项,FM的part就算完了。同时更为方便的是,由于权重共享,我们这里可以直接用Embedding part计算出的embeddings来得到我们的二次项:

# first order term
self.y_first_order = tf.nn.embedding_lookup(self.weights['feature_bias'],self.feat_index)
self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order,feat_value),2)
self.y_first_order = tf.nn.dropout(self.y_first_order,self.dropout_keep_fm[0])# second order term
# sum-square-part
self.summed_features_emb = tf.reduce_sum(self.embeddings,1) # None * k
self.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K# squre-sum-part
self.squared_features_emb = tf.square(self.embeddings)
self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1)  # None * K#second order
self.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square,self.squared_sum_features_emb)
self.y_second_order = tf.nn.dropout(self.y_second_order,self.dropout_keep_fm[1])

DNN part
DNNpart的话,就是将Embedding part的输出再经过几层全链接层:

# Deep component
self.y_deep = tf.reshape(self.embeddings,shape=[-1,self.field_size * self.embedding_size])
self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[0])for i in range(0,len(self.deep_layers)):self.y_deep = tf.add(tf.matmul(self.y_deep,self.weights["layer_%d" %i]), self.weights["bias_%d"%I])self.y_deep = self.deep_layers_activation(self.y_deep)self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[i+1])

最后,我们要将DNN和FM两部分的输出进行结合:

concat_input = tf.concat([self.y_first_order, self.y_second_order, self.y_deep], axis=1)

损失及优化器
我们可以使用logloss(如果定义为分类问题),或者mse(如果定义为预测问题),以及多种的优化器去进行尝试,这些根据不同的参数设定得到:

# loss
if self.loss_type == "logloss":self.out = tf.nn.sigmoid(self.out)self.loss = tf.losses.log_loss(self.label, self.out)
elif self.loss_type == "mse":self.loss = tf.nn.l2_loss(tf.subtract(self.label, self.out))
# l2 regularization on weights
if self.l2_reg > 0:self.loss += tf.contrib.layers.l2_regularizer(self.l2_reg)(self.weights["concat_projection"])if self.use_deep:for i in range(len(self.deep_layers)):self.loss += tf.contrib.layers.l2_regularizer(self.l2_reg)(self.weights["layer_%d" % I])if self.optimizer_type == "adam":self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate, beta1=0.9, beta2=0.999,epsilon=1e-8).minimize(self.loss)
elif self.optimizer_type == "adagrad":self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate,initial_accumulator_value=1e-8).minimize(self.loss)
elif self.optimizer_type == "gd":self.optimizer = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate).minimize(self.loss)
elif self.optimizer_type == "momentum":self.optimizer = tf.train.MomentumOptimizer(learning_rate=self.learning_rate, momentum=0.95).minimize(self.loss)

参数初始化:

采用预训练的FM参数进行初始化。因为如果直接训练DeepFM,loss是从比较大的初始损失值开始优化的,收敛较慢,而先预训练FM,将loss降低到一定的值,再利用FM训练得到的权重参数初始化DeepFM的FM部分,训练Deep + FM得到更小的损失,收敛的速度比较快。

模型效果
前面提到了,我们用logloss作为损失函数去进行模型的参数更新,但是代码中输出了模型的 Normalization 的 Gini值来进行模型评价,我们可以对比一下(记住,Gini值越大越好呦):

好啦,本文只是提供一个引子,有关DeepFM更多的知识大家可以更多的进行学习呦。

问题:

(1).看到很多博客说DeepFM 不需要做额外的特征工程,不是太理解?

比如 交叉特征:用户对item类别的一二级类别点击率,在LR模型,XGB模型都要做,如果用DeepFM模型,这些交叉特征就不需要做了吗?

我的理解:DeepFM做的是隐性的特征交叉,是元素级别的;而XGB做的是显性的特征交叉向量级别;人工特征交叉做的是显性的特征交叉,虽然人工特征交叉可能与XGB做的显性的特征交叉有重复,但是XGB分裂时会选择重要的特征进行分裂,多余的特征也无影响;而DeepFM 也会学习到不同交叉特征的重要程度,体现在DeepFM的权重参数上,所以尽量多的构造对业务有提升的交叉特征 在XGB/DeepFM 中都非常有用。

5、模型调参

Ref: https://tech.meituan.com/2018/06/07/searchads-dnn.html

        影响神经网络的超参数非常多,神经网络调参也是一件非常重要的事情。工业界比较实用的调参方法包括:

        网格搜索/Grid Search:这是在机器学习模型调参时最常用到的方法,对每个超参数都敲定几个要尝试的候选值,形成一个网格,把所有超参数网格中的组合遍历一下尝试效果。简单暴力,如果能全部遍历的话,结果比较可靠。但是时间开销比较大,神经网络的场景下一般尝试不了太多的参数组合。
        随机搜索/Random Search:Bengio在“Random Search for Hyper-Parameter Optimization”10中指出,Random Search比Grid Search更有效。实际操作的时候,可以先用Grid Search的方法,得到所有候选参数,然后每次从中随机选择进行训练。这种方式的优点是因为采样,时间开销变小,但另一方面,也有可能会错过较优的超参数组合。
        分阶段调参:先进行初步范围搜索,然后根据好结果出现的地方,再缩小范围进行更精细的搜索。或者根据经验值固定住其他的超参数,有针对地实验其中一个超参数,逐次迭代直至完成所有超参数的选择。这个方式的优点是可以在优先尝试次数中,拿到效果较好的结果。
        我们在实际调参过程中,使用的是分阶段调参方式,在根据经验参数初始化超参数之后,按照隐层大小(先定义模型结构)--> Batch Size(数据分批) --> 学习率(开始学习、反向传播)--> Drop out/L1/L2 (正则化)的顺序进行参数调优

  • 1. 隐层的层数和大小: 两层 512  256  我们也尝试了3层,4层的隐层,提升效果不是很大,而且训练时间较长,可能导致梯度消失、梯度爆炸、以及过拟合。 隐层大小(512,256) 对比了 128-128,512-256,512-256-128,521-256-256-128  四种中选择。

增加隐层数可以降低网络误差,提高精度,但也使网络复杂化,从而增加了网络的训练时间和出现“过拟合”的倾向。

Ref:   https://segmentfault.com/q/1010000014818306  http://sofasofa.io/forum_main_post.php?postid=1000320

如何形象的理解隐层大小越大,效果越好 https://www.zhihu.com/question/65403482

  • 2. BatchSize :最后选择1000
  • 3. 学习率:最后选择0.001
  • 4. Dropout:神经元保留比例为0.8、
  • 5. L1 取值10-4; L2=10-4
  • 其他参数
    • 输出层激活函数 final_activation='sigmoid'
    • 隐层激活函数采用: activation='relu'
    • 损失函数呐 采用二元交叉熵损失
    • 优化器采用adam,
    • 优化指标 离线AUC、在线CTR。

 

网络常见的一些问题也可以通过超参的设置来解决:

  • 过拟合:网络宽度深度适当调小,正则化参数适当调大,Dropout Ratio适当调大等。
  • 欠拟合:网络宽度深度适当调大,正则化参数调小,学习率减小等。
  • 梯度消失/爆炸问题:合适的激活函数,添加Batch Normalization,网络宽度深度变小等。
  • 局部最优解:增大学习率,合适的优化器,减小Batch Size等。
  • Covariate Shift : 这个指的是训练集的数据分布和预测集的数据分布不一致,这样的情况下如果我们在训练集上训练出一个分类器,肯定在预测集上不会取得比较好的效果。深度学习网络在训练时容易受到输入层分布变化和前面层参数变化的影响,所以训练时需要用较低的学习率,且对参数初始化非常敏感。这一现象即为internal covariate shift。
    • 增加Batch Normalization,网络宽度深度变小等。BN负责调整特征分布回到原始的数据分布

参考资料

1、http://www.360doc.com/content/17/0315/10/10408243_637001469.shtml
2、https://blog.csdn.net/u010665216/article/details/78528261

3、推荐系统遇上深度学习(三)--DeepFM模型理论和实践 - 简书

这篇关于DeepFM模型理论和实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/234952

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识