【问题标题】:How to use the Eigen unsupported levenberg marquardt implementation?如何使用 Eigen 不受支持的 levenberg marquardt 实现?
【发布时间】:2013-08-29 11:05:08
【问题描述】:

我正在尝试最小化以下示例函数:

F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-1])

最小化这种函数的一般方法是 Levenberg-Marquardt 算法。 我想在 C++ 中执行这个最小化并做了一些初步测试 使用 Eigen 得出预期的解决方案。

我的问题如下: 我习惯于在 python 中使用 scipy.optimize.fmin_powell 进行优化。这里 输入函数参数为(func, x0, args=(), xtol=0.0001, ftol=0.0001, maxiter=None, maxfun=None, full_output=0, disp=1, retall=0, callback=None, direc=None)。 所以我可以定义一个func(x0),给出x0 向量并开始优化。如果需要我可以改变 优化参数。

现在,Eigen Lev-Marq 算法以不同的方式工作。我需要定义一个函数 矢量(为什么?)此外,我无法设置优化参数。 根据:
http://eigen.tuxfamily.org/dox/unsupported/classEigen_1_1LevenbergMarquardt.html
我应该可以使用setEpsilon() 和其他设置功能。

但是当我有以下代码时:

my_functor functor;
Eigen::NumericalDiff<my_functor> numDiff(functor);
Eigen::LevenbergMarquardt<Eigen::NumericalDiff<my_functor>,double> lm(numDiff);
lm.setEpsilon(); //doesn't exist!

所以我有两个问题:

  1. 为什么需要函数向量,为什么函数标量不够?
    我搜索过答案的参考资料:
    http://www.ultimatepp.org/reference$Eigen_demo$en-us.html
    http://www.alglib.net/optimization/levenbergmarquardt.php

  2. 如何使用set函数设置优化参数?

