看这篇之前建议先看这篇,里面讲了非线性优化的原理即相关名词的概念,然后介绍了NLopt的使用方法,这个方法是基于C语言的,本片介绍一个NLopt的实例,用的C++语言。
在实例之前,先介绍下NLopt支持的算法,以及算法使用的注意事项
NLopt 支持的算法
NLopt 包含很多种不同的优化算法。
在头文件里面算法名称的枚举类型为
enum algorithm {
GN_DIRECT = 0,
GN_DIRECT_L,
GN_DIRECT_L_RAND,
GN_DIRECT_NOSCAL,
GN_DIRECT_L_NOSCAL,
GN_DIRECT_L_RAND_NOSCAL,
GN_ORIG_DIRECT,
GN_ORIG_DIRECT_L,
GD_STOGO,
GD_STOGO_RAND,
LD_LBFGS_NOCEDAL,
LD_LBFGS,
LN_PRAXIS,
LD_VAR1,
LD_VAR2,
LD_TNEWTON,
LD_TNEWTON_RESTART,
LD_TNEWTON_PRECOND,
LD_TNEWTON_PRECOND_RESTART,
GN_CRS2_LM,
GN_MLSL,
GD_MLSL,
GN_MLSL_LDS,
GD_MLSL_LDS,
LD_MMA,
LN_COBYLA,
LN_NEWUOA,
LN_NEWUOA_BOUND,
LN_NELDERMEAD,
LN_SBPLX,
LN_AUGLAG,
LD_AUGLAG,
LN_AUGLAG_EQ,
LD_AUGLAG_EQ,
LN_BOBYQA,
GN_ISRES,
AUGLAG,
AUGLAG_EQ,
G_MLSL,
G_MLSL_LDS,
LD_SLSQP,
LD_CCSAQ,
GN_ESCH,
NUM_ALGORITHMS /*不是一种算法 只是算法的数量*/
};
命名规律:
G/L代表的就是 全局(global)或者局部(local)优化
N/D代表的就是 不需导数 或者 需要导数 的优化
例如 LN_COBYLA 就是用的 COBYLA 算法 ,然后该算法用于局部(L)无需导数(N)的优化
算法选择
那么我们在使用的时候用哪种算法呢?随意选择吗?NO
对于一个确定的数学模型的优化问题,比较好的方式是 通过比较几种可用的算法,再确定选哪种,因为没有最佳算法,不同优化问题对于最佳的解决方式不同。
在比较算法的时候也要注意,不同算法适配的 函数值容差和参数容差 也应该是不同的。
有一种比较好的比较两种算对于一个数学模型的优劣的方式:
先运行一个算法得到最小值,然后运行第二个算法的时候设定就收敛到那个值,然后比较运行的时间
选择全局优化要注意的问题
当前,所有全局优化算法都要求对所有优化参数指定边界约束。
并且一定要注意的是
在这些算法中 只有 ISRES, AGS, 和 ORIG_DIRECT 支持非线性不等式约束
只有 ISRES 支持非线性等式约束
有一种很好的做法就是通过全局算法找到一个最优点,然后以这个最优点作为局部优化的起点,使结果更准确
全局优化算法在找到最优点时 比局部优化算法要 花的精力多很多
Code
数学模型:
求 :max(lnx1+lnx2) 目标函数
约束: p1x1+p2x2=5 等式约束
x1<=x2 不等式约束
x1>=0;x2>=0 边界约束
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <nlopt.hpp> //nlopt的头文件
引用头文件
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//声明一个 是否使用梯度的 全局变量
bool grad_bool=1;
声明一个 是否使用梯度的 全局变量
这样方便在切换 基于梯度的算法和不需要导数的算法的时候可以 改下这个变量就可以
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
double myfunc(const std::vector<double>& x,std::vector<double>&grad,void* f_data )
{
double result;//声明结果
if (grad_bool)
{
grad[0] = 1 / x[0];
grad[1] = 1 / x[1];
}
result = log(x[0])+log(x[1]);//计算结果
return result;//返回结果
}
目标函数是 max(lnx1+x2)
定义目标函数 注意函数的参数的形式 是不可以变化的
参数x 是 要优化的参数向量
grad返回为此时最优化参数的梯度 就是数学模型里里 对x求偏导的结果 如果要求偏导那么 lnx1+lnx2 对x1求偏导,及1/x1 也就是 代码的 grad[0] = 1 / x[0]; 这个地方
f_data 是要传入的参数
返回值 为在一个x向量下的目标函数的值
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
double myconstraint(const std::vector<double>& x,std::vector<double>&grad,void* f_data)
{
double *p = (double *) f_data;
if (grad_bool)
{
grad[0] = p[0];
grad[1] = p[1];
}
double result;//声明结果
result = x[0] * p[0] + x[1] * p[1] - 5;//计算结果
return result;//返回结果
}
定义等式约束函数
数学模型的等式是:p1x1+p2x2=5
p1和p2以外部参数的方式传进来
最好的方式换种写法,这样什么样的参数格式都可以套用
typedef struct{
double p1,p2;
}my_constraint_data;
声明要传入参数的结构体
double myconstraint(const std::vector<double>& x,std::vector<double>&grad,void* f_data)
{
my_constraint_data *d = (my_constraint_data*)f_data;
//double *p = (double *) f_data;
double p1 = d->p1;
double p2 = d->p2;
if (grad_bool)
{
grad[0] = p1;
grad[1] = p2;
}
double result;//声明结果
result = x[0] * p1 + x[1] * p2 - 5;//计算结果
return result;//返回结果
}
然后取参数的时候这样就可以
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
double myinconstraint(const std::vector<double>& x,std::vector<double>&grad,void* f_data)
{
if (grad_bool)
{
grad[0] = 1;
grad[1] = -1;
}
double result;//声明结果
result = x[0] - x[1] ;//计算结果
return result;//返回结果
}
不等式的约束函数
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main(int argc, char** argv)
{
//初始化节点
ros::init(argc, argv, "lidar_align");
//声明两个句柄
ros::NodeHandle nh;
然后就是main函数的,我是在ros下做的,所以加个节点的初始化
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//数学模型里的参数
//double p[2]={1,2};
my_constraint_data data = {1,2};
//待优化 求取的参数
std::vector<double> x{1,1};
声明数学模型里面 等式约束的 参数 my_constraint_data这个结构体在上面定义了
声明 待优化 求取的参数 1,1相当于初始化了
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//声明一个优化器
nlopt::opt opter;
声明一个NLopt的优化器
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*局部优化算法*/
//opter = nlopt::opt(nlopt::LD_SLSQP,2); //有导数
//opter = nlopt::opt(nlopt::LN_COBYLA,2); //无导数
/*全局优化算法*/
opter = nlopt::opt(nlopt::GN_ISRES,2); //无导数 是可以的 x1 = 1.66667 x2= 1.66667 fmax = 1.02165
给优化器设置使用什么优化算法,并设置因子个数,就是x的个数
优化算法在nlopt.hpp里面有, 可以从里面选择想用的
但是注意并不是所有算法都可以计算出来,例如这个数学模型有 不等式约束和等式约束的 全局优化算法里面只有ISRES可以使用
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*优化参数的边界约束*/
std::vector<double> lb {1,1};//注意参数的个数要对应上
std::vector<double> ub {10000,10000};//注意参数的个数要对应上
//设置 参数 边界
opter.set_lower_bounds(lb);//设置参数下限
opter.set_upper_bounds(ub);//设置参数上限
优化参数的边界约束
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*设置优化精度*/
double tol = 1e-8;//容差
opter.set_xtol_rel(tol);
opter.set_force_stop(tol);
设置优化精度
控制优化什么时候停止
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*设置目标函数 其中目标函数是自己定义的 */
// myfunc 是自己定义的函数名字 ,第二个参数为 可传入数据 没有数据则为NULL
opter.set_max_objective(myfunc,NULL);
设置目标函数
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*设置数学模型的 等式 约束 和 不等式 约束 */
//myconstraint 是自己定义的函数名字 ,第二个参数为 可传入数据,
opter.add_equality_constraint(myconstraint,&data,tol);
opter.add_inequality_constraint(myinconstraint, NULL,tol);
设置数学模型的 等式 约束 和 不等式 约束
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* 获取结果 最大值储存在f_max内 对应的向量储存在x内*/
double f_max = -10000;
nlopt::result res = opter.optimize(x,f_max);
执行优化计算
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
std::cout<<"x1 = " <<x[0]<<" x2= "<<x[1]<<" fmax = "<<f_max<<std::endl;
打印计算结果
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~