背景

在CTR/CVR预估任务中,除了FM模型[2] 之外,后起之秀FFM(Field-aware Factorization Machine)模型同样表现亮眼。FFM可以看作是FM的升级版,Yuchi Juan于2016年提出该模型,但其诞生是受启于Rendle在2010年发表的另一个模型PITF [3](FM也是Rendle在2010年发表的),其论文原文 [1] 中写道:

The idea of FFM originates from PITF proposed for recommender systems with personalized tags.

在各种深度推荐模型出来之前,FM/FFM模型在各大推荐相关的竞赛中大放异彩。今天,我们就来好好梳理一下FFM的原理以及如何将理论转化为实践。

分析

1. FFM公式定义

相较于FM模型,FFM模型引入了域(Field)的概念(该想法来源于PITF [3]),可看做是对特征进行分组。例如,对于性别特征而言,通常有两种取值 \(female\) 、\(male\) 。对值进行one-hot编码之后性别特征会拆分为两个独立的特征 \(x_{female}\) 和 \(x_{male}\) 。显然,这两个特征具有共同的性质:都属于性别。所以可以将这两个特征归在同一个Field下,即有相同的Field编号。不同域的特征之间,往往具有明显的差异性。对比FM中的做法,每个特征有且仅有一个隐向量,在对特征 \(x_i\) 与其他特征进行交叉时,始终使用同一个隐向量 \(V_i\) 。 这种无差别式交叉方式,并没有考虑到不同特征之间的共性(同域)与差异性(异域)。

FFM公式化定义如下:

\[y = w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n\langle V_{i,f_j},V_{j,f_i}\rangle x_ix_j \tag{1}
\]

其中 \(f\) 为域(Field)映射函数,\(f_i\) 表示为 \(x_i\) 特征对应的Field编号。

将公式(1)对比FM可以发现,二者之间的差异仅在于二阶交叉对应的隐向量。设数据集中Field的数目为 \(F\) ,那么对于每个特征 \(x_i\) 拥有 \(F\) 个对应的隐向量,分别应用于与不同域特征进行交叉时。设隐向量维度为 \(k\) ,FFM二阶交叉项参数为 \(nkF\) 。

2. 求解

由于引入了Field,公式(1)不能像FM那样进行改写,所以FFM模型进行 推断 时的时间复杂度为 \(O(kn^2)\) 。

为了方便推导各参数的梯度,隐向量表示为 \(V_{i,f_j}=(v_{i,f_j}^1,v_{i,f_j}^3,\cdots,v_{i,f_j}^k)\) 。公式(1)展开:

\[y = w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n\sum_{q=1}^{k}v_{i,f_j}^{q}v_{j,f_i}^{q}x_ix_j \tag{2}
\]

当参数为 \(w_0\) 时,\(\frac{\partial{y}}{\partial{w_0}}=1\) 。

当参数为 \(w_i\) 时,\(\frac{\partial{y}}{\partial{w_i}}=x_i\) 。

当参数为 \(v_{i,f_j}^q\) 时,其他参数视为常量,只考虑公式(2)交叉项。由于Field数量以及映射关系 \(f\) 取决于数据集,这种情况参数梯度的数学统一表达式稍微复杂点(但当确定 \(f\) 之后很好计算),所以这里就暂且按下不表。

3. 性能评估

上述小节并未得到统一的参数梯度表达式,但估计模型训练时的时间复杂度,仍需评估更新 \(v_{i,f_j}^q\) 参数的时间复杂度。尽管没有梯度公式,但可以通过夹逼定理来确定该参数的更新时间复杂度。两种极端情况:1)\(F=1\) ;2)\(F=n\) ;参数更新时间复杂度位于二者之间。

1) \(F=1\) 时,所有特征均属于同一个Field,此时FFM退化为FM。可以将 \(f\) 暂时省略,公式(2)可以表示为

\[\begin{align}
y
={} & w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n\sum_{q=1}^{k}v_{i}^{q}v_{j}^{q}x_ix_j \notag \\
={} & w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{q=1}^{k}v_i^q\sum_{j=i+1}^{n}v_{j}^{q}x_ix_j \notag \\
\end{align} \tag{3}
\]

