基于C++ Eigen的列文伯格马夸尔特(LM)优化算法

#include "LM.h"
#include <opencv2/opencv.hpp>

int main()
{
	const double a = 1, b = 5, c = 2;
	double a_ = 0.9, b_ = 5.5, c_ = 2.2;

	LM::LevenbergMarquardt lm(a_, b_, c_);
	lm.SetParameters(1e-10, 1e-10, 100, true);

	const size_t N = 1000;
	cv::RNG rng(cv::getTickCount());

	for (size_t i = 0; i < N; i++)
	{
		double x = rng.uniform(0.0, 10.0);
		double y = exp(a * x * x + b * x + c) + rng.gaussian(5.0);
		lm.AddObservation(x, y);
	}

	lm.Slove();
	return 0;
}
#pragma once
#include <iostream>
#include <vector>
#include <chrono>
#include <Eigen/Core>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

namespace LM
{	
	class RunTime
	{
	public:
		void Stat();
		void Stop();
		double Duration();
	private:
		chrono::steady_clock::time_point tstat;
		chrono::steady_clock::time_point tend;
	};

	class LevenbergMarquardt
	{
	public:
		LevenbergMarquardt(double da, double db, double dc);
		void SetParameters(double sEpsilon1, double sEpsilin2, int sMaxIter, bool sIsout);
		void AddObservation(const double x, const double y);
		void CalcJ_fx();
		void CalcH_g();
		double GetCost();
		double F(double a, double b, double c);
		double L0_L(Eigen::Vector3d h);
		void Slove();
	public:
		double epsilon1, epsilon2;
		int MaxIter;
		std::vector<Eigen::Vector2d> obsPt;
		double a, b, c;
		bool isOut;
	private:			
		MatrixXd fx_;
		MatrixXd J_;
		Matrix3d H_;
		Vector3d g_;
	};
}

#include "LM.h"
#include <iomanip>

void LM::RunTime::Stat()
{
	tstat = chrono::steady_clock::now();
}

void LM::RunTime::Stop()
{
	tend = chrono::steady_clock::now();
}

double LM::RunTime::Duration()
{
	return chrono::duration_cast<chrono::duration<double>>(tend - tstat).count();
}

LM::LevenbergMarquardt::LevenbergMarquardt(double da, double db, double dc) :a(da), b(db), c(dc)
{
	epsilon1 = 1e-6;
	epsilon2 = 1e-6;
	MaxIter = 50;
	isOut = true;
}

void LM::LevenbergMarquardt::SetParameters(double sEpsilon1, double sEpsilon2, int sMaxIter, bool sIsout)
{
	epsilon1 = sEpsilon1;
	epsilon2 = sEpsilon2;
	MaxIter = sMaxIter;
	isOut = sIsout;
}

void LM::LevenbergMarquardt::AddObservation(const double x, const double y)
{
	obsPt.push_back(Eigen::Vector2d(x, y));
}

void LM::LevenbergMarquardt::CalcJ_fx()
{
	J_.resize(obsPt.size(), 3);
	fx_.resize(obsPt.size(), 1);

	for (int i = 0; i < obsPt.size(); i++)
	{
		Eigen::Vector2d obs = obsPt.at(i);
		const double x = obs(0);
		const double y = obs(1);
		double dfda = -x * x * exp(a * x * x + b * x + c);
		double dfdb = -x * exp(a * x * x + b * x + c);
		double dfdc = -exp(a * x * x + b * x + c);
		J_(i, 0) = dfda;
		J_(i, 1) = dfdb;
		J_(i, 2) = dfdc;
		fx_(i, 0) = y - exp(a * x * x + b * x + c);
	}
}

void LM::LevenbergMarquardt::CalcH_g()
{
	H_ = J_.transpose() * J_;
	g_ = - J_.transpose() * fx_;
}

double LM::LevenbergMarquardt::GetCost()
{
	MatrixXd cost = fx_.transpose() * fx_;
	return cost(0, 0);
}

double LM::LevenbergMarquardt::F(double da, double db, double dc)
{
	Eigen::VectorXd fx;
	fx.resize(obsPt.size(), 1);

	for (int i = 0; i < obsPt.size(); i++)
	{
		const Eigen::Vector2d obs = obsPt.at(i);
		const double x = obs(0);
		const double y = obs(1);
		fx(i) = y - exp(da * x * x + db * x + dc);
	}
	Eigen::MatrixXd F = 0.5 * fx.transpose() * fx;
	return F(0, 0);
}

double LM::LevenbergMarquardt::L0_L(Eigen::Vector3d h)
{
	/*Eigen::MatrixXd L = -fx_.transpose() * J_ * h - 0.5 * h.transpose() * J_ * J_.transpose() * h;*/

	Eigen::MatrixXd L = -h.transpose() * J_.transpose() * fx_ - 0.5 * h.transpose() * J_.transpose() * J_ * h;
	return L(0, 0);
}

