机器学习算法(3)——FM(Factorization Machine)算法(推导与实现)

1、前言

由于逻辑回归只能处理线性可分的模型或者数据集,又由于现实生活中的分类问题是多种多样的,存在大量的非线性可分的分类问题,为了能够满足要求,对逻辑回归(Logistic Regression)进行了如下的优化:

(1)、对特征进行处理:

             如:核函数的方法,将非线性可分的问题转换成近似线性可分的问题;

(2)、对逻辑回归(Logistic Regression)进行扩展:

             如:因子分解机 FM 算法就是对逻辑回归(Logistic Regression)的扩展。


2、因子分解机 FM(Factorization Machine) 模型

2.1、FM 算法模型的建立

对于因子分解机 FM 模型,引入度的概念。对于度为 2 的因子分解机模型为:

\large \hat{y}=w_0+\sum_{i=1}^{n}w_{i}x_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}<V_{i},V_{j}>x_ix_j

其中:w_0 为R,W为R^n,v属于R^(n,k);

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

\large <V_i,V_j>=\sum_{f=1}^{k}v_{i,f} \cdot v_{j,f}

上式中:Vi 表示的是系数矩阵 V 的第 i 维的向量,且Vi为: 

\large V_i = (v_{i,1},v_{i,2},...,v_{i,k})

k 为超参数,且 k 的大小称为 FM 算法模型的度,在因子分解机 FM 模型中,前两部分就是传统的线性模型,最后的那部分将两个互异的特征向量之间的相互关系考虑进去。

2.2、因子分解机 FM 算法可以处理的问题

(1)、回归问题(Regression)

(2)、二分类问题(Binary Classification)

(3)、排序(Ranking)

本文只讨论分类的问题,对于二分类问题,去阈值函数为 Sigmoid 函数:

\large \sigma (x) = \frac{1}{1+e^{-x}}

同时,损失函数定义为:

\large loss^c(\hat{y}\cdot y)=\sum_{i=1}^{m}-ln\sigma(\hat{y}^{(i)}\cdot y^{(i)})

2.3、FM算法中的交叉项系数

在基本线性回归模型的基础上引入交叉项:

\large \hat{y}=w_0+\sum_{i=1}^{n}w_{i}x_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^{n} w_{i,j}x_ix_j

这种直接在交叉项 x_i,x_j 的前面加上交叉系数 w_ij 的方式,在稀疏数据的情况下存在很大的缺陷,即在对于观察样本中未出现交互的特征分量时,不能对相应的参数进行估计。

对每个特征分量 x_i 引入辅助向量 Vi=(v_i1,v_i2, ..., v_ik)利用 ViVj^T 对交叉项的系数 w_ij 进行估计,即:

\large \hat{w_{ij}}= V_{i}V_{j}^T

令:

\large V=\begin{bmatrix} v_{11} &v_{12} & ...& v_{1k} \\ v_{21} & v_{22} & ... & v_{2k} \\ ...& ...& ...&... \\ v_{n1} & v_{n2} & ...& v_{nk} \end{bmatrix}_{n*k}=\begin{bmatrix} V_1\\ V_2\\ ...\\ V_n\end{bmatrix}

于是有:

\large \hat{W}=VV^T=\begin{bmatrix} V_1\\ V_2\\ ...\\ V_n\end{bmatrix}\begin{bmatrix} V_1^T &V_2^T &... &V_n^T \end{bmatrix}

这样对应了一种矩阵的分解,对 k 值的限定、FM 的表达能力均有一定的影响。

2.4、FM 模型交叉项参数的求解详细推导

对于交叉项的求解可以效仿如下:

\large A+B=\frac{1}{2}[(A+B)^2-(A^2+B^2)]

2.5、用随机梯度法对 FM 模型参数求解

假设数据集 X 中有 m 个训练样本,即

\large X=\left \{ X^{(1)},X^{(2)},...X^{(m)} \right \}

同时每个样本对应一个特征X^(i):

 

\large X^{(i)}=\left \{ X_1^{(i)},X_2^{(i)},...X_m^{(i)} \right \}

对于度 k=2 的因子分解机 FM 模型来说,其主要的参数就一次项和常数项的参数 w0,w1....,wn 以及交叉系数矩阵 V ,在利用随机梯度对模型的参数进行学习的过程中,主要是对损失函数 loss 的求导。

