入门推荐系统——FM因子分解机模型

1.FM模型

  1. 逻辑回归模型及其缺点
    做推荐CTR预估时最简单的思路就是将特征做线性组合(逻辑回归LR),传入sigmoid中得到一个概率值,本质上这就是一个线性模型。
    sigmoid是单调增函数不会改变里面的线性模型的CTR预测顺序,因此逻辑回归模型效果会比较差。即LR的缺点:
    • 是线性模型
    • 个特征对最终输出结果独立,需要手动特征交叉( x i ∗ x j x_i*x_j xixj),比较麻烦
  2. 二阶交叉项的考虑及改进
    考虑所有的二阶交叉项,也就是将目标函数由原来的
    y = w 0 + ∑ i = 1 n w i x i y = w_0+\sum_{i=1}^nw_ix_i y=w0+i=1nwixi
    变为
    y = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n − 1 ∑ i + 1 n w i j x i x j y=w_0+\sum_{i=1}^nw_ix_i+\sum_{i=1}^{n-1}\sum_{i+1}^nw_{ij}x_ix_j y=w0+i=1nwixi+i=1n1i+1nwijxixj 但这个式子有一个问题,只有当 x i x_i xi x j x_j xj均不为0时这个二阶交叉项才会生效,后面这个特征交叉项本质是和多项式核SVM等价的,为了解决这个问题,FM上场。

FM模型使用了如下的优化函数:

y = w 0 + ∑ i = 1 n w i x i + ∑ i = 1 n ∑ i + 1 n < v i , v j > x i x j y = 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 y=w0+i=1nwixi+i=1ni+1n<vi,vj>xixj 事实上做的唯一改动就是把 w i j w_{ij} wij替换成了 < v i , v j > \lt v_i,v_j\gt <vi,vj>,大家应该就看出来了,这实际上就有深度学习的意味在里面了,实质上就是给每个 x i x_i xi计算一个embedding,然后将两个向量之间的embedding做内积得到之前所谓的 w i j w_{ij} wij
好处就是这个模型泛化能力强 ,即使两个特征之前从未在训练集中同时出现,我们也不至于像之前一样训练不出 w i j w_{ij} wij,事实上只需要 x i x_i xi和其他的 x k x_k xk同时出现过就可以计算出 x i x_i xi的embedding!

2.FM公式理解

从公式来看,模型前半部分就是普通的LR线性组合,后半部分的交叉项:特征组合。首先,单从模型表达能力上来看,FM是要强于LR的,至少它不会比LR弱,当交叉项参数 w i j w_{ij} wij全为0的时候,整个模型就退化为普通的LR模型。对于有 n n n个特征的模型,特征组合的参数数量共有 1 + 2 + 3 + ⋯ + n − 1 = n ( n − 1 ) 2 1+2+3+\cdots + n-1=\frac{n(n-1)}{2} 1+2+3++n1=2n(n1)个,并且任意两个参数之间是独立的。所以说特征数量比较多的时候,特征组合之后,维度自然而然就高了。

(线性代数)定理:任意一个实对称矩阵(正定矩阵) W W W都存在一个矩阵 V V V,使得 W = V . V T W=V.V^{T} W=V.VT成立。

所有二次项参数 ω i j \omega_{ij} ωij可以组成一个对称阵 W W W(为了方便说明FM的由来,对角元素可以设置为正实数),那么这个矩阵就可以分解为 W = V T V W=V^TV W=VTV V V V 的第 j j j列( v j v_{j} vj),即第 j j j维特征( x j x_{j} xj)的隐向量。
y ^ ( X ) = ω 0 + ∑ i = 1 n ω i x i + ∑ i = 1 n − 1 ∑ j = i + 1 n < v i , v j > x i x j \hat{y}(X) = \omega_{0}+\sum_{i=1}^{n}{\omega_{i}x_{i}}+\sum_{i=1}^{n-1}{\sum_{j=i+1}^{n} \color{red}{<v_{i},v_{j}>x_{i}x_{j}}} y^(X)=ω0+i=1nωixi+i=1n1j=i+1n<vi,vj>xixj

