簡體   English   中英

在 C++ 中擬合兩個自變量的非線性最小二乘:實現 GSL 算法

[英]Nonlinear least-squares fitting with two independent variables in C++: implementing GSL algorithm

繼我在Fixing parameters of afitting function in Nonlinear Least-Square GSL (@zkoza 成功回答)中提出的上一個問題之后,我想通過修復一些來實現一種可以將數據擬合到非線性函數的算法保留其參數,同時更改其他參數以找到最適合數據的參數。 與我之前的問題的不同之處在於我想要兩個自變量而不是一個自變量。

用於擬合數據的非線性函數

double gaussian(double x, double b, double a, double c)
{
    const double z = (x - b) / c;
    return a * std::exp(-0.5 * z * z);
}

在我之前的問題中,我認為x是唯一的independent variable 現在我想考慮兩個自變量xb

用於僅使用一個independent variable (同時固定變量a )擬合非線性函數的原始算法是 GSL 非線性最小二乘算法的 C++ 包裝器(借自https://github.com/Eleobert/gsl-curve -fit/blob/master/example.cpp ):

template <typename F, size_t... Is>
auto gen_tuple_impl(F func, std::index_sequence<Is...> )
{
    return std::make_tuple(func(Is)...);
}

template <size_t N, typename F>
auto gen_tuple(F func)
{
    return gen_tuple_impl(func, std::make_index_sequence<N>{} );
}

template <class R, class... ARGS>
struct function_ripper {
    static constexpr size_t n_args = sizeof...(ARGS);
};

template <class R, class... ARGS>
auto constexpr n_params(R (ARGS...) )
{
    return function_ripper<R, ARGS...>();
}


auto internal_solve_system(gsl_vector* initial_params, gsl_multifit_nlinear_fdf *fdf,
             gsl_multifit_nlinear_parameters *params) -> std::vector<double>
{
  // This specifies a trust region method
  const gsl_multifit_nlinear_type *T = gsl_multifit_nlinear_trust;
  const size_t max_iter = 200;
  const double xtol = 1.0e-8;
  const double gtol = 1.0e-8;
  const double ftol = 1.0e-8;

  auto *work = gsl_multifit_nlinear_alloc(T, params, fdf->n, fdf->p);
  int info;

  // initialize solver
  gsl_multifit_nlinear_init(initial_params, fdf, work);
  //iterate until convergence
  gsl_multifit_nlinear_driver(max_iter, xtol, gtol, ftol, nullptr, nullptr, &info, work);

  // result will be stored here
  gsl_vector * y    = gsl_multifit_nlinear_position(work);
  auto result = std::vector<double>(initial_params->size);

  for(int i = 0; i < result.size(); i++)
  {
    result[i] = gsl_vector_get(y, i);
  }

  auto niter = gsl_multifit_nlinear_niter(work);
  auto nfev  = fdf->nevalf;
  auto njev  = fdf->nevaldf;
  auto naev  = fdf->nevalfvv;

  // nfev - number of function evaluations
  // njev - number of Jacobian evaluations
  // naev - number of f_vv evaluations
  //logger::debug("curve fitted after ", niter, " iterations {nfev = ", nfev, "} {njev = ", njev, "} {naev = ", naev, "}");

  gsl_multifit_nlinear_free(work);
  gsl_vector_free(initial_params);
  return result;
}

template<auto n>
auto internal_make_gsl_vector_ptr(const std::array<double, n>& vec) -> gsl_vector*
{
    auto* result = gsl_vector_alloc(vec.size());
    int i = 0;
    for(const auto e: vec)
    {
        gsl_vector_set(result, i, e);
        i++;
    }
    return result;
}


template<typename C1>
struct fit_data
{
    const std::vector<double>& t;
    const std::vector<double>& y;
    // the actual function to be fitted
    C1 f;
};


template<typename FitData, int n_params>
int internal_f(const gsl_vector* x, void* params, gsl_vector *f)
{
    auto* d  = static_cast<FitData*>(params);
    // Convert the parameter values from gsl_vector (in x) into std::tuple
    auto init_args = [x](int index)
    {
        return gsl_vector_get(x, index);
    };
    auto parameters = gen_tuple<n_params>(init_args);

    // Calculate the error for each...
    for (size_t i = 0; i < d->t.size(); ++i)
    {
        double ti = d->t[i];
        double yi = d->y[i];
        auto func = [ti, &d](auto ...xs)
        {
            // call the actual function to be fitted
            return d->f(ti, xs...);
        };
        auto y = std::apply(func, parameters);
        gsl_vector_set(f, i, yi - y);
    }
    return GSL_SUCCESS;
}

using func_f_type   = int (*) (const gsl_vector*, void*, gsl_vector*);
using func_df_type  = int (*) (const gsl_vector*, void*, gsl_matrix*);
using func_fvv_type = int (*) (const gsl_vector*, const gsl_vector *, void *, gsl_vector *);

template<auto n>
auto internal_make_gsl_vector_ptr(const std::array<double, n>& vec) -> gsl_vector*;


auto internal_solve_system(gsl_vector* initial_params, gsl_multifit_nlinear_fdf *fdf,
             gsl_multifit_nlinear_parameters *params) -> std::vector<double>;

template<typename C1>
auto curve_fit_impl(func_f_type f, func_df_type df, func_fvv_type fvv, gsl_vector* initial_params, fit_data<C1>& fd) -> std::vector<double>
{
    assert(fd.t.size() == fd.y.size());

    auto fdf = gsl_multifit_nlinear_fdf();
    auto fdf_params = gsl_multifit_nlinear_default_parameters();

    fdf.f   = f;
    fdf.df  = df;
    fdf.fvv = fvv;
    fdf.n   = fd.t.size();
    fdf.p   = initial_params->size;
    fdf.params = &fd;

    // "This selects the Levenberg-Marquardt algorithm with geodesic acceleration."
    fdf_params.trs = gsl_multifit_nlinear_trs_lmaccel;
    return internal_solve_system(initial_params, &fdf, &fdf_params);
}


template <typename Callable, auto n>
auto curve_fit(Callable f, const std::array<double, n>& initial_params, const std::vector<double>& x, const std::vector<double>& y) -> std::vector<double>
{
    // We can't pass lambdas without convert to std::function.
    //constexpr auto n = 3;//decltype(n_params(f))::n_args - 5;
    //constexpr auto n = 2;
    assert(initial_params.size() == n);

    auto params = internal_make_gsl_vector_ptr(initial_params);
    auto fd = fit_data<Callable>{x, y, f};
    return curve_fit_impl(internal_f<decltype(fd), n>, nullptr, nullptr, params,  fd);
}

為了修復高斯函數的參數之一,@zkoza 建議使用functors

struct gaussian_fixed_a
{
    double a;
    gaussian_fixed_a(double a) : a{a} {}
    double operator()(double x, double b, double c) const { return gaussian(x, b, a, c); }
};

最后幾行顯示了我將如何創建一個觀察數據的假數據集(帶有一些正態分布的噪聲),並使用向量xsbs給出的兩個自變量測試擬合曲線函數。

    int main()
    {
        auto device = std::random_device();
        auto gen    = std::mt19937(device());
    
        auto xs = linspace<std::vector<double>>(0.0, 1.0, 300);
        auto bs = linspace<std::vector<double>>(0.4, 1.4, 300);
        auto ys = std::vector<double>(xs.size());
    
        double a = 5.0, c = 0.15;
    
        for(size_t i = 0; i < xs.size(); i++)
        {

            auto y =  gaussian(xs[i], a, bs[i], c);
            auto dist  = std::normal_distribution(0.0, 0.1 * y);
            ys[i] = y + dist(gen);
        }
        gaussian_fixed_a g(a);
        auto r = curve_fit(g, std::array{0.11}, xs, bs, ys);
    
        std::cout << "result: " << r[0] << ' ' << '\n';
        std::cout << "error : " << r[0] - c << '\n';
    
    }

您對如何實現two-independent variables非線性擬合有任何想法嗎?

正如@BenVoigt 在評論中所建議的那樣,解決方案是將高斯函數中的xb自變量替換為作為向量給出的“一個自變量”,其第一個元素是x ,第二個元素是b

非線性擬合的主干也需要稍微編輯。 編輯包括:

  1. fit_data函子替換為:

     struct fit_data { const std::vector< vector<double> > &t; const std::vector<double>& y; // the actual function to be fitted C1 f; };

這樣,自變量不再是vector ,而是vector of a vector (又名matrix )。

  1. 在函數internal_f替換。

a) double ti = d->t[i] with std::vector<double> ti = d->t[i] b) auto func = [ti, &d](auto ...xs) with auto func = [ti, &d](auto ...xs_matrix) c) return d->f(ti, xs...) with return d->f(ti, xs_matrix...)

  1. curve_fit函數內替換:

a) const std::vector<double>& x with const std::vector< vector<double> > &xs_matrix b) auto fd = fit_data<Callable>{x, y, f} with auto fd = fit_data<Callable>{xs_matrix, y, f}

gaussian函數、 gaussian_fixed_a函子和擬合函數如下所示:

double gaussian(std::vector<double> x_vector, double a, double c)
    {
        const double z = (x_vector[0] - x_vector[1]) / c;
        return a * std::exp(-0.5 * z * z);
    }

struct gaussian_fixed_a
{
    double a;
    gaussian_fixed_a(double a) : a{a} {}
    double operator()(std::vector<double> x_vector, double c) const { return gaussian(x_vector, a, c); }
};

double fittingTest(const std::vector< vector<double> > &xs_matrix, const std::vector<double> ys, const double a){

      gaussian_fixed_a g(a);
      auto r = curve_fit(g, std::array{3.0}, xs_matrix, ys);

        return r[0]);

        }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM