机器学习算法实现解析——libFM之libFM的训练过程之SGD的方法

本节主要介绍的是libFM源码分析的第五部分之一——libFM的训练过程之SGD的方法。

5.1、基于梯度的模型训练方法

在libFM中,提供了两大类的模型训练方法,一类是基于梯度的训练方法,另一类是基于MCMC的模型训练方法。对于基于梯度的训练方法,其类为fm_learn_sgd类,其父类为fm_learn类,主要关系为:

fm_learn_sgd类是所有基于梯度的训练方法的父类,其具体的代码如下所示:

#include "fm_learn.h"
#include "../../fm_core/fm_sgd.h"

// 继承自fm_learn
class fm_learn_sgd: public fm_learn {
    protected:
        //DVector<double> sum, sum_sqr;
    public:
        int num_iter;// 迭代次数
        double learn_rate;// 学习率
        DVector<double> learn_rates;// 多个学习率        

        // 初始化
        virtual void init() {       
            fm_learn::init();   
            learn_rates.setSize(3);// 设置学习率
        //  sum.setSize(fm->num_factor);        
        //  sum_sqr.setSize(fm->num_factor);
        }       

        // 利用梯度下降法进行更新,具体的训练的过程在其子类中
        virtual void learn(Data& train, Data& test) { 
            fm_learn::learn(train, test);// 该函数并没有具体实现
            // 输出运行时的参数,包括:学习率,迭代次数
            std::cout << "learnrate=" << learn_rate << std::endl;
            std::cout << "learnrates=" << learn_rates(0) << "," << learn_rates(1) << "," << learn_rates(2) << std::endl;
            std::cout << "#iterations=" << num_iter << std::endl;

            if (train.relation.dim > 0) {// 判断relation
                throw "relations are not supported with SGD";
            }
            std::cout.flush();// 刷新
        }

        // SGD重新修正fm模型的权重
        void SGD(sparse_row<DATA_FLOAT> &x, const double multiplier, DVector<double> &sum) {
            fm_SGD(fm, learn_rate, x, multiplier, sum);// 调用fm_sgd中的fm_SGD函数
        } 

        // debug函数,主要用于打印中间结果
        void debug() {
            std::cout << "num_iter=" << num_iter << std::endl;
            fm_learn::debug();          
        }

        // 对数据进行预测
        virtual void predict(Data& data, DVector<double>& out) {
            assert(data.data->getNumRows() == out.dim);// 判断样本个数是否相等
            for (data.data->begin(); !data.data->end(); data.data->next()) {
                double p = predict_case(data);// 得到线性项和交叉项的和,调用的是fm_learn中的方法
                if (task == TASK_REGRESSION ) {// 回归任务
                    p = std::min(max_target, p);
                    p = std::max(min_target, p);
                } else if (task == TASK_CLASSIFICATION) {// 分类任务
                    p = 1.0/(1.0 + exp(-p));// Sigmoid函数处理
                } else {// 异常处理
                    throw "task not supported";
                }
                out(data.data->getRowIndex()) = p;
            }               
        } 

};

fm_learn_sgd类中,主要包括五个函数,分别为:初始化init函数,训练learn函数,SGD训练SGD函数,debug的debug函数和预测predict函数。

5.1.1、初始化init函数

在初始化中,对学习率的大小进行了初始化,同时继承了父类中的初始化方法。

5.1.2、训练learn函数

learn函数中,没有具体的训练的过程,只是对训练中需要用到的参数进行输出,具体的训练的过程在其对应的子类中定义,如fm_learn_sgd_element类和fm_learn_sgd_element_adapt_reg类。

5.1.3、SGD训练SGD函数

SGD函数使用的是fm_sgd.h文件中的fm_SGD函数。fm_SGD函数是利用梯度下降法对模型中的参数进行调整,以得到最终的模型中的参数。在利用梯度下降法对模型中的参数进行调整的过程中,假设损失函数为ll,那么,对于回归问题来说,其损失函数为:

在定义好上述的计算方法后,其核心的问题是如何计算∂y^(i)∂θ\frac{\partial \hat{y}^{(i)}}{\partial \theta },在“机器学习算法实现解析——libFM之libFM的模型处理部分”中已知:

其中,η \eta 为学习率,在libFM中,其具体的代码如下所示:

// 利用SGD更新模型的参数
void fm_SGD(fm_model* fm, const double& learn_rate, sparse_row<DATA_FLOAT> &x, const double multiplier, DVector<double> &sum) {
    // 1、常数项的修正
    if (fm->k0) {
        double& w0 = fm->w0;
        w0 -= learn_rate * (multiplier + fm->reg0 * w0);
    }
    // 2、一次项的修正
    if (fm->k1) {
        for (uint i = 0; i < x.size; i++) {
            double& w = fm->w(x.data[i].id);
            w -= learn_rate * (multiplier * x.data[i].value + fm->regw * w);
        }
    }
    // 3、交叉项的修正
    for (int f = 0; f < fm->num_factor; f++) {
        for (uint i = 0; i < x.size; i++) {
            double& v = fm->v(f,x.data[i].id);
            double grad = sum(f) * x.data[i].value - v * x.data[i].value * x.data[i].value; 
            v -= learn_rate * (multiplier * grad + fm->regv * v);
        }
    }   
}

5.1.4、预测predict函数

predict函数用于对样本进行预测,这里使用到了predict_case函数,该函数在“机器学习算法实现解析——libFM之libFM的训练过程概述”中有详细的说明,得到值后,分别对回归问题和分类问题做处理,在回归问题中,主要是防止超出最大值和最小值,在分类问题中,将其值放入Sigmoid函数,得到最终的结果。