需要估计的参数有 ω 0 ∈ R \omega_{0}∈ R ω0R ω i ∈ R \omega_{i}∈ R ωiR V ∈ R V∈ R VR < ⋅ , ⋅ > < \cdot, \cdot> <,>是长度为 k k k的两个向量的点乘,公式如下:

< v i , v j > = ∑ f = 1 k v i , f ⋅ v j , f <v_{i},v_{j}> = \sum_{f=1}^{k}{v_{i,f}\cdot v_{j,f}} <vi,vj>=f=1kvi,fvj,f

上面的公式中:

  • ω 0 \omega_{0} ω0为全局偏置;
  • ω i \omega_{i} ωi是模型第 i i i个变量的权重;
  • ω i j = < v i , v j > \omega_{ij} = < v_{i}, v_{j}> ωij=<vi,vj>特征 i i i j j j的交叉权重;
  • v i v_{i} vi是第 i i i维特征的隐向量;
  • < ⋅ , ⋅ > <\cdot, \cdot> <,>代表向量点积;
  • k ( k < < n ) k(k<<n) k(k<<n)为隐向量的长度,包含 k k k 个描述特征的因子。

FM模型中二次项的参数数量减少为 k n kn kn个,远少于多项式模型的参数数量。另外,参数因子化使得 x h x i x_{h}x_{i} xhxi 的参数和 x i x j x_{i}x_{j} xixj 的参数不再是相互独立的,因此我们可以在样本稀疏的情况下相对合理地估计FM的二次项参数。具体来说, x h x i x_{h}x_{i} xhxi x i x j x_{i}x_{j} xixj的系数分别为 < v h , v i > \lt v_{h},v_{i}\gt <vh,vi> < v i , v j > \lt v_{i},v_{j}\gt <vi,vj> ,它们之间有共同项 v i v_{i} vi 。也就是说,所有包含“ x i x_{i} xi 的非零组合特征”(存在某个 j ≠ i j \ne i j=i ,使得 x i x j ≠ 0 x_{i}x_{j}\neq 0 xixj=0 )的样本都可以用来学习隐向量 v i v_{i} vi,这很大程度上避免了数据稀疏性造成的影响。而在多项式模型中, w h i w_{hi} whi w i j w_{ij} wij 是相互独立的。

FM的公式是一个通用的拟合方程,可以采用不同的损失函数用于解决regression、classification等问题,比如可以采用MSE(Mean Square Error)loss function来求解回归问题,也可以采用Hinge/Cross-Entropy loss来求解分类问题。当然,在进行二元分类时,FM的输出需要使用sigmoid函数进行变换,该原理与LR是一样的。直观上看,FM的复杂度是 O ( k n 2 ) O(kn^2) O(kn2) 。但是FM的二次项可以化简,其复杂度可以优化到 O ( k n ) O(kn) O(kn) 。由此可见,FM可以在线性时间对新样本作出预测。

证明
等式证明

解释

  • v i , f v_{i,f} vi,f 是一个具体的值;
  • 第1个等号:对称矩阵 W W W 对角线上半部分;
  • 第2个等号:把向量内积 v i v_{i} vi, v j v_{j} vj 展开成累加和的形式;
  • 第3个等号:提出公共部分;
  • 第4个等号: i i i j j j 相当于是一样的,表示成平方过程。

3.应用

直接把FM得到的结果放进sigmoid中输出一个概率值,由此做CTR预估,事实上我们也可以做召回。
FM模型是利用两个特征的Embedding做内积得到二阶特征交叉的权重,那么我们可以将训练好的FM特征取出离线存好,之后用来做KNN向量检索。
具体步骤:

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

4.代码时间

1.调包实现
Github官方仓库:https://github.com/coreylynch/pyFM

安装及使用:
安装
方法一:pip install

pip install git+https://github.com/coreylynch/pyFM