\large \frac{\partial loss^c(\hat{y}\cdot y)}{\partial \theta}=\frac{1}{-\partial (\hat{y}\cdot y)}\partial(\hat{y}\cdot y)\cdot [1- (\hat{y}\cdot y)]\cdot y\cdot \frac{\partial \hat{y}}{\partial \theta}

                            \large =[1- (\hat{y}\cdot y)]\cdot y \cdot \frac{\partial \hat{y}}{\partial \theta}

而:

\large \frac{\partial \hat{y}}{\partial \theta}=\left\{\begin{matrix} 1 & ,& \theta=w_0 \\ x_i & ,& \theta=w_i \\ x_i \sum_{i=1}^{m}v_{i,f}\cdot x_j-v_{i,f}x_i^2& ,& \theta=v_{i,f} \end{matrix}\right.

2.6、FM 算法的流程

(1)、初始化权重 w0,w1,...,wn 和 v系数矩阵;

(2)、对每个样本:

                    \large w_0=:w_0-\alpha[1-\partial(\hat{y}\cdot y)]\cdot y

                    对于特征 i:

                             \large w_i=:w_i-\alpha[1-\partial(\hat{y}\cdot y)]\cdot y \cdot x_i

                              对于“ 度 f—>k ”:

                                       \large v_{i,f}=:v_{i,f}-\alpha[1-\partial(\hat{y}\cdot y)]\cdot y \cdot[x_i\sum_{j=1}^{n}v_{j,f}x_j-v_{i,f}x_i^2]

(3)、重复步骤(2),直到满足条件停止。


3、Python 实现 FM 算法

3.1、初始化参数

# 初始化权重 w 和交叉项权重 v
def initialize_w_v(n, k):
    w = np.ones((n, 1))
    v = np.mat(np.zeros((n, k)))
    for i in range(n):
        for j in range(k):
            v[i, j] = np.random.normal(0, 0.2)    # 把 v 中的值变为服从 N(0, 0.2) 正态分布的数值
    return w, v

3.2、损失函数的实现

# 定义误差损失函数 loss(y', y) = ∑-ln[sigmoid(y'* y)]
def get_cost(predict, classLabels):
    m = np.shape(predict)[0]
    # m = len(predict)
    cost = []
    error = 0.0
    for i in range(m):
        error -= np.log(sigmoid(predict[i] * classLabels[i]))
        cost.append(error)
    return error

3.3、梯度下降法训练模型 Python 代码实现

# 用梯度下降法求解模型参数,训练 FM 模型。
def stocGradient(dataMatrix, classLabels, k, max_iter, alpha):
    """
    :param dataMatrix: 输入的数据集特征
    :param classLabels: 特征对应的标签
    :param k: 交叉项矩阵的维度
    :param max_iter: 最大迭代次数
    :param alpha: 学习率
    :return: 
    """
    m, n = np.shape(dataMatrix)
    w0 = 0
    w, v = initialize_w_v(n, k)     # 初始化参数
    for it in range(max_iter):
        # print('第 %d 次迭代' % it)
        for x in range(m):
            v_1 = dataMatrix[x] * v    # dataMatrix[x]的 shape 为(1,n),v的 shape 为(n,k)--->v_1的 shape为(1, k)
            v_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * np.multiply(v, v)
            interaction = 0.5 * np.sum(np.multiply(v_1, v_1) - v_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] * v_1[0, j] - v[i, j] *
                                                                             dataMatrix[x, i]*dataMatrix[x, i])
        if it % 1000 == 0:
            print("\t迭代次数:" + str(it) + ",误差:" + str(get_cost(prediction(np.mat(dataMatrix), w0, w, v), classLabels)))
    return w0, w, v

3.4、FM 算法的完整 Python 代码

# -*- coding: utf-8 -*-
# @Time    : 2019-1-11 13:59
# @Author  : Chaucer_Gxm
# @Email   : gxm4167235@163.com
# @File    : Train.py
# @GitHub  : https://github.com/Chaucergit/Code-and-Algorithm
# @blog    : https://blog.csdn.net/qq_24819773
# @Software: PyCharm
import numpy as np
import time