有,

\[\begin{align}
\frac{\partial{y}}{\partial{v_{i,f_j}^q}}
={} & \frac{\partial{y}}{\partial{v_{i}^q}} \notag \\
={} & \sum_{j=i+1}^{n}v_j^qx_ix_j \notag \\
\end{align} \tag{4}
\]

所以,更新参数 \(v_{i,f_j}^q\) 所需时间复杂度为 \(O(n)\)。这只是二阶项中 \(nkF\) 个参数中的其中一个,所以更新二阶项参数总时间复杂度为 \(O(kn^2)\) 。

2)\(F=n\) 时,每个特征的Field都不相同。不失一般性,可以设 \(f_i=i\) ,此时公式(2)可以表示为

\[\begin{align}
y
={} & w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n\sum_{q=1}^{k}v_{i,j}^{q}v_{j,i}^{q}x_ix_j \notag \\
\end{align} \tag{5}
\]

有,

\[\begin{align}
\frac{\partial{y}}{\partial{v_{i,f_j}^q}}
={} & \frac{\partial{y}}{\partial{v_{i,j}^q}} \notag \\
={} & v_{j,i}^qx_ix_j \notag \\
\end{align} \tag{4}
\]

所以,更新参数 \(v_{i,f_j}^q\) 所需时间复杂度为 \(O(1)\)。这只是二阶项中 \(nkF\) 个参数中的其中一个,所以更新二阶项参数总时间复杂度为 \(O(kn^2)\) 。

综上,更新二阶项参数所需时间复杂度为 \(O(kn^2)\) ,因为 \(w_0\) 与 \(w_i\) 参数更新时间复杂度为 \(O(1)\) ,所以FFM训练的时间复杂度为 \(O(kn^2)\) 。

总结:FFM 训练/推断 时间复杂度都为 \(O(kn^2)\) 。

4. 优缺点

优点:

  • 在高维稀疏性数据集中表现很好。

  • 相对FM模型精度更高,特征刻画更精细。

缺点:

  • 时间开销大。FFM时间复杂度为 \(O(kn^2)\) ,FM时间复杂度为 \(O(kn)\) 。
  • 参数多容易过拟合,必须设置正则化方法,以及早停的训练策略。

5. 注意事项

FFM对于数据集的要求 [1]:

  • 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.

1)含有类别特征的数据集,且需要对特征进行二值化处理。

2)越是稀疏的数据集表现效果相较于其他模型更优。

3)FFM比较难应用于纯数值类型的数据集。

数据预处理 [4]:

与FM一样,最好先进行特征归一化,再进行样本归一化。

超参数对于模型的影响 [1]:

首先需要注意的是,FFM的隐向量维度远小于FM的隐向量维度,即 \(k_{FFM} \ll k_{FM}\) 。

1)隐向量维度 \(k\) 对于模型的影响不大。

2)正则化系数 \(\lambda\) 如果太大,容易导致模型欠拟合,反之,容易过拟合。

3)在论文中,使用的是Adagrad优化器,全局学习率 \(\eta\) 也是超参数。如果 \(\eta\) 在一个较小的水平,则可以表现最佳。过大,容易导致过拟合。过小,容易欠拟合。

模型训练加速 [1,4]:

1)梯度分布计算;2)自适应学习率;3)多核并行计算;4)SSE3指令并行编程;

实验

与FM一致使用 \(MovieLens 100K\) 数据集,将评分大于3的样本置为正样本1,其他置为负样本0,构造一个二分类任务。使用 \(CrossEntropy\) 损失函数,最后使用了 \(Adam\) 优化算法。

论文中使用的 \(logistic loss\) 将样本构造为-1、1的二分类,同时使用的是 \(Adagrad\) 优化算法 [1]

核心代码如下:

