Как использовать неподдерживаемую реализацию Eigen levenberg marquardt?

Я пытаюсь минимизировать следующую примерную функцию:

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

Нормальным способом минимизации такой функции может быть алгоритм Левенберга-Марквардта. Я хотел бы выполнить эту минимизацию на С++ и провел несколько начальных тестов с 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 и начать оптимизацию. При необходимости я могу изменить параметры оптимизации.

Теперь алгоритм Эйгена Лева-Марка работает по-другому. Мне нужно определить вектор функции (почему?) Кроме того, я не могу установить параметры оптимизации. Согласно:
http://eigen.tuxfamily.org/dox/unsupported/classEigen

F(x) = f[0]^2(x[0],...,x[n-1]) + ... + f[m-1]^2(x[0],...,x[n-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!

Итак, у меня есть 2 вопроса:

  1. Зачем нужен вектор функции и почему скалярной функции недостаточно?
    Ссылки, где я искал ответ:
    http://www.ultimatepp.org/reference%24Eigen_demo%24en-us.html
    http://www.alglib.net/optimization/levenbergmarquardt.php

  2. Как установить параметры оптимизации с помощью установленных функций?


person Deepfreeze    schedule 29.08.2013    source источник


Ответы (4)


Так что я считаю, что нашел ответы.

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;
}
person Deepfreeze    schedule 30.08.2013
comment
Я проверил ваш пример кода, так как мне нужно было сделать что-то подобное, и заметил, что если вместо суммирования 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. - person coderdave; 06.11.2013
comment
Истинный! Скорее всего, это связано с NumericalDiff. У меня возник вопрос, возможно ли вообще иметь только один скаляр функции или всегда нужны разные векторы функций. Написав это таким образом, я могу определить f(0) и установить f(1) равным нулю. Чтобы сделать это быстрее, я мог бы действительно разделить функцию на f (0) и f (1) или сделать df, чтобы функция NumericalDiff больше не нужна. Кроме того, я перестал использовать этот алгоритм Лев-Марка и использую свой собственный, чтобы иметь лучшую производительность по скорости для вектора функции с ~ 1500 элементами... - person Deepfreeze; 19.11.2013
comment
Приведенный выше пример кода, по-видимому, получен из этого примера noreferrer">CurveFitting.cpp. - person nils; 22.10.2015
comment
Я использую ту же функцию Eigen::LevenbergMaquardt, я изо всех сил пытаюсь найти способ ограничить оптимизированные параметры (LB, UB). Я новичок в С++.. - person TonyParker; 16.04.2017

Этот ответ является расширением двух существующих ответов: 1) я адаптировал исходный код, предоставленный @Deepfreeze, для включения дополнительных комментариев и двух разных тестовых функций. 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
person MattKelly    schedule 02.03.2018

В качестве альтернативы вы можете просто создать новый функтор, подобный этому,

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

а затем инициализируйте экземпляр LevenbergMarquardt следующим образом:

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

Лично я нахожу этот подход немного чище.

person user3465408    schedule 26.03.2014

Кажется, что функция более общая:

  1. Допустим, у вас есть модель с параметрами m.
  2. У вас есть n наблюдений, к которым вы хотите подобрать модель m-параметров методом наименьших квадратов.
  3. Якобиан, если он задан, будет n раз m.

Вам нужно будет указать n значений ошибок в файле fvec. Кроме того, нет необходимости возводить в квадрат f-значения, поскольку неявно предполагается, что общая функция ошибок состоит из суммы квадратов компонентов fvec.

Итак, если вы будете следовать этим рекомендациям и измените код на:

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

Он сойдется за смехотворно малое количество итераций — например, менее 5. Я также попробовал это на более сложном примере — тесте Hahn1 на http://www.itl.nist.gov/div898/strd./nls/data/hahn1.shtml с параметрами m=7 и n= 236 наблюдений, и он сходится к известному правильному решению всего за 11 итераций с численно вычисленным якобианом.

person user3361661    schedule 27.02.2014