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)2−a2−b2−c2)/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机器学习算法》