推荐系统(四)Field-aware Factorization Machines(FFM)

推荐系统系列博客:

  1. 推荐系统(一)推荐系统整体概览
  2. 推荐系统(二)GBDT+LR模型
  3. 推荐系统(三)Factorization Machines(FM)

上一篇博客介绍了FM模型,这篇博客来介绍下FM模型的改进版FFM模型,看名字也能窥探一二,FFM模型相比较FM模型改进点在于“F”,这个F指的是Field-aware,作者也在论文中提到这个idea受rendle大佬的PITF文章[文献2]启发。这篇文章的核心思想点理解起来可能会比较绕,因为这篇博客重点的笔墨都会放在这个idea的理解上。在看这篇博客之前,请务必已经对FM模型非常熟悉,关于FM模型可以参考我的上一篇博客:推荐系统(三)Factorization Machines(FM)。

为了方便下面对FFM核心思想的讲解,我们先来造个数据集,假设我们有个数据集如下:

性别 年龄 教育水平 label
45 初中 0
25 研究生 1

基于上面这个数据集,我们来对比下FM和FFM,这样大家看起来也一目了然。

FM思想: 为每个特征取值训练一个embedding向量(比如性别=男),这样在做特征二价交叉的时候(比如性别和年龄的交叉,男#45)只需要分别拿出“男”的embedding向量和“45”的embedding向量,然后做个向量内积就可以了。这样做的好处就是即使交叉的两个特征从来没在数据集中出现过,也能得到embedding向量,从而学到对应的参数。

FFM思想: 我们考虑两个二阶交叉特征:[男#45、男#初中]。在FM模型中,男#45=embedding(男) ∗* embedding(45)男#初中=embedding(男) ∗* embedding(初中),能够发现这两个交叉特征中,因为都有“性别=男”这个特征,在计算这两个交叉特征时,对于“性别=男”这个特征的embedding向量也是用的一样的,都是embedding(男)。 而FFM认为 [男#45、男#初中] 虽然都是“男”,但因为一个是和年龄交叉,一个是和教育水平交叉,因此如果如果用相同的embedding(男)向量是会导致信息有损。 因此FFM引入了field的概念,即对于“性别=男”这个特征取值,不再只有一个embedding向量(FM中只有一个),而是有fff个(这个fff是field的个数,比如上面这个数据集有[性别,年龄,教育水平]三个field的,因此f=3f=3f=3),扩展来说,对于每个特征取值都不再只有一个embedding向量,都是有fff个。举个例子,还是交叉特征[男#45],因为男的filed是性别,45的field是年龄,因此,男#45=embedding(男,年龄) ∗* embedding(45,性别) 。上面这个公式一定要理解,这是整个FFM的核心,并且对于field是双向的,也就是不能只有embedding(男,年龄),还要有embedding(45,性别)。
注:对于[性别,年龄,教育水平] 我们通常称呼为特征,为了和论文中称呼统一,在这篇博客里,我们称为field。

讲完上面这个核心思想,我们再来看看FM和FFM形式化公式的区别:

  • FM
    y^(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1n<vi,vj>xi,xj(1)\hat{y}(x) = w_0 + \sum_{i=1}^{n}w_ix_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n}<v_i,v_j>x_i,x_j \tag{1} y^(x)=w0+i=1nwixi+i=1nj=i+1n<vi,vj>xi,xj(1)
  • FFM
    y^(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1n<vi,fj,vj,fi>xi,xj(2)\hat{y}(x) = w_0 + \sum_{i=1}^{n}w_ix_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n}<v_{i,f_j},v_{j,f_i}>x_i,x_j \tag{2} y^(x)=w0+i=1nwixi+i=1nj=i+1n<vi,fj,vj,fi>xi,xj(2)

从公式2和公式1的对比中,也能发现,FFM相比较FM,仅在二阶交叉部分引入了field信息(实际上就是个side information)。从时间复杂度上来看,FM的时间复杂度可以简化至O(kn)O(kn)O(kn),而FFM时间复杂度O(kn2)O(kn^2)O(kn2),这也是FFM在工业界用的比较少的原因。从二阶交叉项系数个数来看,FM为nknknk,而FFM为nfknfknfkkkk为embedding维度。因此,虽然FFM添加了field information后,相比较FM刻画的更加精细,由此也带来时间复杂度上升和过拟合问题,至于过拟合问题,论文中给出了两种解决办法:1. 添加正则项,2. 早停。

对于FFM的使用,作者在论文中也给出了建议:

  1. FFMs should be effective for data sets that contain categorical features and are transformed to binary features.
  2. If the transformed set is not sparse enough, FFMs seem to bring less benefit.
  3. It is more difficult to apply FFMs on numerical data sets.

翻译下也就是:

  1. 对于含有类别特征的数据集,需要对特征进行二值化处理,这样FFM才会比较有效
  2. 数据集越稀疏,FFM越有优势,也就是FFM在高维稀疏的数据集上表现比较好。
  3. 如果一个数据集只有连续值,则不适用于FFM。

最后来看看实现,作者给出了一个C++版本:LIBFFM,这里就不多介绍,有兴趣的可以自己看看。paddlepaddle官方也给出了paddle版本的实现 ffm,基于的数据集是Display Advertising Challenge所用的Criteo数据集,这个数据集共有13个连续值特征,26个类别型特征。我们一起来看下paddle的实现,官方的代码并没有一些维度的注释,这让人看起来着实难受,我这里给加了一些维度的注释:

class FFM(nn.Layer):def __init__(self, sparse_feature_number, sparse_feature_dim,dense_feature_dim, sparse_num_field):super(FFM, self).__init__()self.sparse_feature_number = sparse_feature_numberself.sparse_feature_dim = sparse_feature_dimself.dense_feature_dim = dense_feature_dimself.dense_emb_dim = self.sparse_feature_dim  # 9self.sparse_num_field = sparse_num_fieldself.init_value_ = 0.1# sparse part coding# [1000001, 1]self.embedding_one = paddle.nn.Embedding(sparse_feature_number,  # 10000011,sparse=True,weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.TruncatedNormal(mean=0.0,std=self.init_value_ /math.sqrt(float(self.sparse_feature_dim)))))# [1000001, 9*39]self.embedding = paddle.nn.Embedding(self.sparse_feature_number,self.sparse_feature_dim * self.sparse_num_field,sparse=True,weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.TruncatedNormal(mean=0.0,std=self.init_value_ /math.sqrt(float(self.sparse_feature_dim)))))# dense part coding w# shape(13,)# Tensor(shape=[13], dtype=float32, place=CPUPlace, stop_gradient=False,#        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])self.dense_w_one = paddle.create_parameter(shape=[self.dense_feature_dim],dtype='float32',default_initializer=paddle.nn.initializer.Constant(value=1.0))# shape(1, 13, 9*39)self.dense_w = paddle.create_parameter(shape=[1, self.dense_feature_dim,self.dense_emb_dim * self.sparse_num_field  # 13, 9*39],dtype='float32',default_initializer=paddle.nn.initializer.Constant(value=1.0))def forward(self, sparse_inputs, dense_inputs):"""one sample example:[array([0]), array([737395]), array([210498]), array([903564]), array([286224]), array([286835]),array([906818]), array([906116]), array([67180]), array([27346]), array([51086]), array([142177]),array([95024]), array([157883]), array([873363]), array([600281]), array([812592]), array([228085]),array([35900]), array([880474]), array([984402]), array([100885]), array([26235]), array([410878]),array([798162]), array([499868]), array([306163]),array([0. , 0.00497512, 0.05 , 0.08 , 0.20742187, 0.028, 0.35 , 0.08 , 0.082 , 0.,0.4  , 0.  , 0.08  ], dtype=float32)]:param sparse_inputs:  list[array], 26 len[array([0]), array([737395]), array([210498]), array([903564]), array([286224]), array([286835]),array([906818]), array([906116]), array([67180]), array([27346]), array([51086]), array([142177]),array([95024]), array([157883]), array([873363]), array([600281]), array([812592]), array([228085]),array([35900]), array([880474]), array([984402]), array([100885]), array([26235]), array([410878]),array([798162]), array([499868]), array([306163]),:param dense_inputs:  list 13 lenarray([0.        , 0.00497512, 0.05      , 0.08      , 0.20742187,0.028     , 0.35      , 0.08      , 0.082     , 0.        ,0.4       , 0.        , 0.08      ]:return:"""# -------------------- first order term  --------------------# sparse_inputs, list, length 26, [Tensor(shape=[2, 1]),...,]#  [[[737395],[715353]],...] feature_name* batch_size*1# sparse_inputs_concat, Tensor(shape=[2, 26]) ---> batch_size=2, shape[batch_size, 26]# [[737395, 210498, 903564, 286224, 286835, 906818, 906116, 67180 , 27346 , 51086 ,# 142177, 95024 , 157883, 873363, 600281, 812592, 228085, 35900 , 880474, 984402,# 100885, 26235 , 410878, 798162, 499868, 306163],[]]sparse_inputs_concat = paddle.concat(sparse_inputs, axis=1)# shape=[batch_size, 26, 1]# [[[-0.00620287],#          [-0.01724204],#          [-0.02544647],#          [ 0.01982319],#          [-0.03302126],#          [ 0.00377966],...,], [[],..[]]]sparse_emb_one = self.embedding_one(sparse_inputs_concat)# dense_inputs: shape=[batch_size, 13]# dense_w_one: shape=[13]# 点乘# Tensor(shape=[2, 13], dtype=float32, place=CPUPlace, stop_gradient=False,# [[0., 0.00497512, 0.05000000, 0.08000000, 0.20742187, 0.02800000, 0.34999999,# 0.08000000, 0.08200000, 0., 0.40000001, 0., 0.08000000],# [0., 0.93200666, 0.02000000, 0.14000000, 0.03956250, 0.32800001, 0.98000002,# 0.12000000, 1.88600004, 0. , 1.79999995, 0., 0.14000000]]))dense_emb_one = paddle.multiply(dense_inputs, self.dense_w_one)  # shape=[batch_size, 13]# shape=[batch_size, 13, 1]# [[       [0.        ],#          [0.00497512],#          [0.05000000],#          [0.08000000],#          [0.20742187],#          [0.02800000],#          [0.34999999],#          [0.08000000],#          [0.08200000],#          [0.        ],#          [0.40000001],#          [0.        ],#          [0.08000000]],##         [[0.        ],#          [0.93200666],#          [0.02000000],#          [0.14000000],#          [0.03956250],#          [0.32800001],#          [0.98000002],#          [0.12000000],#          [1.88600004],#          [0.        ],#          [1.79999995],#          [0.        ],#          [0.14000000]]]dense_emb_one = paddle.unsqueeze(dense_emb_one, axis=2)  # shape=[batch_size, 13, 1]# paddle.sum(sparse_emb_one, 1) --->shape=[2, 1], [[-0.13885814],[-0.21163476]]# paddle.sum(dense_emb_one, 1)  --->shape=[2, 1], [[-0.13885814], [-0.21163476]]y_first_order = paddle.sum(sparse_emb_one, 1) + paddle.sum(dense_emb_one, 1)  # [batch_size, 1]# -------------------Field-aware second order term  --------------------# shape=[batch_size, 26, 351]sparse_embeddings = self.embedding(sparse_inputs_concat)# shape=[batch_size, 13, 1],  batch_size=2dense_inputs_re = paddle.unsqueeze(dense_inputs, axis=2)# shape=[batch_size, 13, 351]print("==========dense_inputs_re========", dense_inputs_re)print("=============dense_w============", self.dense_w)dense_embeddings = paddle.multiply(dense_inputs_re, self.dense_w)  # [2,13,1]*[1,13,351]=[2,13,351]print("=============dense_embeddings============", dense_embeddings)# shape=[batch_size, 39, 351]feat_embeddings = paddle.concat([sparse_embeddings, dense_embeddings], 1)# shape=[batch_size, 39, 39, 9]field_aware_feat_embedding = paddle.reshape(feat_embeddings,shape=[-1, self.sparse_num_field, self.sparse_num_field, self.sparse_feature_dim])field_aware_interaction_list = []for i in range(self.sparse_num_field):  # 39个特征,26个离散值特征+13个连续值特征for j in range(i + 1, self.sparse_num_field):  # 39field_aware_interaction_list.append(# sum后维度shape=[2, 1],# [# [0.00212428],# [0.00286741]# ]# 对应着FFM二阶部分,embedding(x_i, f_j) * embedding(x_j, f_i)paddle.sum(field_aware_feat_embedding[:, i, j, :] *  # shape=[2, 9], 对应元素相乘field_aware_feat_embedding[:, j, i, :], 1, keepdim=True))# shape=[2, 1]y_field_aware_second_order = paddle.add_n(field_aware_interaction_list)return y_first_order, y_field_aware_second_order

参考文献
[1]: Juan Y, Zhuang Y, Chin W S, et al. Field-aware factorization machines for CTR prediction[C]//Proceedings of the 10th ACM conference on recommender systems. 2016: 43-50.
[2]: Rendle S, Schmidt-Thieme L. Pairwise interaction tensor factorization for personalized tag recommendation[C]//Proceedings of the third ACM international conference on Web search and data mining. 2010: 81-90.
[3]: Jahrer, Michael, et al. “Ensemble of collaborative filtering and feature engineered models for click through rate prediction.” KDDCup workshop. 2012.

推荐系统(四)Field-aware Factorization Machines(FFM)相关推荐

  1. 推荐系统学习笔记之四 Factorization Machines 因子分解机 + Field-aware Factorization Machine(FFM) 场感知分解机

    前言 Factorization Machines(FM) 因子分解机是Steffen Rendle于2010年提出,而Field-aware Factorization Machine (FFM) ...

  2. 【推荐系统论文精读系列】(二)--Factorization Machines

    文章目录 一.摘要 二.介绍 三.稀疏性下预测 四.分解机(FM) A. Factorization Machine Model B. Factorization Machines as Predic ...

  3. 保持函数依赖的模式分解可以减轻或解决什么_推荐系统玩家 之 因子分解机FM(Factorization Machines)...

    前言 因子分解机 (Factorization Machines) 是CTR预估的重要模型之一.自2010年被提出后,得到了大规模的应该,也是美团和头条做推荐和CTR预估的方法之一.要讲述因子分解机F ...

  4. 【推荐算法】FM模型:Factorization Machines

    1.线性回归 在介绍FM之前,我们先简单回顾以下线性回归. 回归分析是一种预测性的建模技术,它研究的是因变量(目标)和自变量(预测器)之间的关系.这种技术通常用于预测分析,时间序列模型以及发现变量之间 ...

  5. 因式分解机(Factorization Machines,FM )

    FM优化(0):因式分解机(Factorization Machines,FM ) FM优化(1):基于地理因式分解法的POI推荐排序算法(Rank-GeoFM) 前言 FM背景 FM模型 FM算法详 ...

  6. Factorization Machines 学习笔记(二)模型方程

    近期学习了一种叫做 Factorization Machines(简称 FM)的算法,它可对随意的实值向量进行预測.其主要长处包含: 1) 可用于高度稀疏数据场景:2) 具有线性的计算复杂度.本文将对 ...

  7. 论文阅读:(NFM)Neural Factorization Machines for Sparse Predictive Analytics

    文章目录 摘要 一.Introduction 二.NFM基本原理 1.系统框架 1.Embedding Layer 2.Bi-Interaction Layer 3.Hidden Layer 4.Pr ...

  8. 【推荐系统】POLY2、FM、FFM模型的进化之路

    文章目录 POLY2模型 FM模型 FFM模型 POLY2模型 逻辑回归模型是CTR预测领域较为广泛使用的模型,原因有模型具备可解释性,良好的数学解释性,可以实现并行化提高效率,但是它有个缺点就是不能 ...

  9. Factorization Machines 因式分解机 论文学习笔记

    Abstracta 因式分解机(Factorization Machines)是一种结合了支持向量机(Support Vector Machines, SVM)的模型.与SVM不同的是,FM 使用对变 ...

