LM、GN算法原理及实现

1、LM算法原理

2、 高斯-牛顿算法

f(x)进行一阶展开为:f(x+\Delta x) = f(x)+J(x)\Delta x

目标函数可转化为 \mathop{\arg\min}_{\Delta x} \frac{1}{2}\left \|f(x) +J(x) \Delta x \right \|^2

上式对\Delta x求导得:J(x)^TJ(x) \Delta x = -J(x)^Tf(x)

简化为H\Delta x = g,即为高斯-牛顿算法的更新方程

3、GN与LM的区别

LM会拒绝可能导致更大的残差平方和的更新,速度也可能会慢些,而使用GN必须保证H的可逆性。

4、LM的c++简单实现

#include <bits/stdc++.h>
#include <eigen3/Eigen/Dense>

using namespace std;
using namespace Eigen;

vector<Vector2d> obs_;
int max_iterations_ = 100;
int max_inner_iterations_ = 10;
int lambda_;
VectorXd errors_;
Matrix3d hessian_;
Vector3d hb_;
MatrixXd jacobian_;
double tau_ = 1e-5;
double epsilon_ = 1e-5;
double current_squared_error;
double a_,b_,c_;

void computeJacobian();
void computeHb();
void initLambda();

int main(){
    double a = 1, b = 2 , c= 3;
    a_ = 0, b_= 0, c_=0;
    std::default_random_engine generator;
    std::normal_distribution<double> noise(0,0.1);
    for(int i=0;i<100;i++){
        Vector2d ob;
        ob(0) = i;
        ob(1) = a*i*i+b*i+c+noise(generator);
        obs_.push_back(ob);
    }
    computeJacobian();
    computeHb();
    initLambda();
    int iter = 0;
    double delta_norm; //每次优化增量的大小
    double v = 2.0;
    while (iter++<max_iterations_)
    {
        current_squared_error = errors_.squaredNorm();
        int inner_iter = 0;
        while (inner_iter++ < max_inner_iterations_)
        {
            Matrix3d damper = lambda_ * Matrix3d::Identity();
            Vector3d delta = (hessian_ + damper).inverse()*hb_;
            double aa = a_ + delta(0);
            double bb = b_ + delta(1);
            double cc = c_ + delta(2);
            delta_norm = delta.norm();
            double new_squared_error = 0.0;
            for(int i = 0;i<obs_.size();i++){
                double xi = obs_[i](0);
                double yi = obs_[i](1);
                double yy = aa*xi*xi+bb*xi+cc;
                new_squared_error += pow(yy-yi,2);
            }
            double rou = (current_squared_error-new_squared_error)/ 
                        (0.5*delta.transpose()*(lambda_*delta+hb_)+1e-3);
            if(rou>0){
                a_= aa,b_ = bb,c_=cc;
                lambda_ = lambda_*max(1.0/3.0, 1-pow((2*rou-1),3));
                v = 2;
                break;
            }else{
                lambda_ = lambda_ *v;
                v *= 2;
            }
        }
        if(delta_norm< epsilon_){
            break;
        }
        computeJacobian();
        computeHb();
    }
    cout<< a_<<" "<<b_<<" "<<c_<<endl;
    return 0;
}


void initLambda(){
    double max_diagnal = 0.;
    for(int i=0;i<3;i++){
        max_diagnal = max(fabs(hessian_(i,i)), max_diagnal);
    }
    lambda_ = tau_ * max_diagnal;
}

void computeJacobian(){
    jacobian_.resize(obs_.size(),3);
    errors_.resize(obs_.size());
    for(int i=0;i<obs_.size();i++){
        double xi = obs_[i][0];
        double yi = obs_[i][1];
        Matrix<double,1,3> jac;
        double yy = a_*xi*xi+b_*xi+c_;
        jac(0,0) = xi*xi;
        jac(0,1) = xi;
        jac(0,2) = 1;
        jacobian_.row(i) = jac;
        errors_(i) = yy-yi;
    }
}

void computeHb(){
    hessian_ = jacobian_.transpose()*jacobian_;
    hb_  = -jacobian_.transpose()*errors_;
}

 

5、Ceres示例

#include<bits/stdc++.h>
#include<eigen3/Eigen/Dense>
#include<ceres/ceres.h>
using namespace std;
using namespace Eigen;

struct costFunc
{
    costFunc(double x, double y):x_(x),y_(y){}
    template<typename T>
    bool operator()(const T* const a,const T* const b, const T* const c, T* residual) const{
        residual[0] = T(y_) - (a[0]*x_*x_+b[0]*x_+c[0]);
        return true;
    }
    const double x_;
    const double y_;
};


int main(){
    vector<Vector2d> obs;
    std::default_random_engine generator;
    std::normal_distribution<double> noise(0,0.1);
    double a = 1.,b = 2., c= 3.;
    double a0 = 0,b0= 0,c0 = 0;
    cout<< "original: " << a0<<" "<<b0<<" "<<c0<<endl;
    for(auto x=1.0;x<100;x+=1.){
        double y = a*x*x+b*x+c + noise(generator);
        obs.push_back(Vector2d(x,y));
    }

    ceres::Problem problem;
    for(int i=0;i<obs.size();i++){
        ceres::CostFunction* pCostFunction = new ceres::AutoDiffCostFunction<costFunc,1,1,1,1>(
            new costFunc(obs[i][0],obs[i][1]));
        problem.AddResidualBlock(pCostFunction, nullptr, &a0,&b0,&c0);
    }

    ceres::Solver::Options options;
    options.linear_solver_type = ceres::DENSE_QR;
    options.minimizer_progress_to_stdout = true;
    ceres::Solver::Summary summary;
    ceres::Solve(options,&problem,&summary);

    cout<< summary.BriefReport()<<endl;
    cout<< "optimized: " << a0<<" "<<b0<<" "<<c0<<endl;
    return 0;
}

