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 tffrom tensorflow.keras.layers import Layerfrom tensorflow.keras.regularizers import l2class 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 = self.w0 + tf.reduce_sum(tf.nn.embedding_lookup(self.w, inputs), axis=1 ) second_order = 0 latent_vector = tf.reduce_sum(tf.nn.embedding_lookup(self.v, inputs), axis=1 ) 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