最新文章

  1. 送书啦!40本经典书籍任你挑!
  2. 【干货】网易云音乐歌单的推荐算法解析
  3. vsc 搜索特定代码_特定问题的通用解决方案:何时编写代码以及何时编写代码...
  4. np中meshgrid生成二维矩阵matplotlib中imshow生成图形
  5. js+css淡入效果
  6. 向量化计算cell_吴恩达老师课程笔记系列第24节-Octave教程之向量化和作业(6)
  7. 微信号承载私域流量的9条心得
  8. 19天津大学计算机考研群,19天津大学金融专硕431经验贴
  9. IOCP的Demo及说明
  10. Remove Duplicates from Sorted Array
  11. 地质图、地质岩性数据、地质灾害分布、土壤理化性质数据集、土地利用数据、土壤重金属含量分布、植被类型分布
  12. Axure RP最新授权码
  13. 计算机一直显示配置更新开不了机怎么办,电脑开机出现配置更新怎么办
  14. Android之布局详解
  15. 计算力学——有限元编程实现
  16. 批量创建钱包地址并保存私钥
  17. Linux笔记本电脑大调查:程序员最喜欢的电脑是什么配置?
  18. KSImageNamed 安装后无效解决方法 亲测有用
  19. 计算机网络_学习笔记 索引
  20. Java implement意思_详解JAVA中implement和extends的区别

热门文章

  1. select下拉框 笔记
  2. AE赛博朋克 超炫酷赛博朋克HUD元素动画AE特效模板素材
  3. Linux——alias命令(设置命令别名)
  4. java中set的遍历_java中遍历set集合,java中set怎么遍历?
  5. 第一章 1.2掌握Deluxe APP的剪辑功能
  6. c语言布尔变量的输出方法,关于布尔变量的用法(新手)
  7. java 对象序列化工具类
  8. 堆排序算法回顾---(韩顺平数据结构)笔记
  9. 本地开发代理、解决跨域、虚拟域名
  10. odoo忘记管理员密码的处理办法