本次要总结分享的是 推荐/CTR 领域内著名的deepfm[1] 论文,参考的代码tensorflow-DeepFM[2],该论文方法较为简单,实现起来也比较容易,该方法在工业界十分常用。

「建议在非深色主题下阅读本文,pc端阅读点击文末左下角“原文链接”,体验更佳」

@

  • 论文动机及创新点

  • 模型结构

    • 输入数据

    • FM 部分

    • Deep 部分

  • 代码分析

    • 数据预处理

    • 定义 DeepFM 模型超参数

    • 构图

  • 总结

论文动机及创新点

  • 在 deepfm 提出之前,现有的模型很难很好的提取低阶和高阶的交互特征,或者需要足够丰富的人工特征工程才能进行。

  • 一些特性交互很容易理解,可以由专家(对业务逻辑很了解的人)设计。然而,大多数其他的特征交互都隐藏在数据中,难以识别先验信息(例如,经典的关联规则“尿布和啤酒”是从数据中挖掘出来的,而不是由专家发现),这只能通过机器学习自动获取。即使对于易于理解的交互特征,专家似乎也不太可能对它们进行穷尽式的建模,尤其是当特征数量很大的时候。

所以对于许多数据挖掘类比赛,特征工程的工作量几乎占到工作量的 95%以上,大部分甚至一些优秀选手,首先一股脑的把所能想到的特征都使用上,然后根据效果做些适当特征选择。当选取的特征效果的确很好时,把构建这些特征的思路包装成某一个听起来很高逼格的”方法论“。

  • 很容易想到,有没有什么办法,能让模型能端到端的进行特征学习呢?从而避免繁杂的人工特征工程过程。deepfm 论文里就是基于这一动机,将 fm 模型和 DNN 模型联合起来进行训练,其中 fm 模型可能捕捉到一些低阶的交互特征,而 DNN 模型捕捉一些高阶模型。该联合模型可以进行端到端的训练学习。

  • Deepfm 模型中的 Deep 部分和 fm 部分共享 embedding,极大减少了需要学习的参数,使其训练过程很有效率。

  • 和以往的 CTR 模型相比,Deepfm 模型效果最好。

模型结构

在这里插入图片描述

上图为 DeepFM 的网络结构图,由左边的 FM 模型和右边的 DNN 模型组成,两个子模型共享下方的输入 embedding。

输入数据

假定有




个样本,每个样本由








组成,其中
























  • 个特征组成,其中包含了 「类别」型特征 和 「数值」型特征组成,每个特征可理解为一个








其中 类别 型特征可用 one-hot 编码表示,数值型特征用其本身数值或者离散化在 one-hot 表示

  • 这里定义













    表示特征个数;















    表示 (数值特征个数+类别特征「取值」个数);




    表示

















类别特征 one-hot 后向量长度即为取值个数

FM 部分

这里定义两个参数矩阵

  • feature_bias:shape 为



















    的一阶参数矩阵

  • feature_embeddings:shape 为



















    的二阶参数矩阵

  • <> 表示内积不得不说:这篇论文里面的网络图都画的好丑

上式中 第一项<w,x> 表示提取一阶特征,第二项表示提取二阶交叉特征;每个样本在类别型 特征上只有一个取值。

  • d 表示













    ,第二项是在不同 field 之间做二阶交叉特征计算。

  • 对于一阶特征中的参数




    表示从 feature_bias 参数矩阵中 lookup 得到一个参数,样本中每个特征都能得到一个对应向量(长度为 1)。

  • 二阶特征中,












    分别表示






















    的隐向量,可以从 feature_embeddings 参数矩阵中 lookup 得到(长度为 k)。

    在这里插入图片描述

这里可以理解将数值特征也embedding成向量,一个数值特征只对应一个embedding向量,而一个类别特征的不同取值则对应不同向量,但向量长度均为k,对应论文里说:即使不同的field长度不一样(one-hot向量长度不一样),但是都能embedding成度相同的向量。

二阶交叉特征推导:

上式中,




表示





















表示隐向量从 feature*embeddings (shape 为[feature_size,k]) 参数矩阵中 lookup 得到的参数,那么对于第













,其得到的







shape 为





&#xff0c;




,因此











表示












个分量,对于类别型特征,







