FM(Factorization Machines)即因子分解机,在计算广告和推荐系统中,CTR(click-through rate)是非常重要的一个环节,判断一个物品是否进行推荐需要根据CTR预估的点击率排序决定。在进行CTR预估时,除了单特征外,往往要对特征进行组合,常用方法有:

  • 人工特征 + LR
  • GBDT + LR
  • FM

    1. 特征组合

    FM是在LR的基础上,增加特征组合项得到的多项式模型。二阶多项式模型表达式如下:
    FM - 图1
    其中FM - 图2代表样本的特征数量,FM - 图3是第FM - 图4个特征的值,FM - 图5是模型参数。可看出组合特征的参数共有FM - 图6个,任意两个参数都是独立的。
    但面对稀疏性强的数据,二次项参数的训练是很困难的:二次项参数FM - 图7的训练需要大量FM - 图8同时非零的样本,否则梯度为0无法进行更新。但由于样本本就是稀疏数据,则满足FM - 图9同时非零的数据非常少,训练样本不足,参数FM - 图10不准确,严重影响模型的性能。

    2. 矩阵分解

    所有二次项参数FM - 图11可以组成一个对称矩阵FM - 图12(任意FM - 图13实对称矩阵都有N个线性无关的特征向量),则对该对称矩阵进行分解:
    FM - 图14
    即:
    截屏2021-01-13 下午7.50.38.png
    其中FM - 图16就是第FM - 图17个特征的隐向量,FM - 图18,特征分量FM - 图19的交叉项系数等于两隐向量的内积,即FM - 图20,这就是FM模型的核心思想。此时FM模型为:
    FM - 图21
    此时FM模型的复杂度为FM - 图22,可通过变换,将FM的二次项进行花剑,复杂度优化为FM - 图23,优化方法如下:
    截屏2021-01-13 下午8.02.58.png

    3. TensorFlow实现FMLayer

    1. class FM_Layer(Layer):
    2. def __init__(self, feature_columns, k, w_reg, v_reg):
    3. super().__init__()
    4. self.feature_columns = feature_columns
    5. self.index_map = []
    6. self.feature_num = 0
    7. for fea in self.feature_columns:
    8. self.index_map.append(self.feature_num)
    9. self.feature_num += fea['feat_num']
    10. self.k = k
    11. self.w_reg = w_reg
    12. self.v_reg = v_reg
    13. def build(self, input_shape):
    14. self.w0 = self.add_weight(name='w0',
    15. shape=(1,),
    16. trainable=True)
    17. self.w = self.add_weight(name='w',
    18. shape=(self.feature_num, 1),
    19. regularizer=l2(self.w_reg),
    20. trainable=True)
    21. self.v = self.add_weight(name='v',
    22. shape=(self.feature_num, self.k),
    23. regularizer=l2(self.v_reg),
    24. trainable=True)
    25. def call(self, inputs, **kwargs):
    26. '''
    27. 数据预处理方式:
    28. sparse feature: label encode
    29. dense feature: 离散分箱
    30. '''
    31. inputs = inputs + tf.convert_to_tensor(self.index_map)
    32. # first order
    33. first_order = self.w0 + tf.reduce_sum(tf.nn.embedding_lookup(self.w, inputs), axis=1) # (batch, 1)
    34. # second order
    35. embedding_inputs = tf.nn.embedding_lookup(self.v, inputs) # (batch, input_length, embed_dim)
    36. square_sum = tf.square(tf.reduce_sum(embedding_inputs, axis=1)) # (batch, embed_dim)
    37. sum_square = tf.reduce_sum(tf.square(embedding_inputs), axis=1) # (batch, embed_dim)
    38. second_order = 0.5 * tf.reduce_sum(square_sum - sum_square, axis=1, keepdims=True) # (batch, 1)
    39. return first_order + second_order

    4. 梯度求解

    采用随机梯度下降法SGD求解参数如下:
    截屏2021-01-13 下午8.12.16.png