4.【Python】分类算法—Factorization Machine(FM,因子分解机)

4.【Python】分类算法—Factorization Machine(FM,因子分解机)

现实生活中存在大量非线性可分的分类问题,而Logistic Regression算法只能解决线性可分的二分类问题。Factorization Machine作为Logistic Regression的改进,可解决非线性分类问题。


为了解决非线性分类的问题,第一种方法是采用核函数将非线性可分问题近似为线性可分问题;第二种方法是对Logistic Regression算法进行拓展,例如Factorization Machine(FM,因子分解机),是一种基于矩阵分解的机器学习算法。


前言

FM算法是对Logistic Regression算法的扩展,包含Logistic Regression的非线性项,还包含非线性的交叉项,可以利用矩阵分解的方法对交叉项进行学习,进而确定系数。


一、FM模型

1.因子分解机FM模型

对于二分类即度为2的模型,前两部分属于传统的线性模型,第三部分是两个非线性互异特征向量的关系。
在这里插入图片描述
⟨ V i , V j ⟩ \left \langle V_{i},V_{j} \right \rangle Vi,Vj表示的是两个大小为k的向量 ⟨ V i ⟩ \left \langle V_{i} \right \rangle Vi和向量 ⟨ V j ⟩ \left \langle V_{j} \right \rangle Vj的点积。 ⟨ V i ⟩ \left \langle V_{i} \right \rangle Vi为系数矩阵V的第i维向量,且k的大小为FM算法的度。
在这里插入图片描述

2.损失函数

这里主要用于二分类问题,在二分类中一般采用logit loss作为优化损失函数,表达式如下:
在这里插入图片描述

二、交叉项的处理

1.交叉项系数

FM算法是在基本线性回归模型上引入交叉项,若对象为稀疏数据,则可能出现样本中没有交叉特征向量 x i x_{i} xi的情况,不能对交叉项系数进行估计,所以引入向量的点积对交叉项系数 w i , j w_{i,j} wi,j进行估计。
在这里插入图片描述
在这里插入图片描述
其中,
在这里插入图片描述

在这里插入图片描述

2.模型求解

对交叉项进行单独求解,将其化解为形如 ( ( a + b + c ) 2 − a 2 − b 2 − c 2 ) / 2 ((a+b+c)^{2}-a^2-b^2-c^2)/2 ((a+b+c)2a2b2c2)/2,前半部分为交叉项的变式,后半部分为0向量。
在这里插入图片描述

三、FM算法的求解

前面介绍的算法一般采用梯度下降法,但是在求解迭代过程中一般需要全部数据进行训练学习,所以在数据量较大时采用随机梯度下降法。

1.随机梯度下降法求解模型参数

随机梯度下降法(Stochastic Gradient Descent)在迭代过程中根据一个数据样本对模型参数进行调整,其优化过程为:
在这里插入图片描述
在采用随机梯度下降法进行学习时,主要对损失函数求偏导数:
在这里插入图片描述
其中 ∂ y ^ ∂ θ \frac{\partial \widehat{y}}{\partial \theta } θy 为:
在这里插入图片描述

2.利用随机梯度下降法训练FM模型参数

主要步骤为先求初始化权重w和交叉项的系数矩阵V,然后对每个样本进行训练求权重和交叉项,最后直到满足条件才进行终止。

首先定义梯度下降法函数stocGradAscent,函数输入为特征dataMatrix、样本标签classLabels、系数矩阵维数k、随机梯度下降法的最大迭代次数max_iter、随机梯度下降法的学习率alpha,函数输出为一次项的权重 w 0 w_{0} w0、常数项的权重 w i w_{i} wi、交叉项的系数矩阵V。

代码如下:

def stocGradAscent(dataMatrix,classLabels,k,max_iter,alpha):
    '''利用随机梯度下降法训练FM模型
    input:dataMatrix(mat)特征
            classLabels(mat)标签
            k(int)v的维数
            max_iter(int)最大迭代次数
            alpha(float)学习率
    output:w0(float),w(mat),v(mat):权重

    '''
    m,n = np.shape(dataMatrix)
    #1.初始化参数
    w = np.zeros((n,1))
    w0 = 0 #偏置项
    v= initialize_v(n,k) #初始化v

    #2.训练
    for it in range(max_iter):
        print("iteration:", it)
        for x in range(m):  #随机优化,对每一个样本而言的
            inter_1 = dataMatrix[x] * v
            inter_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * np.multiply(v,v) #multiply对应元素相乘
            #完成交叉项
            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:", getCost(getPrediction(np.mat(dataTrain), w0, w, v), classLabels))

    #3.返回最终的FM模型的参数
    return w0, w, v