非 0 即 1。

由上面分析可知,每个输入特征都有对应的







参数向量对应。

Deep 部分

这部分更容易理解了,就是个 DNN 网络,模型输入为上图中的 Dense_Embeddings:


















































































注意:FM 与 Deep 部分共享输入的 embedding feature,也就是他们共同影响 Dense_Embeddings。

代码分析

这部分参考的是 tensorflow-DeepFM[3]

数据预处理

该部分对数据集中特征进行了编号,一个连续特征用一个编号,类别特征不同取值用不同编号

def gen_feat_dict(self):if self.dfTrain is None:dfTrain = pd.read_csv(self.trainfile)else:dfTrain = self.dfTrainif self.dfTest is None:dfTest = pd.read_csv(self.testfile)else:dfTest = self.dfTestdf = pd.concat([dfTrain, dfTest])self.feat_dict = {}tc = 0for col in df.columns:if col in self.ignore_cols:continueif col in self.numeric_cols:# map to a single indexself.feat_dict[col] = tctc += 1else:us = df[col].unique()self.feat_dict[col] = dict(zip(us, range(tc, len(us)+tc)))tc += len(us)self.feat_dim = tc

由上述代码可以看出 feat_dim 就是我们前面定义的 feature_size

 def parse(self, infile=None, df=None, has_label=False):assert not ((infile is None) and (df is None)), "infile or df at least one is set"assert not ((infile is not None) and (df is not None)), "only one can be set"if infile is None:dfi = df.copy()else:dfi = pd.read_csv(infile)if has_label:y = dfi["target"].values.tolist()dfi.drop(["id", "target"], axis=1, inplace=True)else:ids = dfi["id"].values.tolist()dfi.drop(["id"], axis=1, inplace=True)# dfi for feature index# dfv for feature value which can be either binary (1/0) or float (e.g., 10.24)dfv = dfi.copy()for col in dfi.columns:if col in self.feat_dict.ignore_cols:dfi.drop(col, axis=1, inplace=True)dfv.drop(col, axis=1, inplace=True)continueif col in self.feat_dict.numeric_cols:dfi[col] = self.feat_dict.feat_dict[col]else:dfi[col] = dfi[col].map(self.feat_dict.feat_dict[col])dfv[col] = 1.# list of list of feature indices of each sample in the datasetXi = dfi.values.tolist()# list of list of feature values of each sample in the datasetXv = dfv.values.tolist()if has_label:return Xi, Xv, yelse:return Xi, Xv, ids

由上面代码可以看出:dfi 表示特征的编号,对于一个类别特征,不同取值其编号不同;dfv 表示该特征值,对于数值型特征就是该值本身,类别特征全是 1(表示取到了该编号的类别值)。

定义 DeepFM 模型超参数

class DeepFM(BaseEstimator, TransformerMixin):def __init__(self, feature_size, field_size,embedding_size=8, dropout_fm=[1.0, 1.0],deep_layers=[32, 32], dropout_deep=[0.5, 0.5, 0.5],deep_layers_activation=tf.nn.relu,epoch=10, batch_size=256,learning_rate=0.001, optimizer_type="adam",batch_norm=0, batch_norm_decay=0.995,verbose=False, random_seed=2016,use_fm=True, use_deep=True,loss_type="logloss", eval_metric=roc_auc_score,l2_reg=0.0, greater_is_better=True):assert (use_fm or use_deep)assert loss_type in ["logloss", "mse"], \"loss_type can be either 'logloss' for classification task or 'mse' for regression task"self.feature_size = feature_size        # M=数值型特征个数+类别型特征取值个数,就是feat_dimself.field_size = field_size            # F=特征个数self.embedding_size = embedding_size    # K=embedding_sizeself.dropout_fm = dropout_fmself.deep_layers = deep_layersself.dropout_deep = dropout_deepself.deep_layers_activation = deep_layers_activationself.use_fm = use_fmself.use_deep = use_deepself.l2_reg = l2_regself.epoch = epochself.batch_size = batch_sizeself.learning_rate = learning_rateself.optimizer_type = optimizer_typeself.batch_norm = batch_normself.batch_norm_decay = batch_norm_decayself.verbose = verboseself.random_seed = random_seedself.loss_type = loss_typeself.eval_metric = eval_metricself.greater_is_better = greater_is_betterself.train_result, self.valid_result = [], []self._init_graph()