class FFM(object):
def __init__(self, vec_dim, feat_num, field_num, lr, lamda):
self.vec_dim = vec_dim
self.feat_num = feat_num
self.field_num = field_num
self.lr = lr
self.lamda = lamda self._build_graph() def _build_graph(self):
self.add_input()
self.inference() def add_input(self):
self.x = tf.placeholder(tf.float32, shape=[None, self.feat_num], name='input_x')
self.y = tf.placeholder(tf.float32, shape=[None], name='input_y') def inference(self):
with tf.variable_scope('linear_part'):
w0 = tf.get_variable(name='bias', shape=[1], dtype=tf.float32)
self.W = tf.get_variable(name='linear_w', shape=[self.feat_num], dtype=tf.float32)
self.linear_part = w0 + tf.reduce_sum(tf.multiply(self.x, self.W), axis=1)
with tf.variable_scope('interaction_part'):
self.V = tf.get_variable(name='interaction_w', shape=[self.feat_num, self.field_num, self.vec_dim], dtype=tf.float32)
self.interaction_part = tf.constant(0, dtype=tf.float32)
for i in range(self.feat_num):
for j in range(i+1, self.feat_num):
self.interaction_part += \
tf.reduce_sum(tf.multiply(self.V[i, field_map[j]], self.V[j, field_map[i]])) * \
tf.multiply(self.x[:, i], self.x[:, j]) self.y_logits = self.linear_part + self.interaction_part
self.y_hat = tf.nn.sigmoid(self.y_logits)
self.pred_label = tf.cast(self.y_hat > 0.5, tf.int32)
self.loss = -tf.reduce_mean(self.y*tf.log(self.y_hat+1e-8) + (1-self.y)*tf.log(1-self.y_hat+1e-8))
self.reg_loss = self.lamda*(tf.reduce_mean(tf.nn.l2_loss(self.W)) + tf.reduce_mean(tf.nn.l2_loss(self.V)))
self.total_loss = self.loss + self.reg_loss self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.total_loss)

感想: FFM 训练速度真的很慢。

reference

[1] Juan, Yuchin, et al. "Field-aware factorization machines for CTR prediction." Proceedings of the 10th ACM Conference on Recommender Systems. ACM, 2016.

[2] Rendle, S. (2010, December). Factorization machines. In 2010 IEEE International Conference on Data Mining (pp. 995-1000). IEEE.

[3] Rendle, Steffen, and Lars Schmidt-Thieme. "Pairwise interaction tensor factorization for personalized tag recommendation." Proceedings of the third ACM international conference on Web search and data mining. ACM, 2010.

[4] https://tech.meituan.com/2016/03/03/deep-understanding-of-ffm-principles-and-practices.html

[5] https://www.jianshu.com/p/781cde3d5f3d

[6] https://zhuanlan.zhihu.com/p/38241764

[7] https://zhuanlan.zhihu.com/p/64113429

[8] https://zhuanlan.zhihu.com/p/50692817

[9] https://blog.csdn.net/leadai/article/details/81713800

知识分享

个人知乎专栏:https://zhuanlan.zhihu.com/c_1164954275573858304

欢迎关注微信公众号:SOTA Lab

专注知识分享,不定期更新计算机、金融类文章

