Recommendation-System-7-FFM

Introduction

Field-aware Factorization Machine (FFM)是由Criteo Research 的YuChin Juan 在2016年发表到ACM的预测CTR的推荐算法,它这里引入了Field场的概念到M里面

Motivation

在Field-aware FM 里面, 它的目的是通过引入field场的概念来将之前的 PITF(pairwise intereaction tensor factorization)和FM的特征交叉的方式更加generalize。 和PITF相比,FFM使得特征交叉是不限于User, Tag, Item这三种,而是更多不同的fields的embedding之间的交叉。

Field-Factorization Machine (FFM)

Field

在说明FFM的原理之前,先举个例子说明一下field场的概念。一般来说, 推荐系统里面的数据会有大量的稀疏特征,而在处理的时候会用OneHot encoding把类别特征变成0, 1 向量,encoding之后每个binary的值xi就相对于一个特征值。 而一个field场就是把同一类的特征值进行拼在一起。 这里举个例子。如果原来的特征有Publisher, Advertiser, Gender 三类别特征个,而其中Publisher有{ESPN, Vogue, NBC}三个特征值, Advertiser有{Nike, Gucci, Addidas} 三个特征值, Gender 有{Male, Female}两个特征值. 那么我们把这些类别特征OneHot Encoding 变成0,1 特征之后,{ESPN, Vogue, NBC} 3个binary 特征属于Publisher一类, {Nike, Gucci, Addidas}3个binary特征属于Advertiser一类,而{Male, Female} 2个binary特征属于Gender类,那么我们可以把Publisher, Advertiser, Gender 3个类看成是3 个fields.

Field 1: Publisher Field 2: Advertiser Field 3: Gender
ESPN Vogue NBC Nike Gucci Addidas Male Female

Field-aware Factorization Machine

在FM那样每个稀疏特征只对应一个embedding,比如在Advertiser里面Nike对应一个embedding,Addidas对应一个embedding。那么如果输入特征是Publisher = ESPN, Advertiser=Nike, Gender=Male, 那么FM的特征交叉 $\Phi _{FM} = w _{ESPN} \cdot w _{Nike} + w _{ESPN} \cdot w _{Male} + w _{Male} \cdot w _{Nike}$。 是每个embedding 向量 w 两两相互点乘相加。

在FM 里面,由于每个特征只有一个embedding,使得一个embedding会用于计算不同的特征之间的潜在影响关系。 比如 $w _{ESPN}$ 用于计算$w _{ESPN} \cdot w _{Nike}, w _{ESPN} \cdot w _{Male}$ 会同时影响到 (ESPN, Nike)和 (ESPN, Male) 两组交叉特征的隐性信息。而不同于FM的是, FFM在引入field的概念后假设Nike, Male这两个特征是属于不同的field的,每两组特征交叉的隐性向量应该相互独立,即像(ESPN, Nike)交叉向量和 (ESPN, Male) 交叉向量要用不同的$w _{ESPN}$进行计算, 这样才能使交叉后的隐性特征之间是相互独立的

因此,在FFM里面,每个特征都有多个embedding, 每个embedding对应一个field。 比如我现在有3个fields: Publisher, Advertiser, Gender, 那么我Nike这个特征就会有3个embedding vectors,分别是$w _{Nike, Publisher}, w _{Nike, Advertiser}, w _{Nike, Gender}$
而FFM的二阶特征交叉的公式变成:

而它要优化的目标函数是

Comparison with FM, DeepFM

  • 在FM和DeepFm里面,每个feature都只有一个embedding,而FFM里面每个feature的Embedding的个数等于field的个数
  • Space Complexity里面FM是 nk, 而FFM是nfk,乘上了field的个数。而Computation Complexity里面, FM是O(nk)而FFM是O(n^2k)

Source Code

这里参考DeepCTR里面的FFM源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import tensorflow as tf
from tensorflow.keras.layers import Layer
from tensorflow.keras.regularizers import l2


class FFM_Layer(Layer):
def __init__(self, sparse_feature_columns, k, w_reg=1e-6, v_reg=1e-6):
"""
:param dense_feature_columns: A list. sparse column feature information.
:param k: A scalar. The latent vector
:param w_reg: A scalar. The regularization coefficient of parameter w
:param v_reg: A scalar. The regularization coefficient of parameter v
"""
super(FFM_Layer, self).__init__()
self.sparse_feature_columns = sparse_feature_columns
self.k = k
self.w_reg = w_reg
self.v_reg = v_reg
self.index_mapping = []
self.feature_length = 0
for feat in self.sparse_feature_columns:
self.index_mapping.append(self.feature_length)
self.feature_length += feat['feat_num']
self.field_num = len(self.sparse_feature_columns)

def build(self, input_shape):
self.w0 = self.add_weight(name='w0', shape=(1,),
initializer=tf.zeros_initializer(),
trainable=True)
self.w = self.add_weight(name='w', shape=(self.feature_length, 1),
initializer='random_normal',
regularizer=l2(self.w_reg),
trainable=True)
self.v = self.add_weight(name='v',
shape=(self.feature_length, self.field_num, self.k),
initializer='random_normal',
regularizer=l2(self.v_reg),
trainable=True)

def call(self, inputs, **kwargs):
inputs = inputs + tf.convert_to_tensor(self.index_mapping)
# first order
first_order = self.w0 + tf.reduce_sum(tf.nn.embedding_lookup(self.w, inputs), axis=1) # (batch_size, 1)
# field second order
second_order = 0
latent_vector = tf.reduce_sum(tf.nn.embedding_lookup(self.v, inputs), axis=1) # (batch_size, field_num, k)
for i in range(self.field_num):
for j in range(i+1, self.field_num):
second_order += tf.reduce_sum(latent_vector[:, i] * latent_vector[:, j], axis=1, keepdims=True)
return first_order + second_order


class FFM(Model):
def __init__(self, feature_columns, k, w_reg=1e-6, v_reg=1e-6):
"""
FFM architecture
:param feature_columns: A list. sparse column feature information.
:param k: the latent vector
:param w_reg: the regularization coefficient of parameter w
:param field_reg_reg: the regularization coefficient of parameter v
"""
super(FFM, self).__init__()
self.sparse_feature_columns = feature_columns
self.ffm = FFM_Layer(self.sparse_feature_columns, k, w_reg, v_reg)

def call(self, inputs, **kwargs):
ffm_out = self.ffm(inputs)
outputs = tf.nn.sigmoid(ffm_out)
return outputs

def summary(self, **kwargs):
sparse_inputs = Input(shape=(len(self.sparse_feature_columns),), dtype=tf.int32)
tf.keras.Model(inputs=sparse_inputs, outputs=self.call(sparse_inputs)).summary()

Reference

[1] Paper: https://www.csie.ntu.edu.tw/~cjlin/papers/ffm.pdf
[2] Github: https://github.com/shenweichen/DeepCTR/tree/9f155590cc44c14821dcb691811656eb2ef2f49b

Comments