手写系列之手写LM(Levenberg–Marquardt)算法(基于eigen)

紧接上次的手写高斯牛顿,今天顺便将LM算法也进行了手写实现,并且自己基于eigen的高斯牛顿进行了对比,可以很明显的看到,LM的算法收敛更快,精度也略胜一筹,这次高博的书不够用了,参考网上伪代码进行实现.同学们有什么问题都可以讨论,理论部分的细节后面会补上,最近确实有点忙.

伪代码

在这里插入图片描述

程序源码

#include <eigen3/Eigen/Core>
#include <eigen3/Eigen/Dense>
#include"opencv2/opencv.hpp"
#include"opencv4/opencv2/opencv.hpp"
#include<iostream>
#include<vector>
using namespace Eigen;
using namespace std;
using namespace cv;
//生成测试数据
void makeTheTestNum(vector<double> &xSet ,vector<double> &ySet);
//LM算法结算
void LM(const vector<double> &xSet ,const vector<double> &ySet ,double &a,double &b,double &c);

void makeTheTestNum(vector<double> &xSet ,vector<double> &ySet){

    RNG rng;   
    double noise = rng.gaussian(1);
    //设定值
    double a = 2;
    double b = 1;
    double c = 1;
    for(int i = 0;i <100;i++)
    {   
        double x = i/100.0;//注意这个.0,不然出来全是0
        double fx = exp(a*x*x + b*x + c)+noise;
        xSet.push_back(x);
        ySet.push_back(fx);
    }
    cout<<xSet.size()<<endl;
    if(xSet.size() != ySet.size())
        cout<<"data is bad!"<<endl;

}

void LM(const vector<double> &xSet ,const vector<double> &ySet ,double &a,double &b,double &c){
    bool flag = 0;
    double cost =0.0;
    double lastcost = 0.0;
    int maxtimes = 1000;
    double v = 2;
    double rho = 0;
    double tao = 1e-10;
    //获得初值
    Matrix3d H = Matrix3d::Zero();
    Vector3d g = Vector3d::Zero();
    Vector3d J;
    //装填数据
    for(int j = 0;j< xSet.size();j++){  
        double x = xSet[j];
        double y = ySet[j];
        // cout<<"x" <<x<<endl;
        // cout<<"y" <<y<<endl;
        double e = y - exp(a*x*x + b*x + c);
        J[0] =  -exp(a*x*x + b*x + c)*x*x;
        J[1] = -exp(a*x*x + b*x + c)*x;
        J[2] = -exp(a*x*x + b*x + c)*1;

        Matrix3d tempH = J*J.transpose(); 
        H +=tempH;
        g += -J*e;
        cost +=e*e;
    }
    //设置这个u的初值
    double u = tao*H.maxCoeff();
    cout<<"init u :"<<u<<endl;
    cout<<"H init"<<H.matrix()<<endl;
    cout<<"J init"<<J.matrix()<<endl;
    cout<<"g init"<<g.matrix()<<endl;
    Matrix3d I = MatrixXd::Identity(3, 3);
    for(int i =0; i< maxtimes;i++){
        
        //使用eigen解算线性方程组,此处和GN的方程多了一个u*I,这就是LM的关键
        Matrix3d A = H+u*I;
        Vector3d delta_abc = A.ldlt().solve(g);
        //cout<<"delta_abc"<<delta_abc<<endl;
        //Vector3d delta_abc = H.ldlt().solve(g);
        if(delta_abc.norm()<1e-12){   
            flag =1;
            break;
                }
        cout<<"delta_abc"<<delta_abc.transpose()<<endl;
        //判断是否发散
        if(isnan(delta_abc[0])||isnan(delta_abc[1])||isnan(delta_abc[2])){   
            flag =0;
            break;
                }
        a += delta_abc[0];
        b += delta_abc[1];
        c += delta_abc[2];
        double cost_new = 0;
        for(int j = 0;j< xSet.size();j++){  
            double x = xSet[j];
            double y = ySet[j];
            double e = y - exp(a*x*x + b*x + c);
            cost_new +=e*e;
        }
        rho = (cost - cost_new)/(delta_abc.transpose()*(u*delta_abc+g));

        //LM的工作
        if(rho >0 ){
        	cost= 0;
            //注意初始化两个H和g,如果不是0会有很多奇怪的错误
            H = Matrix3d::Zero();
            g = Vector3d::Zero();
            J = Vector3d::Zero();
            //装填数据
            for(int j = 0;j< xSet.size();j++){  
                double x = xSet[j];
                double y = ySet[j];
                // cout<<"x" <<x<<endl;
                // cout<<"y" <<y<<endl;
                double e = y - exp(a*x*x + b*x + c);
                J[0] =  -exp(a*x*x + b*x + c)*x*x;
                J[1] = -exp(a*x*x + b*x + c)*x;
                J[2] = -exp(a*x*x + b*x + c)*1;

                Matrix3d tempH = J*J.transpose(); 
                H +=tempH;
                g += -J*e;
                cost +=e*e;
            }
            if(delta_abc.norm()<1e-12||cost<1e-12){   
                flag =1;
                break;
                }
            //更新u和v,缩小范围,更接近高斯牛顿
            u = u*max(0.3333333,(1-(2*rho-1)*(2*rho-1)*(2*rho-1)));
            v=2;
            }
            else{
            //不满足,扩大范围,更接近最速下降
                u = u * v;
                v = 2 * v;
            }
            cout<<"第"<<i<<"次:"<<endl;
            cout<<"a : "<<a<<endl;
            cout<<"b : "<<b<<endl;
            cout<<"c : "<<c<<endl;
            lastcost = cost;
            cout<<"delta_abc"<<delta_abc.norm()<<endl;
        }

        if(flag)
        {
                cout<<"已收敛,结果为:"<<endl;
                cout<<"final a : "<<a<<endl;
                cout<<"final b : "<<b<<endl;
                cout<<"final c : "<<c<<endl;
        }
        else
        {
            cout<<"发散了QAQ,最后一次结果"<<endl;
                cout<<"final a : "<<a<<endl;
                cout<<"final b : "<<b<<endl;
                cout<<"final c : "<<c<<endl;       
        }



}