在初始化权重时,为简化步骤将所有权重都化为0。在对交叉项系数矩阵初始化时,使用正态分布对其进行初始化,定义initialize_v函数如下所示。

代码如下:

from random import normalvariate #正态分布

#初始化交叉项的权重
def initialize_v(n, k):
    '''初始化交叉项
    input: n(int)特征的个数
            k(int)FM模型的超参数
    output: v(mat):交叉项的系数权重
    '''
    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

其中损失函数采用sigmoid函数,定义sigmoid函数如下。

代码如下:

#定义sigmoid函数
def sigmoid(inx):
    return 1.0/(1 + np.exp(-inx))

在每完成1000次迭代以后,计算一次当前的损失值,定义计算损失的函数getCost如下。

代码如下:

#计算损失函数的值
#定义getcost函数
def getCost(predict, classLabels):
    '''计算预测准确性
    input: predict(list)预测值
            classLabels(list)标签值
    output: error(float)计算损失函数的值
    '''
    m = len(predict)
    error = 0.0
    for i in range(m):
        error -= np.log(sigmoid(predict[i] * classLabels[i]))
    return error

三、FM算法实践

采用非线性可分的数据集作为训练数据,主要分为两个部分,一为利用训练数据集对模型进行训练,二为利用测试集对数据进行预测。

1.训练FM模型

FM训练模型的主函数如下,首先利用loadDataSet函数导入训练数据,然后利用stocGradAscent函数训练模型,其后利用getPrediction函数对训练数据进行预测,利用getAccuracy函数将预测值和样本进行比较得到准确率,最后利用save_model函数保存模型。

定义导入训练数据的loadDataSet函数,代码如下:

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

由于FM模型中使用的是log损失函数,在导入标签的过程中,需要将原始数据范围{0,1}转换为{-1,1}。

定义预测训练样本的getPrediction函数,代码如下:

def getPrediction(dataMatrix, w0, w, v):
    '''得到预测值
    input: dataMatrix(mat)特征
            w(int)常数项权重
            w0(int)一次项权重
            v(float)交叉项权重
    output: result(list)预测的结果
    '''
    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)  # multiply对应元素相乘
        # 完成交叉项
        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

定义getAccuracy函数评价模型预测效果,代码如下:

def getAccuracy(predict, classLabels):
    '''计算预测准确性
    input: predict(list)预测值
            classLabels(list)标签
    output: float(error) / allItem(float)错误率
    '''
    m = len(predict)
    allItem = 0
    error = 0
    for i in range(m):
        allItem += 1
        if float(predict[i]) < 0.5 and classLabels[i] == 1.0:
            error += 1
        elif float(predict[i]) >= 0.5 and classLabels[i] == -1.0:
            error += 1
        else:
            continue
    return float(error) / allItem

定义save_model函数保存FM模型的偏置项和权重,代码如下:

def save_model(file_name, w0, w, v):
    '''保存训练好的模型
    input: file_name(string):保存的文件名
            w0(float):偏置项
            w(mat): 一次项的权重
            v(mat): 交叉项的权重
    '''
    f = open(file_name, "w")
    #1.保存w0
    f.write(str(w0) + "\n")
    #2.保存一次项的权重
    w_array = []
    m = np.shape(w)[0]
    for i in range(m):
        w_array.append(str(w[i, 0]))
    f.write("\t".join(w_array) + "\n")
    #3.保存交叉项的权重
    m1, n1 = np.shape(v)
    for i in range(m1):
        v_tmp = []
        for j in range(n1):
            v_tmp.append(str(v[i, j]))
        f.write("\t".join(v_tmp) + "\n")
    f.close()

完整代码如下:

# conding:UTF-8
import numpy as np
from random import normalvariate #正态分布


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 initialize_v(n, k):
    '''初始化交叉项
    input: n(int)特征的个数
            k(int)FM模型的超参数
    output: v(mat):交叉项的系数权重
    '''
    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

