一、 因子分解机 FM 算法的提出
在 Logistic Regression 只能处理线性可分的二分类问题,但在现实中大多数是非线性问题,为了能使 Logistic Regression 算法能处理非线性,我们需要对算法进行升级,有两种方法:
(一)、利用人工对特征处理,如使用核函数对特征进行处理,但人工处理对初学者比较难。
(二)、对 Logistic Regression 算法进行拓展,如使用因子分解机 FM (Factorization Machine),是一种基于矩阵分解的机器学习算法。
在传统的线性模型如 Logistic Regression 中,每个特征都是独立的,如果需要考虑特征与特征直接的交互作用,可能需要人工对特征进行交叉组合,非线性 SVM 可以对特征进行 kernel 映射,但是在特征高度稀疏的情况下,并不能很好地进行学习。现在在模型如因子分解机 FM、SVD++ 等,可以学习到特征之间的交互隐藏关系,但基本上每个模型都只适用于特定的输入和场景。
在因子分解机 FM 模型中,不仅包含了 Logistic Regression 模型的线性项,还包含非线性的交叉项,利用矩阵分解对模型中交叉项进行学习,得到每一项的系数无需人工参与。
因子分解机 FM 模型的应用:
(一)、回归问题——损失函数一般为均方误差
(二)、二分类问题——损失函数为 Logit loss ,最终形式,为阈值函数,采用 Sigmoid 函数,本节主讲。
(三)、排序
FM 对比 SVM :
SVM 和 FM 的主要区别在于,SVM 的二元特征交叉参数是独立的,如 wij,而 FM 的二元特征交叉参数是两个 k 维的向量 vi、vj,这样子的话,<vi,vj> 和 <vi,vk> 就不是独立的,而是相互影响的。线性SVM只有一维特征,不能挖掘深层次的组合特征在实际预测中并没有很好的表现;而多项式 svn 正如前面提到的,交叉的多个特征需要在训练集上共现才能被学习到,否则该对应的参数就为0,这样对于测试集上的 case 而言这样的特征就失去了意义,因此在稀疏条件下,SVM 表现并不能让人满意。而 FM 不一样,通过向量化的交叉,可以学习到不同特征之间的交互,进行提取到更深层次的抽象意义。
此外,FM 和 SVM 的区别还体现在:
1)FM 可以在原始形式下进行优化学习,而基于kernel的非线性SVM通常需要在对偶形式下进行;
2)FM 的模型预测是与训练样本独立,而 SVM 则与部分训练样本有关,即支持向量。
二、因子分解机 FM 的模型
二元交叉的 FM 模型如下,其中,w 是输入特征的参数,<vi,vj> 是输入特征 i,j 间的交叉参数,v 是 k 维向量。
之所以通过向量 v 的学习方式而不是简单的 wij 参数,这是因为在稀疏条件下,这样的表示方法打破了特征的独立性,能够更好地挖掘特征之间的相关性。就是通过向量 v 的学习方式能够更好的挖掘特征间的相互关系,尤其在稀疏条件下。
交叉项的求解,两两交叉的复杂度由O(k*n^2)降低到O(kn)。
三、FM 算法的损失函数及求解
在二分类问题中,损失函数为:
使用随机梯度下降法对模型中参数进行调整,优化过程如下:
在利用随机梯度对模型的参数进行学习主要是对损失函数进行求导,即:
其中:
四、FM 算法的流程
- 初始化权重w0,w1,....,wn和V
- 对每一个样本: 对特征i: 对f:
- 重复步骤2, 知道满足终止条件
五、 因子分解机 FM 算法的实践
1、 利用梯度下降法训练 FM 模型
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 26 15:10:31 2019
@author: 2018061801
"""
#FM_train.py
import numpy as np
from random import normalvariate # 正态分布用于v的初始化
def loadDataSet(data):
'''导入训练数据
input: data(string)训练数据
output: dataMat(list)特征
labelMat(list)标签
'''
dataMat = []
labelMat = []
fr = open(data) # 打开文件
for line in fr.readlines():
lines = line.strip().split("\t")
lineArr = []
for i in range(len(lines) - 1):
lineArr.append(float(lines[i]))
dataMat.append(lineArr)
labelMat.append(float(lines[-1]) * 2 - 1) # 转换成{-1,1}
fr.close()
return dataMat, labelMat
def sigmoid(inx):
return 1.0 / (