优化算法中梯度下降算法的编程实现
简介
梯度下降算法是运筹学的基础数学方法,用来求解运筹学所构造的数学问题。
本文在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 observations) = 0;
virtual bool CostFunction(vector variables, vector &CostFunctionValues)=0;
public:
// virtual
virtual bool GetOneDerivative(int VarialbleID, vector variables, double &theDerivativeValue);
public:
bool DerivativesFunction(vector variables, vector &theDerivatives);
void SetStepLength(double delta);
public:
virtual void Show() = 0;
protected:
vector ResidualBlockFunctions_;
int SizeObservations_;
int SizeVariables_;
int SizeResiduals_;
// for derivative calculation
double delta_;
private:
string name_;
};
class SteepestCostFunction : virtual public UserCostFunction
{
public:
SteepestCostFunction(string name, int SizeObservations, int SizeVariables, int SizeResiduals);
~SteepestCostFunction();
public:
void Show();
public:
virtual void AddResidualBlock(vector observations);
virtual bool CostFunction(vector variables, vector &CostFunctionValues);
public:
virtual bool GetOneDerivative(int VarialbleID, vector variables, double &theDerivativeValue);
private:
string name_;
};
代价函数值的计算框架
本文设定代价函数为,残差块为,参量以矩阵形式表达,这里假设参量数量为3,则参量为,观测数据为和,假设其理论数学关系符合三阶多项式,。虽然代价函数的形式可以根据用户实际使用而不同,但残差块的平方和形式仍然在大量文献中被采用,如下。
残差块也面临相同的情况,虽然可以根据用户使用情况而不同,但观测值与理论值之差的形式依然是广泛使用的形式,如下。
采用上述形式,则代价函数可以表示如下。
代价函数值的计算由函数bool CostFunction
完成,由于该函数是核心功能,其名称必须固定,因此该函数作为虚基类"UserCostFunction"的纯虚函数进行声明,在继承类"PolyResidualBlockFunction"中进行定义。
一阶导数的计算框架
优化算法管理类
优化算法管理类包含两个部分,虚基类"UserOptimizationManager"和继承类"SteepestOptimizationManager"。÷