# 导入准备训练的数据集
def load_data(filename):
    data = open(filename)
    feature = []
    label = []
    for line in data.readlines():
        feature_tmp = []
        lines = line.strip().split('\t')
        for x in range(len(lines)-1):
            feature_tmp.append(float(lines[x]))
        label.append(int(lines[-1])*2-1)
        feature.append(feature_tmp)
    data.close()
    return feature, label


# 初始化权重 w 和交叉项权重 v
def initialize_w_v(n, k):
    w = np.ones((n, 1))
    v = np.mat(np.zeros((n, k)))
    for i in range(n):
        for j in range(k):
            v[i, j] = np.random.normal(0, 0.2)    # 把 v 中的值变为服从 N(0, 0.2) 正态分布的数值
    return w, v


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


# 定义误差损失函数 loss(y', y) = ∑-ln[sigmoid(y'* y)]
def get_cost(predict, classLabels):
    m = np.shape(predict)[0]
    # m = len(predict)
    cost = []
    error = 0.0
    for i in range(m):
        error -= np.log(sigmoid(predict[i] * classLabels[i]))
        cost.append(error)
    return error


# 用梯度下降法求解模型参数,训练 FM 模型。
def stocGradient(dataMatrix, classLabels, k, max_iter, alpha):
    """
    :param dataMatrix: 输入的数据集特征
    :param classLabels: 特征对应的标签
    :param k: 交叉项矩阵的维度
    :param max_iter: 最大迭代次数
    :param alpha: 学习率
    :return: 
    """
    m, n = np.shape(dataMatrix)
    w0 = 0
    w, v = initialize_w_v(n, k)     # 初始化参数
    for it in range(max_iter):
        # print('第 %d 次迭代' % it)
        for x in range(m):
            v_1 = dataMatrix[x] * v    # dataMatrix[x]的 shape 为(1,n),v的 shape 为(n,k)--->v_1的 shape为(1, k)
            v_2 = np.multiply(dataMatrix[x], dataMatrix[x]) * np.multiply(v, v)
            interaction = 0.5 * np.sum(np.multiply(v_1, v_1) - v_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] * v_1[0, j] - v[i, j] *
                                                                             dataMatrix[x, i]*dataMatrix[x, i])
        if it % 1000 == 0:
            print("\t迭代次数:" + str(it) + ",误差:" + str(get_cost(prediction(np.mat(dataMatrix), w0, w, v), classLabels)))
    return w0, w, v


# 定义预测结果的函数
def prediction(dataMatrix, w0, w, v):
    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 = 0.5 * np.sum(np.multiply(inter_1, inter_1) - inter_2)
        p = w0 + dataMatrix[x] * w + interaction
        pre = sigmoid(p[0, 0])
        result.append(pre)
    return result


# 计算准确度
def getaccuracy(predict, classLabels):
    m = np.shape(predict)[0]
    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(filename, w0, w, v):
    f = open(filename, 'w')
    f.write(str(w0)+'\n')
    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')
    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()


# 主函数
def main():
    # 第一步:导入数据
    feature, label = load_data('train_data.txt')
    # print(feature, label)
    # 第二步:利用梯度下降训练模型
    w0, w, v = stocGradient(np.mat(feature), label, 4, 20001, 0.02)
    predict_result = prediction(np.mat(feature), w0, w, v)
    print('训练精度为:%f' % (1-getaccuracy(predict_result, label)))
    # 第三步保存模型
    save_model('weights_FM', w0, w, v)

if __name__ == '__main__':
    start = time.time()
    main()
    end = time.time()
    print('训练模型用时为:%s' % str(end-start))

训练结果:

21.660687655417806
1.2649489467261146	75.27079468117786
-0.0	-0.0	-0.0	-0.0
0.0	0.0	0.0	0.0

测试结果:

0.9989796253213139
0.9999443493345659
0.623877247116008
0.9994974000340888
0.9999892853562842
0.9117533237892267
0.9993122746939441
0.9999998391255357
0.9518803708612011
0.9518180046166856
0.999999939840248

完整程序(训练程序+测试程序)与数据集地址:

 https://github.com/Chaucergit/Code-and-Algorithm/tree/master/ML/3.Factorization%20Machine


参考书目:

[1].统计方法.李航

[2].Python机器学习算法.赵志勇

[3].利用Python进行数据分析.WesKinney著,唐学韬等译

 

 

 

 

  • 11
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值