解析求导形式:

#include<bits/stdc++.h>
#include<eigen3/Eigen/Dense>
#include<ceres/ceres.h>
using namespace std;
using namespace Eigen;

class CostFunc: public ceres::SizedCostFunction<1,3>{
public:
    CostFunc(double x , double y):x_(x),y_(y){}
    virtual ~CostFunc(){}
    virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const{
        const double a = parameters[0][0];
        const double b = parameters[0][1];
        const double c = parameters[0][2];

        residuals[0] = a*x_*x_+b*x_+c -y_;
        if(!jacobians) return true;
        double* jac = jacobians[0];
        if(!jac) return true;
        jac[0] = x_*x_;
        jac[1] = x_;
        jac[2] = 1;
        return true;
    }

private:
    const double x_;
    const double y_;
};


int main(){
    vector<Vector2d> obs;
    std::default_random_engine generator;
    std::normal_distribution<double> noise(0,0.1);
    double a = 1.,b = 2., c= 3.;
    double a0 = 0,b0= 0,c0 = 0;
    cout<< "original: " << a0<<" "<<b0<<" "<<c0<<endl;
    for(auto x=1.0;x<100;x+=1.){
        double y = a*x*x+b*x+c + noise(generator);
        obs.push_back(Vector2d(x,y));
    }
    double param[3] = {a0,b0,c0};
    ceres::Problem problem;
    for(int i=0;i<obs.size();i++){
        ceres::CostFunction* pCostFunction = new CostFunc(obs[i][0],obs[i][1]);
        problem.AddResidualBlock(pCostFunction, nullptr, param);
    }

    ceres::Solver::Options options;
    options.linear_solver_type = ceres::DENSE_QR;
    options.minimizer_progress_to_stdout = true;
    ceres::Solver::Summary summary;
    ceres::Solve(options,&problem,&summary);

    cout<< summary.BriefReport()<<endl;
    cout<< "optimized: " << param[0]<<" "<<param[1]<<" "<<param[2]<<endl;
    return 0;
}

 

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LM(Levenberg-Marquardt)算法是一种非线性最小二乘优化算法,用于解决非线性参数拟合问题。PyTorch是一个深度学习框架,主要用于神经网络的训练和推理。PyTorch本身没有直接实现LM算法,但可以使用PyTorch提供的优化器来实现类似的功能。 下面是一个使用PyTorch实现非线性参数拟合的示例代码,其中使用了LM算法的一个变种——LM-BFGS优化器: ```python import torch from scipy.optimize import curve_fit import matplotlib.pyplot as plt # 定义目标函数 def func(x, a, b, c): return a * torch.sin(b * x) + c # 生成模拟数据 x = torch.linspace(0, 2 * torch.pi, 100) y = func(x, 2.5, 1.3, 0.8) + 0.1 * torch.randn_like(x) # 定义损失函数 def loss_fn(params): a, b, c = params y_pred = func(x, a, b, c) loss = torch.mean((y_pred - y)**2) return loss # 使用scipy中的curve_fit函数进行参数拟合 params_init = torch.tensor([1.0, 1.0, 1.0], requires_grad=True) params_opt, _ = curve_fit(func, x.numpy(), y.numpy(), p0=params_init.detach().numpy()) params_opt = torch.tensor(params_opt) # 使用LM-BFGS优化器进行参数拟合 optimizer = torch.optim.LBFGS([params_init]) for _ in range(100): def closure(): optimizer.zero_grad() loss = loss_fn(params_init) loss.backward() return loss optimizer.step(closure) # 绘制拟合结果 plt.plot(x.numpy(), y.numpy(), 'r', label='Original') plt.plot(x.numpy(), func(x, *params_opt.numpy()), 'g--', label='Curve_fit') plt.plot(x.numpy(), func(x, *params_init.detach().numpy()), 'b--', label='LM-BFGS') plt.legend() plt.show() ``` 在上述代码中,首先定义了一个目标函数`func`,用于生成模拟数据。然后使用该目标函数生成一组带有噪声的模拟数据。 接下来,定义了损失函数`loss_fn`,用于计算模型的预测值与真实值之间的均方误差。然后,使用scipy中的`curve_fit`函数进行参数拟合,得到LM算法的拟合结果作为对照。 最后,使用PyTorch的`torch.optim.LBFGS`优化器进行LM-BFGS优化算法的参数拟合。通过多次迭代调用优化器的`step`方法,可以实现参数的更新和优化。 最后,使用matplotlib库将原始数据、curve_fit的拟合结果和LM-BFGS的拟合结果进行可视化展示。 需要注意的是,PyTorch主要用于深度学习任务,对于一般的非线性参数拟合问题,LM算法实现可能更适合使用scipy等专门的数值计算库。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值