方法二:手动安装

  • 在官方仓库下载这个包
  • 解压包
  • cd到当前文件夹下python setup.py install安装
  • 若报错,去掉setup.py文件里面的libraries=[“m”]一行,再回到第3步重新安装

安装失败。需要提前安装好Cython,但是我按网上的教程都未成功。后续有待实现

测试

  1. 导包
from pyfm import pylibfm
from sklearn.feature_extraction import DictVectorizer
import numpy as np
  1. 创建训练集并转换为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},
]
v = DictVectorizer()
X = v.fit_transform(train)
print(X.toarray())

查看结果

[[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.]]

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

array([1., 1., 1., 1.])

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

实战:电影评分数据

数据集在这里下载:http://www.grouplens.org/system/files/ml-100k.zip

导包,并定义一个导入指定格式数据集的函数

import numpy as np
from sklearn.feature_extraction import DictVectorizer
from pyfm import pylibfm

# Read in data
def loadData(filename,path="ml-100k/"):
    data = []
    y = []
    users=set()
    items=set()
    with open(path+filename) as f:
        for line in f:
            (user,movieid,rating,ts)=line.split('\t')
            data.append({ "user_id": str(user), "movie_id": str(movieid)})
            y.append(float(rating))
            users.add(user)
            items.add(movieid)

    return (data, np.array(y), users, items)

导入训练集和测试集,并转换格式

(train_data, y_train, train_users, train_items) = loadData("ua.base")
(test_data, y_test, test_users, test_items) = loadData("ua.test")
v = DictVectorizer()
X_train = v.fit_transform(train_data)
X_test = v.transform(test_data)

训练模型并测试

# Build and train a Factorization Machine
fm = pylibfm.FM(num_factors=10, num_iter=100, verbose=True, task="regression", initial_learning_rate=0.001, learning_rate_schedule="optimal")
fm.fit(X_train,y_train)

预测结果打印误差

preds = fm.predict(X_test)
from sklearn.metrics import mean_squared_error
print("FM MSE: %.4f" % mean_squared_error(y_test,preds))

FM MSE: 0.8873

实战:分类任务

处理数据

import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.cross_validation import train_test_split
from pyfm import pylibfm

from sklearn.datasets import make_classification

X, y = make_classification(n_samples=1000,n_features=100, n_clusters_per_class=1)
data = [ {v: k for k, v in dict(zip(i, range(len(i)))).items()}  for i in X]

X_train, X_test, y_train, y_test = train_test_split(data, y, test_size=0.1, random_state=42)

v = DictVectorizer()
X_train = v.fit_transform(X_train)
X_test = v.transform(X_test)

建模型
注意:这里的参数num_factors和tasks改变了

fm = pylibfm.FM(num_factors=50, num_iter=10, verbose=True, task="classification", initial_learning_rate=0.0001, learning_rate_schedule="optimal")
fm.fit(X_train,y_train)

这是处理分类任务,误差函数也和之前的不同

from sklearn.metrics import log_loss
print("Validation log loss: %.4f" % log_loss(y_test,fm.predict(X_test)))

Validation log loss: 1.3678

  1. 从零实现
    数据集介绍
    criteo:非常经典的点击率预估数据集,其中连续特征有13个,类别型特征有26个,没有提供特征的具体名称,特征分别如下:

dense_feats:‘I1’, ‘I2’, ‘I3’, ‘I4’, ‘I5’, ‘I6’, ‘I7’, ‘I8’, ‘I9’, ‘I10’,‘I11’, ‘I12’, ‘I13’
sparse_feats: ‘C1’, ‘C2’, ‘C3’, ‘C4’, ‘C5’, ‘C6’, ‘C7’, ‘C8’, ‘C9’, ‘C10’, ‘C11’, ‘C12’, ‘C13’, ‘C14’, ‘C15’, ‘C16’, ‘C17’, ‘C18’, ‘C19’, ‘C20’, ‘C21’, ‘C22’, ‘C23’, ‘C24’, ‘C25’, ‘C26’

5.思考

FM存在的问题,以及哪可以改进

6.参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值