#定义sigmoid函数
def sigmoid(inx):
    return 1.0/(1 + np.exp(-inx))

#计算损失函数的值
#定义getcost函数
def getCost(predict, classLabels):
    '''计算预测准确性
    input: predict(list)预测值
            classLabels(list)标签值
    output: error(float)计算损失函数的值
    '''
    m = len(predict)
    error = 0.0
    for i in range(m):
        error -= np.log(sigmoid(predict[i] * classLabels[i]))
    return error


def stocGradAscent(dataMatrix,classLabels,k,max_iter,alpha):
    '''利用随机梯度下降法训练FM模型
    input:dataMatrix(mat)特征
            classLabels(mat)标签
            k(int)v的维数
            max_iter(int)最大迭代次数
            alpha(float)学习率
    output:w0(float),w(mat),v(mat):权重

    '''
    m,n = np.shape(dataMatrix)
    #1.初始化参数
    w = np.zeros((n,1))
    w0 = 0 #偏置项
    v= initialize_v(n,k) #初始化v

    #2.训练
    for it in range(max_iter):
        print("iteration:", it)
        for x in range(m):  #随机优化,对每一个样本而言的
            inter_1 = dataMatrix[x] * v
            inter_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * np.multiply(v,v) #multiply对应元素相乘
            #完成交叉项
            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:", getCost(getPrediction(np.mat(dataTrain), w0, w, v), classLabels))

    #3.返回最终的FM模型的参数
    return w0, w, v


def getPrediction(dataMatrix, w0, w, v):
    '''得到预测值
    input: dataMatrix(mat)特征
            w(int)常数项权重
            w0(int)一次项权重
            v(float)交叉项权重
    output: result(list)预测的结果
    '''
    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)  # multiply对应元素相乘
        # 完成交叉项
        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 getAccuracy(predict, classLabels):
    '''计算预测准确性
    input: predict(list)预测值
            classLabels(list)标签
    output: float(error) / allItem(float)错误率
    '''
    m = len(predict)
    allItem = 0
    error = 0
    for i in range(m):
        allItem += 1
        if float(predict[i]) < 0.5 and classLabels[i] == 1.0:
            error += 1
        elif float(predict[i]) >= 0.5 and classLabels[i] == -1.0:
            error += 1
        else:
            continue
    return float(error) / allItem


def save_model(file_name, w0, w, v):
    '''保存训练好的模型
    input: file_name(string):保存的文件名
            w0(float):偏置项
            w(mat): 一次项的权重
            v(mat): 交叉项的权重
    '''
    f = open(file_name, "w")
    #1.保存w0
    f.write(str(w0) + "\n")
    #2.保存一次项的权重
    w_array = []
    m = np.shape(w)[0]
    for i in range(m):
        w_array.append(str(w[i, 0]))
    f.write("\t".join(w_array) + "\n")
    #3.保存交叉项的权重
    m1, n1 = np.shape(v)
    for i in range(m1):
        v_tmp = []
        for j in range(n1):
            v_tmp.append(str(v[i, j]))
        f.write("\t".join(v_tmp) + "\n")
    f.close()


if __name__ == '__main__':
    #1.导入训练数据
    print("-------1.load data-------")
    dataTrain,labelTrain = loadDataSet("data.txt")
    print("-------2.learning--------")
    #2.利用随机梯度训练FM模型
    w0, w, v = stocGradAscent(np.mat(dataTrain), labelTrain, 3, 10000, 0.01)
    predict_result = getPrediction(np.mat(dataTrain), w0, w, v) #计算训练的准确性
    print("-------training error: %f " %(1 - getAccuracy(predict_result, labelTrain)))
    print("-------3.save model------")
    #3.保存训练好的FM模型
    save_model("weights", w0, w, v)


2.FM测试模型

上述将FM分类模型训练完成后,将系数都保存在weights文件中,建立FM_test.py文件。

FM测试模型主要是对新数据进行预测,主要步骤为:定义loadDataSet函数导入测试数据,定义loadModel函数导入训练模型数据,定义save_result模型保存最终的预测结果。

主函数代码如下:

if __name__ == '__main__':
    #1.导入测试数据
    dataTest = loadDataTest("test_data.txt")
    #2.导入FM训练模型
    w0, w, v = loadModel("weights")
    #3.预测
    result = getPrediction(dataTest, w0, w, v)
    #4.保存最终的预测结果
    save_result = ("predict_result", result)