int main(){
    cout.precision(9);
    //设置初始值
    double a = -3;
    double b = -1;
    double c = 7;
    //观测值
    vector<double> xSet ;
    vector<double> ySet;
    makeTheTestNum(xSet ,ySet);
    LM(xSet, ySet, a, b, c);
    return 0;
}

CMakeLists.txt

cmake_minimum_required( VERSION 2.8 )
project( guassNewton )

set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )



# Eigen
find_package(Eigen3 3.3 REQUIRED)
include_directories(${Eigen3_INCLUDE_DIRS})
# OpenCV
find_package(OpenCV 3.1 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})



add_executable( guassNewton  LM.cc )
target_link_libraries(guassNewton   ${OpenCV_LIBS} )

运行结果

0:
a : -2.42817009
b : -1.21346293
c : 6.01511826
delta_abc1.1586837
delta_abc  1.48927117 -0.567649404 -0.9591803921:
a : -0.93889892
b : -1.78111233
c : 5.05593786
delta_abc1.86015631
delta_abc  3.20365945  -1.27539914 -0.9020805132:
a : 2.26476053
b : -3.05651147
c : 4.15385735
delta_abc3.56424271
delta_abc  2.28598992 -0.697482592 -0.9022199713:
a : 4.55075046
b : -3.75399407
c : 3.25163738
delta_abc2.55464925
delta_abc-0.445211318    1.4096799 -0.9637589784:
a : 4.10553914
b : -2.34431417
c : 2.2878784
delta_abc1.76472148
delta_abc -1.31455441     2.111265 -0.8267599855:
a : 2.79098473
b : -0.233049172
c : 1.46111842
delta_abc2.62088254
delta_abc-0.691169818   1.07445643 -0.4001350066:
a : 2.09981491
b : 0.841407256
c : 1.06098341
delta_abc1.33876075
delta_abc-0.0980877612    0.15582017 -0.05990215437:
a : 2.00172715
b : 0.997227425
c : 1.00108126
delta_abc0.193621802
delta_abc-0.00172660324  0.00277169756 -0.001080913698:
a : 2.00000055
b : 0.999999123
c : 1.00000034
delta_abc0.00343974425
delta_abc-5.45923277e-07  8.78403636e-07 -3.43603167e-079:
a : 2
b : 1
c : 0.999999999
delta_abc1.08981113e-06
已收敛,结果为:
final a : 2
final b : 1
final c : 0.999999999

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Levenberg-Marquardt算法是非线性最小二乘问题的一种最优化方法,通常用于曲线拟合或参数估计问题。下面是该算法的一个简单实现示例: 假设我们有一组数据点(x,y),并且我们想要拟合一个函数f(x)来最小化均方误差。 首先,我们定义一个函数f(x,params)来表示所要拟合的函数,其中params代表待估参数。 下面是Levenberg-Marquardt算法的代码: ```python def levenberg_marquardt(x, y, param0, f, tol=1e-6, max_iter=1000): # x, y: 输入数据 # param0: 待估参数的初始值 # f: 所要拟合的函数 # tol: 收敛阈值 # max_iter: 最大迭代次数 # 返回:估计的参数值 # 初始化参数 params = np.array(param0) n = len(params) # 计算jacobian矩阵 def jacobian(x, params): h = 1e-6 J = np.zeros((len(x), n)) for i in range(n): p = params.copy() p[i] += h J[:, i] = (f(x, p) - f(x, params)) / h return J # 初始化lambda值和误差矩阵 lamda = 0.01 err = y - f(x, params) # 迭代 for i in range(max_iter): J = jacobian(x, params) # 计算增量 A = np.dot(J.T, J) + lamda * np.eye(n) b = np.dot(J.T, err) dp = np.linalg.solve(A, b) new_params = params + dp # 计算新的误差 new_err = y - f(x, new_params) if np.sum(new_err**2) < np.sum(err**2): params = new_params err = new_err lamda /= 10 else: lamda *= 10 # 判断是否达到收敛 if np.max(np.abs(dp)) < tol: return params # 达到最大迭代次数,返回估计的参数值 return params ``` 以上代码实现了Levenberg-Marquardt算法的主要步骤:计算jacobian矩阵、初始化lambda值和误差矩阵、计算增量并更新参数,以及判断是否达到收敛。 在实际使用中,可以根据具体的问题调整迭代次数、lambda值和收敛阈值等参数,以达到更好的拟合效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值