初学推荐系统-04-FM (因子分解机:多特征的二阶特征交叉)

926 阅读3分钟

5 FM模型的引入

5.1.1 逻辑回归模型的及其缺点

FM模型其实就是一种思路,具体应用较少。
一般来说做推荐CTR预估时最简单的思路是将特征做线性组合(逻辑回归LR),传入sigmod中得到一个概率值,本质上是一个线性模型;也就是LR的缺点有:

  1. 这是一个线性模型
  2. 每个特征对最终输出的结果独立,需要手动进行特征交叉(xi*xj),比较麻烦。

5.1.2 改进,以及FM的引入

由于LR模型的上述缺陷(主要是手动做特征交叉比较麻烦),干脆就考虑所有的二阶交叉项,也就是将目标函数由原来的

y=w0+i=1nwixiy = w_0+\sum_{i=1}^nw_ix_i

改为

y=w0+i=1nwixi+i=1n1i+1nwijxixjy = w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{i+1}^nw_{ij}x_ix_j

但这个式子有一个问题,只有当xix_ixjx_j均不为0时这个二阶交叉项才会生效,后面这个特征交叉项本质是和多项式核SVM等价的.

FM模型使用了如下的优化函数,事实上做的唯一改动就是把wijw_{ij}替换成了<vi,vj>\lt v_i,v_j\gt,这实际上就有深度学习的意味在里面了,实质上就是给每个xix_i计算一个embedding,然后将两个向量之间的embedding做内积得到之前所谓的wijw_{ij}好处就是这个模型泛化能力强 ,即使两个特征之前从未在训练集中同时出现,我们也不至于像之前一样训练不出wijw_{ij},事实上只需要xix_i和其他的xkx_k同时出现过就可以计算出xix_i的embedding:

y=w0+i=1nwixi+i=1ni+1n<vi,vj>xixjy = w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n}\sum_{i+1}^n\lt v_i,v_j\gt x_ix_j

说明:

  1. ω0\omega_{0}为全局偏置;
  2. ωi\omega_{i}是模型第i个变量的权重;
  3. ωij=<vi,vj>\omega_{ij} = < v_{i}, v_{j}> 特征i和j的交叉权重;
  4. viv_{i} 是第i维特征的隐向量;
  5. <,> <\cdot, \cdot> 代表向量点积;
  6. k(k<<n)k(k<<n) 为隐向量的长度,包含 k 个描述特征的因子。
  7. embedding 有词嵌入的意思,它的其中一个作用是将稀疏向量变成了稠密的向量。

5.3. FM模型的应用

最直接的想法就是直接把FM得到的结果放进sigmoid中输出一个概率值,由此做CTR预估,事实上我们也可以做召回。

由于FM模型是利用两个特征的Embedding做内积得到二阶特征交叉的权重,那么我们可以将训练好的FM特征取出离线存好,之后用来做KNN(邻域算法)向量检索。

工业应用的具体操作步骤:

  • 离线训练好FM模型(学习目标可以是CTR)
  • 将训练好的FM模型Embedding取出
  • 将每个uid对应的Embedding做avg pooling(平均)形成该用户最终的Embedding,item也做同样的操作
  • 将所有的Embedding向量放入Faiss等
  • 线上uid发出请求,取出对应的user embedding,进行检索召回

5.4 调包实现

# -*- coding: utf-8 -*-
# 第一步安装,调包
from pyfm import pylibfm
from sklearn.feature_extraction import DictVectorizer
import numpy as np

if __name__ == '__main__':
    # 第二步:创建训练集并转换成one-hot编码的特征形式
    train = [
        {"user": "1", "item": "5", "age": 19},
        {"user": "2", "item": "43", "age": 33},
        {"user": "3", "item": "20", "age": 55},
        {"user": "4", "item": "10", "age": 20},
    ]
    # DictVectorizer() Transforms lists of feature-value mappings to vectors.
    dv = DictVectorizer()
    X = dv.fit_transform(train)
    print(X.toarray())

    # 第三步:创建标签, 这里简单创建了一个全1的标签:
    y = np.repeat(1.0, X.shape[0])

    # 第四步:训练并预测, 就和调用sklearn的包是一样的用法:
    fm = pylibfm.FM()
    fm.fit(X,y)
    ret= fm.predict(dv.transform({"user": "1", "item": "10", "age": 24}))
    print(ret)

打印结果:

[[19.  0.  0.  0.  1.  1.  0.  0.  0.]
 [33.  0.  0.  1.  0.  0.  1.  0.  0.]
 [55.  0.  1.  0.  0.  0.  0.  1.  0.]
 [20.  1.  0.  0.  0.  0.  0.  0.  1.]]
Creating validation dataset of 0.01 of training for adaptive regularization
-- Epoch 1
Training log loss: 0.36855
[0.99034247]