通过引入field的概念,FFM把相同性质的特征归于同一个field,相当于把FM中已经细分的feature再次进行拆分从而进行特征组合的二分类模型。
在传统的线性模型中,每个特征都是独立的,如果需要考虑特征与特征之间的相互作用,可能需要人工对特征进行交叉组合。非线性SVM可以对特征进行核变换,但是在特征高度稀疏的情况下,并不能很好的进行学习。由于推荐系统是一个高度系数的数据场景,由此产生了FM系列算法,包括FM,FFM,DeepFM等算法。
和FM算法一样,FFM主要应用在推荐算法中的CTR点击率预估(排序)问题,推荐系统一般可以分成两个模块,召回和排序。比如对于电影推荐,召回模块会针对用户生成一个推荐电影列表,而排序模块则负责对这个电影列表根据用户的兴趣做排序。当把FFM算法应用到推荐系统中时,具体地是应用在排序模块。
FFM模型结构
线性部分大家都很熟悉,非线性部分是FFM的关键所在,并且与同样有非线性部分的FM算法相比,FFM引入了field的概念。因此交叉项与FM算法的<vi , vj>不同,这里的是<vi,fj , vj,fi>
以下图网上流传广泛的图为例:
其中,红色部分对应的是Field,来自于原始特征的个数; 蓝色部分对应的是feature,来自于原始特征onehot之后的个数。(连续型特征不用one-hot) 对于特征Feature:User=YuChin,有Movie=3Idiots、Genre=Comedy、Genre=Drama、Price四项要进行交叉组合:
绿色部分为对应特征one-hot之后的值,出现为1,不出现为0。对于连续型变量的处理,这里采用的是使用实际值,当然,也可以对连续型变量离散化处理,再进行one-hot。
这里我们的FFM算法是基于Tensorflow实现的。
为什么用Tensorflow呢?观察二次项,由于field的引入,Vffm需要计算的参数有 nfk 个,远多于FM模型的 nk个,而且由于每次计算都依赖于乘以的xj的field,所以,无法用fm的计算技巧(ab = 1/2(a+b)^2-a^2-b^2),所以计算复杂度是 O(n^2)。
因此使用Tensorflow的目的是想通过GPU进行计算。同时这也给我们提供了一个思路:如果模型的计算复杂度较高,当不能使用CPU快速完成模型训练时,可以考虑使用GPU计算。比如Xgboost是已经封装好可以用在GPU上的算法库,而那些没有GPU版本的封装算法库时,例如我们此次采用的FFM算法,我们可以借助Tensorflow的GPU版本框架设计算法,并完成模型训练。
代码主要分三部分:
build_data.py
主要是完成对原始数据的转化。主要包括构造特征值对应field的字典。
FFM.py
主要包括线性部分及非线性部分的代码实现。
tools.py
主要包括训练集的构造。
这里我们主要分析 FFM.py,也就是模型的构建过程:
首先初始化一些参数,包括:
代码如下图所示:
然后,构造了一个model类,主要存放:
代码如下图所示:
之后,对模型构造部分代码进行分析,可发现模型由两部分组成,第一部分是下图红框内容,其实就是线性表达式 w^Tx+b,其中:
定义变量及初始化后,就可以构造线性模型,代码如下图所示:
然后,定义一个Vffm变量用来存放交叉项的权重,并初始化。因为我们已经了解到Vffm是一个三维向量,所以,v :shape(p,f,k) 。
之后是vi,fj、vj,fi的构造。因为v 有p行,代表共有p个特征值,所以vifj = v[i, feature2field[j]],说人话就是第i个特征值在第j个特征值对应的field上的隐向量。
vjfi 的构造方法类似,所以vivj就可以求出来.然后就是把交叉项累加,然后 reshape 成(batch_size,1)的形状,以便与线性模型进行矩阵加法计算。
代码如下图所示:
下图描述了所有交叉项的隐向量Vffm所处的位置。假设p=16、f=3、k=6,16个项所属的field为{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1, 7: 1, 8: 1, 9: 1, 10: 2, 11: 2, 12: 2, 13: 2, 14: 2, 15: 2},共有120个交叉项,vifj和vjfi隐向量分别各有120个。其中:
vifj隐向量: {(3, 0), (11, 2), (2, 1), (6, 2), (5, 1), (7, 2), (4, 0), (1, 2), (12, 2), (8, 1), (2, 2), (4, 1), (1, 1), (3, 2), (0, 0), (13, 2), (8, 2), (7, 1), (4, 2), (1, 0), (14, 2), (0, 1), (9, 2), (6, 1), (3, 1), (2, 0), (5, 2), (0, 2), (10, 2)},共29个不同的隐向量。 vjfi隐向量: {(12, 1), (9, 1), (3, 0), (11, 2), (8, 0), (15, 1), (14, 0), (4, 0), (12, 2), (9, 0), (8, 1), (15, 0), (14, 1), (11, 1), (5, 0), (10, 0), (13, 2), (7, 1), (6, 0), (11, 0), (10, 1), (1, 0), (14, 2), (13, 1), (7, 0), (15, 2), (12, 0), (2, 0), (13, 0)},共29个不同的隐向量。
而vifj和vjfi相加起来共有48个各不相同的隐向量。也就是说,下图展示的每一个隐向量都有用,即都能在模型中找到,且很多交叉项的隐向量一样。
举个栗子:
v1,15代表着第1个特征值在第15个特征值对应的field(field=2)上的隐向量。即v1,15 = v[1, feature2field[15]] = Vffm[1,2] ,其中v1,15的shape = (1,6)。
最后,就是loss的定义。这里要注意我们之前在构造训练集时已经把输出的(0,1)分类转化为(-1,1)分类。
核心加重点是关注loss的计算公式,此时的计算公式是-1/1做二分类的时候常用的loss计算方法。
[1] 深入FFM原理与实践
[3] 推荐算法之FFM:原理及实现简介