void LM::LevenbergMarquardt::Slove()
{
	int k = 0;
	double vu = 2.0;
	CalcJ_fx();
	CalcH_g();
	bool found = (g_.lpNorm<Eigen::Infinity>() < epsilon1); //误差足够小
	std::vector<double> H_diag;
	for (int i = 0; i < 3; i++)
	{
		H_diag.push_back(H_(i, i));
	}
	auto max = std::max_element(H_diag.begin(), H_diag.end());
	double mu = *max;
	double tsum = 0;
	while (!found && k < MaxIter) // 误差足够小,增量足够小,达到迭代次数
	{
		k++;
		RunTime runtime;
		runtime.Stat();
		Eigen::Matrix3d G = H_ + mu * Eigen::Matrix3d::Identity();
		Eigen::Vector3d h = G.ldlt().solve(g_);

		if (h.norm() < epsilon2 * sqrt(a * a + b * b + c * c) + epsilon2) //增量足够小
		{
			found = true;
		}
		else
		{
			double a_new = a + h(0);
			double b_new = b + h(1);
			double c_new = c + h(2);
			double sig = (F(a, b, c) - F(a_new, b_new, c_new)) / L0_L(h);
			if (sig > 0)
			{
				a = a_new;
				b = b_new;
				c = c_new;
				CalcJ_fx();
				CalcH_g();
				found = (g_.lpNorm<Eigen::Infinity>() < epsilon1);
				mu = mu * std::max<double>(0.333, 1 - std::pow(2 * sig - 1, 3));
				vu = 2.0;
			}
			else
			{
				mu = mu * vu;
				vu *= 2;
			}
		}

		runtime.Stop();
		if (isOut)
		{
			std::cout << "Iter: " << std::left << std::setw(5) << k << std::left << std::setw(10)
				<< "Result: " << std::left << std::setw(10) << a << " " << std::left << std::setw(10) << b << " " << std::left << std::setw(10) << c << std::left << std::setw(10)
				<< "step: " << std::left << std::setw(15) << h.norm() << std::left << std::setw(10)
				<< "cost: " << std::left << std::setw(10) << GetCost() << std::left << std::setw(10)
				<< "time: " << std::left << std::setw(10) << (tsum += runtime.Duration()) << std::endl;
		}
	}

	if (found)
	{
		std::cout << "\nConverged\n\n";
	}
	else
	{
		std::cout << "\nDiverged\n\n";
	}
}

 

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LM算法,全称为Levenberg-Marquard算法,它可用于解决非线性最小二乘问题,多用于曲线拟合等场合。 LM算法的实现并不算难,它的关键是用模型函数 f 对待估参数向量 p 在其邻域内做线性近似,忽略掉二阶以上的导数项,从而转化为线性最小二乘问题,它具有收敛速度快等优点。LM算法属于一种“信赖域法”——所谓的信赖域法,此处稍微解释一下:在最优化算法中,都是要求一个函数的极小值,每一步迭代中,都要求目标函数值是下降的,而信赖域法,顾名思义,就是从初始点开始,先假设一个可以信赖的最大位移 s ,然后在以当前点为中心,以 s 为半径的区域内,通过寻找目标函数的一个近似函数(二次的)的最优点,来求解得到真正的位移。在得到了位移之后,再计算目标函数值,如果其使目标函数值的下降满足了一定条件,那么就说明这个位移是可靠的,则继续按此规则迭代计算下去;如果其不能使目标函数值的下降满足一定的条件,则应减小信赖域的范围,再重新求解。 事实上,你从所有可以找到的资料里看到的LM算法的说明,都可以找到类似于“如果目标函数值增大,则调整某系数再继续求解;如果目标函数值减小,则调整某系数再继续求解”的迭代过程,这种过程与上面所说的信赖域法是非常相似的,所以说LM算法是一种信赖域法。
下面是一个简单的C++实现,用于演示如何使用LM算法来解决无约束非线性最小二乘问题: ```c++ #include <iostream> #include <vector> #include <Eigen/Dense> #include <unsupported/Eigen/NonLinearOptimization> using namespace Eigen; struct LMFunctor { int operator()(const VectorXd &x, VectorXd &fvec) const { // 定义非线性方程组 fvec(0) = x(0) + 2 * x(1) + std::exp(x(0)) - std::exp(x(1)); fvec(1) = x(0) * x(1) + std::exp(x(0)) * std::exp(x(1)) - 2; return 0; } int inputs() const { return 2; } // 输入变量的维度 int values() const { return 2; } // 输出变量的维度 }; int main() { // 初始化优化器 LMFunctor functor; VectorXd x(2); x << 1.0, 1.0; LevenbergMarquardt<LMFunctor> lm(functor); lm.parameters.maxfev = 1000; lm.parameters.xtol = 1.0e-10; lm.parameters.ftol = 1.0e-10; // 运行优化器 int info = lm.minimize(x); if (info != Eigen::Success) { std::cout << "LM optimization failed." << std::endl; return 1; } // 输出优化结果 std::cout << "x = " << x.transpose() << std::endl; return 0; } ``` 在上述代码中,我们首先定义了一个LMFunctor结构体,用于表示非线性方程组。然后,我们使用Eigen库中的LevenbergMarquardt类来初始化一个LM优化器,并将其应用于我们定义的非线性方程组。最后,我们输出优化结果。 需要注意的是,上述实现仅用于演示如何使用LM算法进行无约束非线性最小二乘问题的求解。在实际应用中,您可能需要根据具体的问题进行优化器的参数调整和代码的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值