前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >因子分解机算法原理及实现

因子分解机算法原理及实现

作者头像
石晓文
发布2020-07-03 17:10:53
8680
发布2020-07-03 17:10:53
举报

由于在逻辑回归中使用的是特征的最原始组合,最终得到的分隔超平面属于线性模型,其只能处理线性可分的二分类问题。现实生活中的分类问题是多种多样的,存在大量的非线性可分的分类问题。

为了使得逻辑回归能够处理更多的复杂问题,对其的优化主要有两种:①对特征进行处理,如核函数的方法,将非线性可分的问题转换成近似线性可分的问题;②对模型本身进行扩展,因子分解机应运而生,其本质是一种基于矩阵分解的方法。笔者在自己科研课题中也曾用过一种FM的改良版,所以对该方法比较有亲切感~

Factorization Machine

对于FM模型,引入度的概念。对于度为2的FM模型为:

其中<Vi,Vj>表示的是两个大小为k的向量Vi和向量Vj的点积:

其中Vi表示的是系数矩阵V的第i维向量,k称为超参数,且大小为模型的度。整个模型由线性部分和交叉部分组成。

损失函数

由于该方法引入了非线性的交叉项,因此可以适用于三类问题,分别是:二分类问题,回归问题,召回&排序问题。本文以二分类问题为例,只需要将模型输出最后通过Sigmoid函数激活即可。

使用logit loss作为损失函数,即:

交叉项处理

将模型改写为一般形式为:

由于这种直接在交叉项的前面加上交叉项系数的方式,在稀疏数据的情况下存在一个很大的缺陷,即在对于观察样本中未出现交互的特征分量时,不能像逻辑回归那样对交叉项系数直接进行估计。

这里针对每一个原始特征引入辅助向量Vi来利用ViVjT对交叉项系数进行估计:

假设:

可得:

这就是矩阵分解的形式,而k可以影响模型的表达能力。

梯度下降法

这里对交叉项的形式进行一定的推导改写,过程如下:

这里选用梯度下降法中的随机梯度法来进行迭代训练,优点在于每次迭代只使用一个样本,在大规模数据集中可以大大降低计算成本。优化流程为:

对损失函数求一阶导为:

而模型输出对参数一阶导有不同形式,分情况讨论:

代入损失函数一阶导后,参数各自的梯度表达式为:

现在让我们用代码实现训练过程:

import numpy as np
def fm_sgd(dataMatrix, classLabels, k, max_iter, alpha):
    '''input: dataMatrix特征
            classLabels标签
            k v的维数
            max_iter最大迭代次数
            alpha学习率
    output: w0,w,v权重'''
    m, n = np.shape(dataMatrix)
    # 1、初始化参数
    w = np.zeros((n, 1))  
    w0 = 0
    v = initialize_v(n, k)  
    
    # 2、训练
    for it in range(max_iter):
        for x in range(m):
            inter_1 = dataMatrix[x] * v
            inter_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * \
             np.multiply(v, v)
            # 完成交叉项
            interaction = np.sum(np.multiply(inter_1, inter_1) - inter_2) / 2.
            p = w0 + dataMatrix[x] * w + interaction
            loss = sigmoid(classLabels[x] * p[0, 0]) - 1
        
            w0 = w0 - alpha * loss * classLabels[x]
            for i in range(n):
                if dataMatrix[x, i] != 0:
                    w[i, 0] = w[i, 0] - alpha * loss * classLabels[x] * dataMatrix[x, i]                  
                    for j in range(k):
                        v[i, j] = v[i, j] - alpha * loss * classLabels[x] * \
                        (dataMatrix[x, i] * inter_1[0, j] -\
                          v[i, j] * dataMatrix[x, i] * dataMatrix[x, i])
        
        # 计算损失函数的值
        if it % 1000 == 0:
            print("\t------- iter: ", it, " , cost: ", \
            Cost(Prediction(np.mat(dataMatrix), w0, w, v), classLabels))
    
    return w0, w, v

其中激活函数和辅助矩阵的初始化函数分别为sigmoid和initialize_v。

import numpy as np
from numpy import normalvariate
def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))

def initialize_v(n, k):
    '''input: n特征的个数
            k超参数
    output: v辅助矩阵'''
    v = np.mat(np.zeros((n, k)))
    
    for i in range(n):
        for j in range(k):        
            v[i, j] = normalvariate(0, 0.2)
    return v

其中模型预测函数和损失函数的计算函数分别为Prediction和Cost。

def Prediction(dataMatrix, w0, w, v):
    '''input: dataMatrix特征
            w常数项权重
            w0一次项权重
            v辅助矩阵
    output: result预测结果'''
    m = np.shape(dataMatrix)[0]   
    result = []
    for x in range(m):
        
        inter_1 = dataMatrix[x] * v
        inter_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * \
         np.multiply(v, v) 
        # 完成交叉项
        interaction = np.sum(np.multiply(inter_1, inter_1) - inter_2) / 2.
        p = w0 + dataMatrix[x] * w + interaction        
        pre = sigmoid(p[0, 0])        
        result.append(pre)        
    return result

def Cost(predict, classLabels):
    '''input: predict预测值
            classLabels标签
    output: error损失函数值'''
    m = len(predict)
    error = 0.0
    for i in range(m):
        error -=  np.log(sigmoid(predict[i] * classLabels[i] ))  
    return error 

到这里整个流程基本就结束了~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小小挖掘机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Factorization Machine
  • 损失函数
  • 交叉项处理
  • 梯度下降法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档