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

    技术2022-07-12  109

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

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

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

    Processed: 0.014, SQL: 9