优化算法中梯度下降算法的编程实现

优化算法中梯度下降算法的编程实现

简介

梯度下降算法是运筹学的基础数学方法,用来求解运筹学所构造的数学问题。

本文在Linux平台下,采用C++语言编写梯度下降算法的实现程序。

程序的基本思路是以虚基类构建计算流程,以继承类定义代价函数(Cost Function)的形式,代价函数由用户自定义,继承关系由C++的多态特性辅助。

优化算法通过三个部分实现,残差块(Residual Block)构造、代价函数(Cost Function)构造和优化计算(Optimization)。由于优化计算的框架是固定的,只有具体到残差块函数、代价函数等是不同的,因此将优化计算整体拆分为计算框架和具体内容两个部分,其中的计算框架部分由程序构造,具体内容由用户自定义。为了达成这一思路,程序以虚基类构建计算流程,以继承类定义残差块、代价函数等的形式,代价函数由用户自定义,继承关系由C++的多态特性辅助。

程序包含三个虚基类,残差块类(ResidualBlockFunction)、代价函数类(CostFunction)和优化算法管理类(OptimizationManager),残差块类负责定义残差块的数学结构,为最基础的类;代价函数类负责构造代价函数的数学结构,该结构通过残差块构造,并且负责代价函数的导数求解,导数使用数值微分的方法进行求解;优化算法管理类统筹整个优化计算的实现过程。

虚基类名称以"User"开头,继承类名称以描述自身目标的词汇而非"User"开头。

程序结构

残差块类

残差块类包含两个部分,虚基类和继承类。虚基类名为"UserResidualBlockFunction",继承类名为"PolyResidualBlockFunction"。

代价函数类

代价函数类包含两个部分,虚基类"UserCostFunction"和继承类"SteepestCostFunction"。

代价函数类由优化算法管理类调用,负责为优化算法管理类提供代价函数的函数值、一阶导数值(也称为梯度、Jacobi)、二阶导数(即黑森矩阵,Hessian Matrix)等,因此其核心功能是计算代价函数值、计算代价函数的导数值。同时,代价函数类负责管理残差块,由此,代价函数类的核心功能还包括残差块的添加。

一个典型的代价函数类的虚基类如下,核心功能所指的函数包括了添加计算代价函数值bool CostFunction,计算代价函数的一阶导数值bool DerivativesFunction,残差块函数void AddResidualBlock。而其它函数是用于辅助核心功能的,包括计算代价函数对某一参数的一阶偏导数值的函数bool GetOneDerivative,设定迭代步长void SetStepLength

在虚基类的这些函数中,有一部分是纯虚函数(Pure Virtual Function),它们的定义交由继承类完成,并且继承类必须定义,否则程序无法完成编译。这些函数都是与用户的选择挂钩的,包括添加残差块函数和计算代价函数值的函数。残差块和代价函数必须由用户自行定义,虽然在大量研究文献中,残差块都定义为观测数值与理论数值之差,代价函数都定义为残差块的平方和,但这不代表残差块和代价函数没有其它定义方式,因此,这两个函数的定义交由继承类完成。

class UserCostFunction
{
    public:
        UserCostFunction(string name, int SizeObservations, int SizeVariables, int SizeResiduals);
        ~UserCostFunction();

    public:
        // pure virtual
        virtual void AddResidualBlock(vector<double> observations) = 0;
        virtual bool CostFunction(vector<double> variables, vector<double> &CostFunctionValues)=0;

    public:
        // virtual
        virtual bool GetOneDerivative(int VarialbleID, vector<double> variables, double &theDerivativeValue);

    public:                                                                                                                                                                                                      
        bool DerivativesFunction(vector<double> variables, vector<double> &theDerivatives);
        void SetStepLength(double delta);

    public:
        virtual void Show() = 0;

    protected:
        vector<UserResidualBlockFunction*> ResidualBlockFunctions_;
        int SizeObservations_;
        int SizeVariables_;
        int SizeResiduals_;

        // for derivative calculation
        double delta_;

    private:
        string name_;
};

<center>虚基类"UserCostFunction"的头文件部分</center>

class SteepestCostFunction : virtual public UserCostFunction
{
    public:
        SteepestCostFunction(string name, int SizeObservations, int SizeVariables, int SizeResiduals);                                                                                                           
        ~SteepestCostFunction();
            
    public:
        void Show();

    public:
        virtual void AddResidualBlock(vector<double> observations);
        virtual bool CostFunction(vector<double> variables, vector<double> &CostFunctionValues);

    public:
        virtual bool GetOneDerivative(int VarialbleID, vector<double> variables, double &theDerivativeValue);

    private:
        string name_;
};

<center>继承类"SteepestCostFunction"的头文件部分</center>

代价函数值的计算框架

本文设定代价函数为F(A),残差块为f_i(A),参量以矩阵形式表达,这里假设参量数量为3,则参量为A[a_0,a_1,a_2]^T,观测数据为x_iy_i,假设其理论数学关系符合三阶多项式,y_i=a_0+a_1x_i+a_2x_i^2。虽然代价函数F(A)的形式可以根据用户实际使用而不同,但残差块的平方和形式仍然在大量文献中被采用,如下。
F(A) = \sum_{i=1}^{m} {(f_i(A))^2}
残差块f_i(A)也面临相同的情况,虽然可以根据用户使用情况而不同,但观测值与理论值之差的形式依然是广泛使用的形式,如下。
f_i(A) = y_i-(a_0+a_1x_i+a_2x_i^2)
采用上述形式,则代价函数可以表示如下。
F(A)= (y_0-(a_0+a_1x_0+a_2x_0^2))^2 \\ +(y_1-(a_0+a_1x_1+a_2x_1^2))^2 \\+\cdots \\+ (y_m-(a_0+a_1x_m+a_2x_m^2))^2
代价函数值的计算由函数bool CostFunction完成,由于该函数是核心功能,其名称必须固定,因此该函数作为虚基类"UserCostFunction"的纯虚函数进行声明,在继承类"PolyResidualBlockFunction"中进行定义。

一阶导数的计算框架

优化算法管理类

优化算法管理类包含两个部分,虚基类"UserOptimizationManager"和继承类"SteepestOptimizationManager"。÷

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 13,871评论 1 32
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 13,152评论 1 51
  • 今天晚上值班,每次值班大半夜都是电脑上看电影,连续剧。今天看了几集连续剧,又看看其他的,此时此刻突然发现,今天的日...
    黄灰红阅读 1,333评论 0 0
  • 你好吗?我亲爱的!我知道你一直在陪伴着我! 你好吗?当你出现纠结难受的时候,我知道,你在掉入到事情里面了,我知道你...
    COCO之声阅读 2,839评论 2 1

友情链接更多精彩内容