函数loadDataTest的输入为测试数据的位置,输出为测试数据的特征,代码如下:

def loadDataSet(data):
    '''导入测试数据
    input: data(string)训练数据
    output: dataMat(list)特征
    '''
    dataMat = []
    fr = open(data) #打开文件
    for line in fr.readlines():
        lines = line.strip().split("\t")
        lineArr = []

        for i in range(len(lines)):
            lineArr.append(float(lines[i]))
        dataMat.append(lineArr)

    fr.close()
    return dataMat

函数loadModel输出的是FM训练模型中的参数包括偏置项 w 0 w_{0} w0,一次项的权重 w i w_{i} wi,交叉项的权重V,代码如下:

def loadModel(model_file):
    '''导入FM模型
    input: model_file(string)FM模型
    output:w0, np.mat(w).T, np.mat(v) FM模型的参数
    '''
    f = open(model_file)
    line_index = 0
    w0 = 0.0
    w = []
    v = []
    for line in f.readlines():
        lines = line.strip().split("\t")
        if line_index == 0:  #w0
            w0 = float(lines[0].strip())
        elif line_index == 1: #w
            for x in lines:
                w.append(float(x.strip()))
        else:
            v_tmp = []
            for x in lines:
                v_tmp.append(float(x.strip()))
            v.append()
        line_index += 1
    f.close()
    return w0, np.mat(w).T, np.mat(v)

函数save_result将FM测试模型的预测结果保存到文件中,代码如下:

def save_result(file_name, result):
    '''保存最终的预测结果
    input: file_name(string)需要保存的文件名
            result(mat):对测试数据的预测结果
    '''
    f = open(file_name, "w")
    f.write("\n".join(str(x) for x in result))
    f.close()

完整代码如下:

# conding:UTF-8
import numpy as np


def loadDataTest(data):
    '''导入测试数据
    input: data(string)训练数据
    output: dataMat(list)特征
    '''
    dataMat = []
    fr = open(data) #打开文件
    for line in fr.readlines():
        lines = line.strip().split("\t")
        lineArr = []

        for i in range(len(lines)):
            lineArr.append(float(lines[i]))
        dataMat.append(lineArr)

    fr.close()
    return dataMat

def loadModel(model_file):
    '''导入FM模型
    input: model_file(string)FM模型
    output:w0, np.mat(w).T, np.mat(v) FM模型的参数
    '''
    f = open(model_file)
    line_index = 0
    w0 = 0.0
    w = []
    v = []
    for line in f.readlines():
        lines = line.strip().split("\t")
        if line_index == 0:  #w0
            w0 = float(lines[0].strip())
        elif line_index == 1: #w
            for x in lines:
                w.append(float(x.strip()))
        else:
            v_tmp = []
            for x in lines:
                v_tmp.append(float(x.strip()))
            v.append()
        line_index += 1
    f.close()
    return w0, np.mat(w).T, np.mat(v)


#定义sigmoid函数
def sigmoid(inx):
    return 1.0/(1 + np.exp(-inx))

def getPrediction(dataMatrix, w0, w, v):
    '''得到预测值
    input: dataMatrix(mat)特征
            w(int)常数项权重
            w0(int)一次项权重
            v(float)交叉项权重
    output: result(list)预测的结果
    '''
    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)  # multiply对应元素相乘
        # 完成交叉项
        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 save_result(file_name, result):
    '''保存最终的预测结果
    input: file_name(string)需要保存的文件名
            result(mat):对测试数据的预测结果
    '''
    f = open(file_name, "w")
    f.write("\n".join(str(x) for x in result))
    f.close()


if __name__ == '__main__':
    #1.导入测试数据
    dataTest = loadDataTest("test_data.txt")
    #2.导入FM训练模型
    w0, w, v = loadModel("weights")
    #3.预测
    result = getPrediction(dataTest, w0, w, v)
    #4.保存最终的预测结果
    save_result("predict_result", result)


总结

本文主要介绍了Factorization Machine(FM,因子分解机)分类算法,与Softmax 和Logistic分类算法不同的是,此算法可用于非线性分类,因此在函数模型上多了一项交叉项表示非线性向量的交互。python的实现过程主要分为两部分——训练和测试。

参考文献:《Python机器学习算法》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值