推荐系统系列(二):FFM理论与实践的更多相关文章

  1. 通过autofac教你彻底明白依赖解耦(二)理论结合实践 - 大侠.Net

    上节说了一下基本的理论知识,例子可能不太好,不过无所谓了,目的是要让大家明白啥是依赖倒置和依赖注入,目的就达到了,简单一句话,这2玩意都是用来解耦合的. 不过依赖倒置这个词哥哥真不敢苟同,哥哥来个颠覆 ...

  2. 计算广告CTR预估系列(七)--Facebook经典模型LR+GBDT理论与实践

    计算广告CTR预估系列(七)--Facebook经典模型LR+GBDT理论与实践 2018年06月13日 16:38:11 轻春 阅读数 6004更多 分类专栏: 机器学习 机器学习荐货情报局   版 ...

  3. 个性化排序算法实践(二)——FFM算法

    场感知分解机(Field-aware Factorization Machine ,简称FFM)在FM的基础上进一步改进,在模型中引入类别的概念,即field.将同一个field的特征单独进行one- ...

  4. 推荐系统系列(四):PNN理论与实践

    背景 上一篇文章介绍了FNN [2],在FM的基础上引入了DNN对特征进行高阶组合提高模型表现.但FNN并不是完美的,针对FNN的缺点上交与UCL于2016年联合提出一种新的改进模型PNN(Produ ...

  5. 图机器学习(GML)&图神经网络(GNN)原理和代码实现(前置学习系列二)

    项目链接:https://aistudio.baidu.com/aistudio/projectdetail/4990947?contributionType=1 欢迎fork欢迎三连!文章篇幅有限, ...

  6. [知识库分享系列] 二、.NET(ASP.NET)

    最近时间又有了新的想法,当我用新的眼光在整理一些很老的知识库时,发现很多东西都已经过时,或者是很基础很零碎的知识点.如果分享出去大家不看倒好,更担心的是会误人子弟,但为了保证此系列的完整,还是选择分享 ...

  7. Java 理论与实践: 处理 InterruptedException(转)

    很多 Java™ 语言方法,例如 Thread.sleep() 和 Object.wait(),都可以抛出InterruptedException.您不能忽略这个异常,因为它是一个检查异常(check ...

  8. 高翔《视觉SLAM十四讲》从理论到实践

    目录 第1讲 前言:本书讲什么:如何使用本书: 第2讲 初始SLAM:引子-小萝卜的例子:经典视觉SLAM框架:SLAM问题的数学表述:实践-编程基础: 第3讲 三维空间刚体运动 旋转矩阵:实践-Ei ...

  9. C# 互操作性入门系列(二):使用平台调用调用Win32 函数

    好文章搬用工模式启动ing ..... { 文章中已经包含了原文链接 就不再次粘贴了 言明 改文章是一个系列,但只收录了2篇,原因是 够用了 } --------------------------- ...

随机推荐

  1. 在Windows平台上运行Tomcat

    从之前的学习中知道,可以调用Bootstrap类将Toomcat作为一个独立的应用程序来运行,在Windows平台上,可以调用startup.bat批处理文件来启动Tomcat,或运行shutdown ...

  2. Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Chinese_PRC_CI_AI" in the equal to operation.

    Executed as user: NT AUTHORITY\SYSTEM. Cannot resolve the collation conflict between "Chinese_P ...

  3. input type 为 number 时去掉上下小箭头

    <input type="number" ...> <style> input::-webkit-outer-spin-button, input::-we ...

  4. 用NetHttpClient执行Post操作遇到的问题

    最近在做接口的调试,用NetHttpClient来进行相关操作.部分数据是用get方法来操作的,没有问题,但有个数据是用Post来操作的,始终报错,用了抓包分析工具发现没有发送数据出去.但找不出代码的 ...

  5. 终端复用工具-tmux

    目录 终端复用工具--Tmux 一.为什么要用Tmux? 二.tmux是什么? 三.Tmux基本概念 四.Tmux使用规则 1.安装Tmux 2.基本使用 3.自定义配置文件 五.补充 1.tmux ...

  6. U盘被识别但不显示盘符怎么样才能解决?

    很多朋友在将U盘插入电脑后,会发现右下角的任务栏虽然出现了U盘的图标,但是在我的电脑中并没有显示出U盘的盘符,也就无法继续对U盘进行操作.遇到这种情况该怎么办呢?下面好系统U盘启动就告诉大家相应的解决 ...

  7. Delphi TIdUDPServer组件

  8. linux学习笔记七

    #文件权限很重要,有些时候删除和新建文件没有权限根本操作不了,linux一切皆是文件,所以必须得了解下权限了. 文件的一般权限 简单的ls -ld 命令就能看到权限,dr-xr-x---补全应该是dr ...

  9. 移动端css适配

    /* iphoneX.iphoneXs */ @media only screen and (device-width: 375px) and (device-height: 812px) and ( ...

  10. Python中的字符串及其相关操作

    1.表示: 字符串可以用单引号或者双引号括起来,两者效果是完全一样的. 针对较长的字符串,也可以用三个引号括起来,即"""..."""或者' ...