【问题讨论】:

    标签: c++ optimization eigen


    【解决方案1】:

    所以我相信我已经找到了答案。

    1) 该函数可以作为函数向量和函数标量工作。
    如果有m 可解参数,则需要创建或数值计算m x m 的雅可比矩阵。为了进行矩阵向量乘法J(x[m]).transpose*f(x[m]),函数向量f(x) 应该有m 项。这可以是m不同的功能,但我们也可以给f1完整的功能,让其他项目0

    2) 可以使用lm.parameters.maxfev = 2000;设置和读取参数

    两个答案都已在以下示例代码中进行了测试:

    #include <iostream>
    #include <Eigen/Dense>
    
    #include <unsupported/Eigen/NonLinearOptimization>
    #include <unsupported/Eigen/NumericalDiff>
    
    // Generic functor
    template<typename _Scalar, int NX = Eigen::Dynamic, int NY = Eigen::Dynamic>
    struct Functor
    {
    typedef _Scalar Scalar;
    enum {
        InputsAtCompileTime = NX,
        ValuesAtCompileTime = NY
    };
    typedef Eigen::Matrix<Scalar,InputsAtCompileTime,1> InputType;
    typedef Eigen::Matrix<Scalar,ValuesAtCompileTime,1> ValueType;
    typedef Eigen::Matrix<Scalar,ValuesAtCompileTime,InputsAtCompileTime> JacobianType;
    
    int m_inputs, m_values;
    
    Functor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {}
    Functor(int inputs, int values) : m_inputs(inputs), m_values(values) {}
    
    int inputs() const { return m_inputs; }
    int values() const { return m_values; }
    
    };
    
    struct my_functor : Functor<double>
    {
    my_functor(void): Functor<double>(2,2) {}
    int operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec) const
    {
        // Implement y = 10*(x0+3)^2 + (x1-5)^2
        fvec(0) = 10.0*pow(x(0)+3.0,2) +  pow(x(1)-5.0,2);
        fvec(1) = 0;
    
        return 0;
    }
    };
    
    
    int main(int argc, char *argv[])
    {
    Eigen::VectorXd x(2);
    x(0) = 2.0;
    x(1) = 3.0;
    std::cout << "x: " << x << std::endl;
    
    my_functor functor;
    Eigen::NumericalDiff<my_functor> numDiff(functor);
    Eigen::LevenbergMarquardt<Eigen::NumericalDiff<my_functor>,double> lm(numDiff);
    lm.parameters.maxfev = 2000;
    lm.parameters.xtol = 1.0e-10;
    std::cout << lm.parameters.maxfev << std::endl;
    
    int ret = lm.minimize(x);
    std::cout << lm.iter << std::endl;
    std::cout << ret << std::endl;
    
    std::cout << "x that minimizes the function: " << x << std::endl;
    
    std::cout << "press [ENTER] to continue " << std::endl;
    std::cin.get();
    return 0;
    }
    

    【讨论】:

    • 我测试了您的示例代码,因为我需要做一些类似的事情,并注意到如果不是求和 10*pow(x(0)+3,2) + pow(x(1)-5, 2) 在 f(0) 和设置 f(1) 0 你把 10*pow(x(0)+3,2) 在 f(0) 和 pow(x(1)-5, 2) 在 f(1 ) 它收敛得更快。在 30 次迭代中,将术语分开,我能够达到 100 度的准确度,而按照您实现的方式,它花了大约 500 次。
    • 真的!这很可能是由于 NumericalDiff。我的问题是,是否可能只有一个函数标量,或者是否总是需要不同的函数向量。通过以这种方式编写它,我可以定义一个 f(0) 并将 f(1) 设置为零。为了让它更快,我可以将函数拆分为 f(0) 和 f(1),或者制作一个 df,以便不再需要 NumericalDiff 函数。此外,我停止使用此 Lev-Marq 算法并使用我自己的算法,以便为具有约 1500 个条目的函数向量提供更好的速度性能......
    • 上面的代码示例似乎来源于这个示例CurveFitting.cpp
    • 我正在使用相同的函数 Eigen::LevenbergMaquardt,我正在努力寻找一种方法来限制优化参数(LB、UB)。我是 C++ 新手..
    【解决方案2】:

    此答案是两个现有答案的扩展: 1) 我修改了@Deepfreeze 提供的源代码,以包含额外的 cmets 和两个不同的测试函数。 2)我使用@user3361661 的建议以正确的形式重写目标函数。正如他所建议的,它将我的第一个测试问题的迭代次数从 67 次减少到 4 次。

    #include <iostream>
    #include <Eigen/Dense>
    
    #include <unsupported/Eigen/NonLinearOptimization>
    #include <unsupported/Eigen/NumericalDiff>
    
    /***********************************************************************************************/
    
    // Generic functor
    // See http://eigen.tuxfamily.org/index.php?title=Functors
    // C++ version of a function pointer that stores meta-data about the function
    template<typename _Scalar, int NX = Eigen::Dynamic, int NY = Eigen::Dynamic>
    struct Functor
    {
    
      // Information that tells the caller the numeric type (eg. double) and size (input / output dim)
      typedef _Scalar Scalar;
      enum { // Required by numerical differentiation module
          InputsAtCompileTime = NX,
          ValuesAtCompileTime = NY
      };
    
      // Tell the caller the matrix sizes associated with the input, output, and jacobian
      typedef Eigen::Matrix<Scalar,InputsAtCompileTime,1> InputType;
      typedef Eigen::Matrix<Scalar,ValuesAtCompileTime,1> ValueType;
      typedef Eigen::Matrix<Scalar,ValuesAtCompileTime,InputsAtCompileTime> JacobianType;
    
      // Local copy of the number of inputs
      int m_inputs, m_values;
    
      // Two constructors:
      Functor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {}
      Functor(int inputs, int values) : m_inputs(inputs), m_values(values) {}
    
      // Get methods for users to determine function input and output dimensions
      int inputs() const { return m_inputs; }
      int values() const { return m_values; }
    
    };
    
    /***********************************************************************************************/
    
    // https://en.wikipedia.org/wiki/Test_functions_for_optimization
    // Booth Function
    // Implement f(x,y) = (x + 2*y -7)^2 + (2*x + y - 5)^2
    struct BoothFunctor : Functor<double>
    {
      // Simple constructor
      BoothFunctor(): Functor<double>(2,2) {}
    
      // Implementation of the objective function
      int operator()(const Eigen::VectorXd &z, Eigen::VectorXd &fvec) const {
        double x = z(0);   double y = z(1);
        /*
         * Evaluate the Booth function.
         * Important: LevenbergMarquardt is designed to work with objective functions that are a sum
         * of squared terms. The algorithm takes this into account: do not do it yourself.
         * In other words: objFun = sum(fvec(i)^2)
         */
        fvec(0) = x + 2*y - 7;
        fvec(1) = 2*x + y - 5;
        return 0;
      }
    };
    
    /***********************************************************************************************/
    
    // https://en.wikipedia.org/wiki/Test_functions_for_optimization
    // Himmelblau's Function
    // Implement f(x,y) = (x^2 + y - 11)^2 + (x + y^2 - 7)^2
    struct HimmelblauFunctor : Functor<double>
    {
      // Simple constructor
      HimmelblauFunctor(): Functor<double>(2,2) {}
    
      // Implementation of the objective function
      int operator()(const Eigen::VectorXd &z, Eigen::VectorXd &fvec) const {
        double x = z(0);   double y = z(1);
        /*
         * Evaluate Himmelblau's function.
         * Important: LevenbergMarquardt is designed to work with objective functions that are a sum
         * of squared terms. The algorithm takes this into account: do not do it yourself.
         * In other words: objFun = sum(fvec(i)^2)
         */
        fvec(0) = x * x + y - 11;
        fvec(1) = x + y * y - 7;
        return 0;
      }
    };
    
    /***********************************************************************************************/
    
    void testBoothFun() {
      std::cout << "Testing the Booth function..." << std::endl;
      Eigen::VectorXd zInit(2); zInit << 1.87, 2.032;
      std::cout << "zInit: " << zInit.transpose() << std::endl;
      Eigen::VectorXd zSoln(2); zSoln << 1.0, 3.0;
      std::cout << "zSoln: " << zSoln.transpose() << std::endl;
    
      BoothFunctor functor;
      Eigen::NumericalDiff<BoothFunctor> numDiff(functor);
      Eigen::LevenbergMarquardt<Eigen::NumericalDiff<BoothFunctor>,double> lm(numDiff);
      lm.parameters.maxfev = 1000;
      lm.parameters.xtol = 1.0e-10;
      std::cout << "max fun eval: " << lm.parameters.maxfev << std::endl;
      std::cout << "x tol: " << lm.parameters.xtol << std::endl;
    
      Eigen::VectorXd z = zInit;
      int ret = lm.minimize(z);
      std::cout << "iter count: " << lm.iter << std::endl;
      std::cout << "return status: " << ret << std::endl;
      std::cout << "zSolver: " << z.transpose() << std::endl;
      std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl;
    }
    
    /***********************************************************************************************/
    
    void testHimmelblauFun() {
      std::cout << "Testing the Himmelblau function..." << std::endl;
      // Eigen::VectorXd zInit(2); zInit << 0.0, 0.0;  // soln 1
      // Eigen::VectorXd zInit(2); zInit << -1, 1;  // soln 2
      // Eigen::VectorXd zInit(2); zInit << -1, -1;  // soln 3
      Eigen::VectorXd zInit(2); zInit << 1, -1;  // soln 4
      std::cout << "zInit: " << zInit.transpose() << std::endl;
      std::cout << "soln 1: [3.0, 2.0]" << std::endl;
      std::cout << "soln 2: [-2.805118, 3.131312]" << std::endl;
      std::cout << "soln 3: [-3.77931, -3.28316]" << std::endl;
      std::cout << "soln 4: [3.584428, -1.848126]" << std::endl;
    
      HimmelblauFunctor functor;
      Eigen::NumericalDiff<HimmelblauFunctor> numDiff(functor);
      Eigen::LevenbergMarquardt<Eigen::NumericalDiff<HimmelblauFunctor>,double> lm(numDiff);
      lm.parameters.maxfev = 1000;
      lm.parameters.xtol = 1.0e-10;
      std::cout << "max fun eval: " << lm.parameters.maxfev << std::endl;
      std::cout << "x tol: " << lm.parameters.xtol << std::endl;
    
      Eigen::VectorXd z = zInit;
      int ret = lm.minimize(z);
      std::cout << "iter count: " << lm.iter << std::endl;
      std::cout << "return status: " << ret << std::endl;
      std::cout << "zSolver: " << z.transpose() << std::endl;
      std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl;
    }
    
    /***********************************************************************************************/
    
    int main(int argc, char *argv[])
    {
    
    std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << std::endl;
    testBoothFun();
    testHimmelblauFun();
    return 0;
    }
    

    运行此测试脚本的命令行输出为:

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Testing the Booth function...
    zInit:  1.87 2.032
    zSoln: 1 3
    max fun eval: 1000
    x tol: 1e-10
    iter count: 4
    return status: 2
    zSolver: 1 3
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Testing the Himmelblau function...
    zInit:  1 -1
    soln 1: [3.0, 2.0]
    soln 2: [-2.805118, 3.131312]
    soln 3: [-3.77931, -3.28316]
    soln 4: [3.584428, -1.848126]
    max fun eval: 1000
    x tol: 1e-10
    iter count: 8
    return status: 2
    zSolver:  3.58443 -1.84813
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    【讨论】:

      【解决方案3】:

      您也可以像这样简单地创建一个新的仿函数,

      struct my_functor_w_df : Eigen::NumericalDiff<my_functor> {};
      

      然后像这样初始化 LevenbergMarquardt 实例,

      my_functor_w_df functor;
      Eigen::LevenbergMarquardt<my_functor_w_df> lm(functor);
      

      就个人而言,我觉得这种方法更简洁一些。

      【讨论】:

        【解决方案4】:

        好像功能比较通用:

        1. 假设您有一个 m 参数模型。
        2. 您有 n 个观测值,您希望以最小二乘的方式拟合 m 参数模型。
        3. Jacobian(如果提供)将是 n 乘以 m。

        您需要在 fvec 中提供 n 个错误值。 此外,不需要平方 f 值,因为它隐含地假设整体误差函数由 fvec 分量的平方和组成。

        因此,如果您遵循这些准则并将代码更改为:

        fvec(0) = sqrt(10.0)*(x(0)+3.0);
        fvec(1) = x(1)-5.0;
        

        它将以极少的迭代次数收敛——比如少于 5 次。我还在一个更复杂的例子上进行了尝试——http://www.itl.nist.gov/div898/strd/nls/data/hahn1.shtml 的 Hahn1 基准测试,m=7 参数和 n=236 个观察值,它收敛到使用数值计算的雅可比行列式,只需 11 次迭代即可得到已知正确解。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-12
          相关资源
          最近更新 更多