构图

feature_bias:shape 为



















的一阶参数矩阵 feature_embeddings:shape 为



















的二阶参数矩阵

def _init_graph(self):self.graph = tf.Graph()with self.graph.as_default():tf.set_random_seed(self.random_seed)self.feat_index = tf.placeholder(tf.int32, shape=[None, None],name="feat_index")  # None * Fself.feat_value = tf.placeholder(tf.float32, shape=[None, None],name="feat_value")  # None * Fself.label = tf.placeholder(tf.float32, shape=[None, 1], name="label")  # None * 1self.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_keep_deep")self.train_phase = tf.placeholder(tf.bool, name="train_phase")self.weights = self._initialize_weights()# modelself.embeddings = tf.nn.embedding_lookup(self.weights["feature_embeddings"],self.feat_index)  # None * F * Kfeat_value = tf.reshape(self.feat_value, shape=[-1, self.field_size, 1])self.embeddings = tf.multiply(self.embeddings, feat_value) ## 供下面的FM和Deep部分使用# ---------- first order term ----------self.y_first_order = tf.nn.embedding_lookup(self.weights["feature_bias"], self.feat_index) # None * F * 1self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order, feat_value), 2)  # None * Fself.y_first_order = tf.nn.dropout(self.y_first_order, self.dropout_keep_fm[0]) # None * F# ---------- second order term ---------------# sum_square partself.summed_features_emb = tf.reduce_sum(self.embeddings, 1)  # None * Kself.summed_features_emb_square = tf.square(self.summed_features_emb)  # None * K# square_sum partself.squared_features_emb = tf.square(self.embeddings)self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1)  # None * K# second orderself.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square, self.squared_sum_features_emb)  # None * Kself.y_second_order = tf.nn.dropout(self.y_second_order, self.dropout_keep_fm[1])  # None * K# ---------- Deep component ----------self.y_deep = tf.reshape(self.embeddings, shape=[-1, self.field_size * self.embedding_size]) # None * (F*K)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]) # None * layer[i] * 1if self.batch_norm:self.y_deep = self.batch_norm_layer(self.y_deep, train_phase=self.train_phase, scope_bn="bn_%d" %i) # None * layer[i] * 1self.y_deep = self.deep_layers_activation(self.y_deep)self.y_deep = tf.nn.dropout(self.y_deep, self.dropout_keep_deep[1+i]) # dropout at each Deep layer# ---------- DeepFM ----------if self.use_fm and self.use_deep:concat_input = tf.concat([self.y_first_order, self.y_second_order, self.y_deep], axis=1)elif self.use_fm:concat_input = tf.concat([self.y_first_order, self.y_second_order], axis=1)elif self.use_deep:concat_input = self.y_deepself.out = tf.add(tf.matmul(concat_input, self.weights["concat_projection"]), self.weights["concat_bias"])

这里需要注意:本实现代码中,「也对连续特征也直接做了 embedding」,用的时候也可以把连续特征改为 deep 侧的直接输入,另外针对多值离散特征这里也没有处理。

总结

  • 该方法将 FM(捕捉低级特征)和 Deep(捕捉高级特征)进行端到端的联合训练,并且共享输入 embedding,这是现在非常常见的做法。

  • 论文讲到该方法可以一定程度避免人工特征工程,从模型看的确做到了无脑交叉,模型自动学习各种交叉的权重。

Reference

[1]

deepfm: https://arxiv.org/abs/1703.04247

[2]

tensorflow-DeepFM: https://github.com/ChenglongChen/tensorflow-DeepFM

[3]

tensorflow-DeepFM: https://github.com/ChenglongChen/tensorflow-DeepFM


往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑
获取本站知识星球优惠券,复制链接直接打开:
https://t.zsxq.com/qFiUFMV
本站qq群704220115。加入微信群请扫码:

