推荐系统(四)Field-aware Factorization Machines(FFM)
推荐系统(四)Field-aware Factorization Machines(FFM)
推荐系统系列博客:
- 推荐系统(一)推荐系统整体概览
- 推荐系统(二)GBDT+LR模型
- 推荐系统(三)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=1∑nwixi+i=1∑nj=i+1∑n<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=1∑nwixi+i=1∑nj=i+1∑n<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为nfknfknfk,kkk为embedding维度。因此,虽然FFM添加了field information后,相比较FM刻画的更加精细,由此也带来时间复杂度上升和过拟合问题,至于过拟合问题,论文中给出了两种解决办法:1. 添加正则项,2. 早停。
对于FFM的使用,作者在论文中也给出了建议:
- FFMs should be effective for data sets that contain categorical features and are transformed to binary features.
- If the transformed set is not sparse enough, FFMs seem to bring less benefit.
- It is more difficult to apply FFMs on numerical data sets.
翻译下也就是:
- 对于含有类别特征的数据集,需要对特征进行二值化处理,这样FFM才会比较有效
- 数据集越稀疏,FFM越有优势,也就是FFM在高维稀疏的数据集上表现比较好。
- 如果一个数据集只有连续值,则不适用于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)相关推荐
- 推荐系统学习笔记之四 Factorization Machines 因子分解机 + Field-aware Factorization Machine(FFM) 场感知分解机
前言 Factorization Machines(FM) 因子分解机是Steffen Rendle于2010年提出,而Field-aware Factorization Machine (FFM) ...
- 【推荐系统论文精读系列】(二)--Factorization Machines
文章目录 一.摘要 二.介绍 三.稀疏性下预测 四.分解机(FM) A. Factorization Machine Model B. Factorization Machines as Predic ...
- 保持函数依赖的模式分解可以减轻或解决什么_推荐系统玩家 之 因子分解机FM(Factorization Machines)...
前言 因子分解机 (Factorization Machines) 是CTR预估的重要模型之一.自2010年被提出后,得到了大规模的应该,也是美团和头条做推荐和CTR预估的方法之一.要讲述因子分解机F ...
- 【推荐算法】FM模型:Factorization Machines
1.线性回归 在介绍FM之前,我们先简单回顾以下线性回归. 回归分析是一种预测性的建模技术,它研究的是因变量(目标)和自变量(预测器)之间的关系.这种技术通常用于预测分析,时间序列模型以及发现变量之间 ...
- 因式分解机(Factorization Machines,FM )
FM优化(0):因式分解机(Factorization Machines,FM ) FM优化(1):基于地理因式分解法的POI推荐排序算法(Rank-GeoFM) 前言 FM背景 FM模型 FM算法详 ...
- Factorization Machines 学习笔记(二)模型方程
近期学习了一种叫做 Factorization Machines(简称 FM)的算法,它可对随意的实值向量进行预測.其主要长处包含: 1) 可用于高度稀疏数据场景:2) 具有线性的计算复杂度.本文将对 ...
- 论文阅读:(NFM)Neural Factorization Machines for Sparse Predictive Analytics
文章目录 摘要 一.Introduction 二.NFM基本原理 1.系统框架 1.Embedding Layer 2.Bi-Interaction Layer 3.Hidden Layer 4.Pr ...
- 【推荐系统】POLY2、FM、FFM模型的进化之路
文章目录 POLY2模型 FM模型 FFM模型 POLY2模型 逻辑回归模型是CTR预测领域较为广泛使用的模型,原因有模型具备可解释性,良好的数学解释性,可以实现并行化提高效率,但是它有个缺点就是不能 ...
- Factorization Machines 因式分解机 论文学习笔记
Abstracta 因式分解机(Factorization Machines)是一种结合了支持向量机(Support Vector Machines, SVM)的模型.与SVM不同的是,FM 使用对变 ...
最新文章
- 送书啦!40本经典书籍任你挑!
- 【干货】网易云音乐歌单的推荐算法解析
- vsc 搜索特定代码_特定问题的通用解决方案:何时编写代码以及何时编写代码...
- np中meshgrid生成二维矩阵matplotlib中imshow生成图形
- js+css淡入效果
- 向量化计算cell_吴恩达老师课程笔记系列第24节-Octave教程之向量化和作业(6)
- 微信号承载私域流量的9条心得
- 19天津大学计算机考研群,19天津大学金融专硕431经验贴
- IOCP的Demo及说明
- Remove Duplicates from Sorted Array
- 地质图、地质岩性数据、地质灾害分布、土壤理化性质数据集、土地利用数据、土壤重金属含量分布、植被类型分布
- Axure RP最新授权码
- 计算机一直显示配置更新开不了机怎么办,电脑开机出现配置更新怎么办
- Android之布局详解
- 计算力学——有限元编程实现
- 批量创建钱包地址并保存私钥
- Linux笔记本电脑大调查:程序员最喜欢的电脑是什么配置?
- KSImageNamed 安装后无效解决方法 亲测有用
- 计算机网络_学习笔记 索引
- Java implement意思_详解JAVA中implement和extends的区别