5.2、SGD的训练方法

随机梯度下降法(Stochastic Gradient Descent ,SGD)是一种简单有效的优化方法。对于梯度下降法的更多内容,可以参见“梯度下降优化算法综述”。在利用SGD对FM模型训练的过程如下图所示:

在libFM中,SGD的实现在fm_learn_sgd_element.h文件中。在该文件中,定义了fm_learn_sgd_element类,fm_learn_sgd_element类继承自fm_learn_sgd类,主要实现了fm_learn_sgd类中的learn方法,具体的程序代码如下所示:

#include "fm_learn_sgd.h"

// 继承了fm_learn_sgd
class fm_learn_sgd_element: public fm_learn_sgd {
    public:
        // 初始化
        virtual void init() {
            fm_learn_sgd::init();
            // 日志输出
            if (log != NULL) {
                log->addField("rmse_train", std::numeric_limits<double>::quiet_NaN());
            }
        }
        // 利用SGD训练FM模型
        virtual void learn(Data& train, Data& test) {
            fm_learn_sgd::learn(train, test);// 输出参数信息

            std::cout << "SGD: DON'T FORGET TO SHUFFLE THE ROWS IN TRAINING DATA TO GET THE BEST RESULTS." << std::endl; 
            // SGD
            for (int i = 0; i < num_iter; i++) {// 开始迭代,每一轮的迭代过程
                double iteration_time = getusertime();// 记录开始的时间
                for (train.data->begin(); !train.data->end(); train.data->next()) {// 对于每一个样本
                    double p = fm->predict(train.data->getRow(), sum, sum_sqr);// 得到样本的预测值
                    double mult = 0;// 损失函数的导数
                    if (task == 0) {// 回归
                        p = std::min(max_target, p);
                        p = std::max(min_target, p);
                        // loss=(y_ori-y_pre)^2
                        mult = -(train.target(train.data->getRowIndex())-p);// 对损失函数求导
                    } else if (task == 1) {// 分类
                        // loss
                        mult = -train.target(train.data->getRowIndex())*(1.0-1.0/(1.0+exp(-train.target(train.data->getRowIndex())*p)));
                    }
                    // 利用梯度下降法对参数进行学习
                    SGD(train.data->getRow(), mult, sum);                   
                }               
                iteration_time = (getusertime() - iteration_time);// 记录时间差
                // evaluate函数是调用的fm_learn类中的方法
                double rmse_train = evaluate(train);// 对训练结果评估
                double rmse_test = evaluate(test);// 将模型应用在测试数据上
                std::cout << "#Iter=" << std::setw(3) << i << "\tTrain=" << rmse_train << "\tTest=" << rmse_test << std::endl;
                // 日志输出
                if (log != NULL) {
                    log->log("rmse_train", rmse_train);
                    log->log("time_learn", iteration_time);
                    log->newLine();
                }
            }       
        }

};

参考文献

  • Rendle S. Factorization Machines[C]// IEEE International Conference on Data Mining. IEEE Computer Society, 2010:995-1000.
  • Rendle S. Factorization Machines with libFM[M]. ACM, 2012.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏null的专栏

简单易学的机器学习算法——K-Means++算法

一、K-Means算法存在的问题 由于K-Means算法的简单且易于实现,因此K-Means算法得到了很多的应用,但是从K-Means算法的过程中发现,K-Me...

3665
来自专栏数值分析与有限元编程

可视化 | MATLAB划分均匀矩形网格

之前发过一个划分均匀三角形网格的例子。下面结合一个悬臂梁说说如何在规则区域划分均匀矩形网格。 ? 将一个矩形平面区域划分成相同大小的矩形。X方向等分nex,Y方...

5489
来自专栏李智的专栏

Python针对图像的基础操作

5. 返回目录中所有JPG 图像的文件名列表,直方图均衡化,平均图像,主成分分析等

1602
来自专栏机器之心

教程 | 基础入门:深度学习矩阵运算的概念和代码实现

选自Medium 机器之心编译 参与:蒋思源 本文从向量的概念与运算扩展到矩阵运算的概念与代码实现,对机器学习或者是深度学习的入门者提供最基础,也是最实用的教...

44013
来自专栏简书专栏

深度学习问题1-5

参考链接:https://blog.csdn.net/colourful_sky/article/details/79164720

1053
来自专栏iOSDevLog

决策树

1174
来自专栏闪电gogogo的专栏

《统计学习方法》笔记二 感知机

感知机(perceptron)是二分类的线性分类模型,输入为实例的特征向量,输出为实例的类别,取±1。感知机对应与输入空间中将实例划分为正负两类的分离超平面,属...

772
来自专栏mantou大数据

[机器学习实战]K-近邻算法

1. K-近邻算法概述(k-Nearest Neighbor,KNN) K-近邻算法采用测量不同的特征值之间的距离方法进行分类。该方法的思路是:如果一个样本在特...

4505
来自专栏机器学习算法与Python学习

Python:numpy的总结(1)

1、multiply 例子: x1=[1,2,3];x2=[4,5,6] print multiply(x1,x2) 输出: [ 4 10 18] multi...

3634
来自专栏racaljk

[动态规划] 矩阵链乘法问题

矩阵链乘法问题是指给定一串矩阵序列M₁M2..Mn,求至少需要进行多少次乘法运算才能求得结果

4482

扫码关注云+社区

领取腾讯云代金券