【论文解读】DeepFM论文总结相关推荐

  1. 【SSD论文解读】 论文中用到的数据集:Pascal VOC、MS COCO、ImageNet

    [SSD论文解读] 论文中用到的数据集 一.数据集介绍 1.Pascal VOC 2.MS COCO 3.ILSVRC 二.数据集处理函数 1.专门的datasets类 2.论文中的数据集处理函数 三 ...

  2. Resnet论文解读与TensorFlow代码分析

    残差网络Resnet论文解读 1.论文解读 博客地址:https://blog.csdn.net/loveliuzz/article/details/79117397 2.理解ResNet结构与Ten ...

  3. 目标检测学习笔记2——ResNet残差网络学习、ResNet论文解读

    ResNet残差网络学习.ResNet论文解读 一.前言 为什么会提出ResNet? 什么是网络退化现象? 那网络退化现象是什么造成的呢? ResNet要如何解决退化问题? 二.残差模块 三.残差模块 ...

  4. Semantic Segmentation--SegNet:A Deep Convolutional Encoder-Decoder Architecture..论文解读

    title: Semantic Segmentation–SegNet:A Deep Convolutional Encoder-Decoder Architecture-论文解读 tags: Obj ...

  5. 自监督学习(Self-Supervised Learning)多篇论文解读(下)

    自监督学习(Self-Supervised Learning)多篇论文解读(下) 之前的研究思路主要是设计各种各样的pretext任务,比如patch相对位置预测.旋转预测.灰度图片上色.视频帧排序等 ...

  6. 自监督学习(Self-Supervised Learning)多篇论文解读(上)

    自监督学习(Self-Supervised Learning)多篇论文解读(上) 前言 Supervised deep learning由于需要大量标注信息,同时之前大量的研究已经解决了许多问题.所以 ...

  7. 可视化反投射:坍塌尺寸的概率恢复:ICCV9论文解读

    可视化反投射:坍塌尺寸的概率恢复:ICCV9论文解读 Visual Deprojection: Probabilistic Recovery of Collapsed Dimensions 论文链接: ...

  8. 从单一图像中提取文档图像:ICCV2019论文解读

    从单一图像中提取文档图像:ICCV2019论文解读 DewarpNet: Single-Image Document Unwarping With Stacked 3D and 2D Regressi ...

  9. 点云配准的端到端深度神经网络:ICCV2019论文解读

    点云配准的端到端深度神经网络:ICCV2019论文解读 DeepVCP: An End-to-End Deep Neural Network for Point Cloud Registration ...

最新文章

  1. 初步学习TypeScript
  2. 内存分析工具MAT的使用
  3. Android的一些布局小知识点
  4. PHP中__get()和__set()的用法实例详
  5. fir滤波器算法c语言程序,FIR滤波器设计C语言程序
  6. 对象在内存中的存储布局
  7. 恒生估值系统_恒生指数和恒生国企指数投资价值分析
  8. 关于“服务器提交了协议冲突. Section=ResponseStatusLine问题
  9. 一点一点学写Makefile-1
  10. 机器学习——数学基础1,方差平方差标准差均方误差均方根误差
  11. vmware虚拟机网络设置详解
  12. 关于互联网之技术总监工作的职责职能比较好的文章收集
  13. 华为交换机端口配置流量统计
  14. C#中public 、private、protect的区别
  15. 工具 | Doxygen的使用详解
  16. 央行:加强金融业网络安全和信息化统筹指导,筑牢金融网络安全屏障
  17. 网站诊断分析-网站诊断分析软件-网站SEO诊断分析软件
  18. 关于网站中图片排名优化的技巧是?
  19. 学计算机的鼓励,鼓励学习的经典语句
  20. 谷歌分布式计算框架MapReduce论文2004 中文翻译

热门文章

  1. [blog摘要]Exploring and Decoding ETW Providers using Event Log Channels
  2. Android 系统自带 Theme(主题)
  3. 静态初始化块的执行顺序
  4. C#多线程学习之(五)使用定时器进行多线程的自动管理
  5. 纪伯伦:我曾七次鄙视我的灵魂
  6. 树莓派slam_SLAM+语音机器人DIY系列:(五)树莓派3开发环境搭建——6.树莓派USB与tty串口号绑定...
  7. my.ini修改后服务无法启动_VisualSVN Server 自助修改密码页面
  8. 临床、实验室和流行病学研究的样本量 Sample Sizes for Clinical, Laboratory and Epidemiology Studies
  9. 中药免疫肿瘤学数据库,TCMIO使用指南
  10. java比python难_python java JAVA比python难多少?