修复非线性最小二乘 GSL 中拟合函数的参数

2023-12-29

我正在编写一些代码,这些代码使用 [GNU 科学库 (GSL)][1] 的非线性最小二乘算法进行曲线拟合。

我已经成功地获得了一个工作代码,该代码使用来自的 C++ 包装器从拟合分析中估计了正确的参数https://github.com/Eleobert/gsl-curve-fit/blob/master/example.cpp https://github.com/Eleobert/gsl-curve-fit/blob/master/example.cpp.

现在,我想修复函数的一些参数以使其适合。我想修改该函数,以便我已经可以输入要固定的参数值。

关于如何做有任何想法吗? 我在这里展示完整的代码。

这是执行非线性最小二乘拟合的代码:

#include <gsl/gsl_vector.h>
#include <gsl/gsl_multifit_nlinear.h>
#include <iostream>
#include <random>
#include <vector>
#include <cassert>
#include <functional>

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>{} );
}

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;
}

auto internal_make_gsl_vector_ptr(const std::vector<double>& 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 *);


auto internal_make_gsl_vector_ptr(const std::vector<double>& 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 curve_fit(Callable f, const std::vector<double>& 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;
    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);
}

// linspace from https://github.com/Eleobert/meth/blob/master/interpolators.hpp
template <typename Container>
auto linspace(typename Container::value_type a, typename Container::value_type b, size_t n)
{
    assert(b > a);
    assert(n > 1);

    Container res(n);
    const auto step = (b - a) / (n - 1);
    auto val = a;
    for(auto& e: res)
    {
        e = val;
        val += step;
    }
    return res;
}

这是我用于拟合的函数:

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

最后几行创建了观察数据的假数据集(带有一些正态分布的噪声)并测试拟合曲线函数。

int main()
{
    auto device = std::random_device();
    auto gen    = std::mt19937(device());

    auto xs = linspace<std::vector<double>>(0.0, 1.0, 300);
    auto ys = std::vector<double>(xs.size());

    double a = 5.0, b = 0.4, c = 0.15;

    for(size_t i = 0; i < xs.size(); i++)
    {
        auto y =  gaussian(xs[i], a, b, c);
        auto dist  = std::normal_distribution(0.0, 0.1 * y);
        ys[i] = y + dist(gen);
    }

    auto r = curve_fit(gaussian, {1.0, 0.0, 1.0}, xs, ys);

    std::cout << "result: " << r[0] << ' ' << r[1] << ' ' << r[2] << '\n';
    std::cout << "error : " << r[0] - a << ' ' << r[1] - b << ' ' << r[2] - c << '\n';

}

在这种情况下,我想修复其中之一a, b, c参数并估计其余两个。例如,修复a并估计b and c。但我想找到一个解决方案,以便我可以向固定参数输入任何值a,无需每次都修改高斯函数。


好的。这是基于 linkedin 代码的答案http://github.com/Eleobert/gsl-curve-fit/blob/master/example.cpp http://github.com/Eleobert/gsl-curve-fit/blob/master/example.cpp。但是,这不是问题中发布的代码:您应该相应地更新您的问题,以便其他人可以从问题和答案中受益。

因此,基本上,主要问题是 GSL 是一个用纯 C 编写的库,而您使用的是用 C++ 编写的高级包装器,在上述链接中发布。虽然包装器用现代 C++ 写得很好,但它有一个基本问题:它是“僵化的”——它只能用于它设计的问题的子类,而这个子类是所提供功能的相当狭窄的子集由原始的C代码。

让我们尝试对其进行一些改进,并从包装器的使用方式开始:

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

int main()
{
    auto device = std::random_device();
    auto gen = std::mt19937(device());

    auto xs = linspace<std::vector<double>>(0.0, 1.0, 300);
    auto ys = std::vector<double>(xs.size());

    double a = 5.0, b = 0.4, c = 0.15;

    for (size_t i = 0; i < xs.size(); i++)
    {
        auto y = gaussian(xs[i], a, b, c);
        auto dist = std::normal_distribution(0.0, 0.1 * y);
        ys[i] = y + dist(gen);
    }

    auto result = curve_fit(gaussian, {1.0, 0.0, 1.0}, xs, ys);
   // use result
}

与原始的 C 语言代码相比,这段代码简单得惊人。一是初始化x-y值对,此处存储为向量xs and ys并执行一个带有 4 个易于理解的参数的函数:要拟合数据的函数、函数所依赖的拟合参数的初始值、x值和相应的y函数必须拟合的数据值。

您的问题是如何保留这个高级接口,但将其用于拟合函数,其中只有一些参数是“自由”的,即可以在拟合过程中更改,而其他参数的值必须固定。这可以很容易地实现,例如使用函数可以访问的全局变量,但我们讨厌全局变量,并且在没有真正原因的情况下从不使用它们。

我建议使用众所周知的 C++ 替代方案:函子。看:

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, a, b, c); }
};

This struct/class引入了一种新类型的函数对象。在构造函数中,参数a被传递并存储在一个对象中。然后,有一个函数调用运算符只需要 3 个参数而不是 4 个,替换为a从它的存储值。这个对象可以假装是高斯分布a固定常量和只有其他 3 个参数,x, b, and c,这可能会有所不同。

我们想这样使用它:

    gaussian_fixed_a g(a);
    auto r2 = curve_fit(g, std::array{0.444, 0.11}, xs, ys);

这与您用于原始包装器的代码几乎相同,但有两个差异:

  1. 您现在使用对象名称(此处:g) 而不是函数名
  2. 您必须将参数数量传递给curve_fit as a 编译时常量,因为它随后在内部使用它来调用由该数字参数化的模板。我通过使用实现它std::array,编译器可以根据需要在编译时推断其大小。另一种选择是使用令人讨厌的模板语法,curve_fit<2>(...

为此,您需要更改接口curve_fit, from

template <typename Callable>
auto curve_fit(Callable f, const std::vector<double>& initial_params, const std::vector<double>& x,
               const std::vector<double>& y) -> std::vector<double>

to

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>

(顺便说一句:这个->右侧带有众所周知类型的语法不是最好的语法,恕我直言,但就这样吧)。这个想法是强制编译器在编译时从参数的大小中读取拟合参数的数量。array.

然后你需要在参数列表中进行类似的调整curve_fit_impl——差不多就是这样了。

在这里,我花了很多时间试图找出为什么这段代码不起作用。事实证明它一直有效,秘密在于,如果您将函数拟合到某些数据,则最好提供相当接近解决方案的初始值。这就是为什么使用这个初始化器std::array{0.444, 0.11}而不是原来的{0, 1},因为后者不会收敛到任何接近正确答案的结果。

我们真的需要使用显式函数对象吗?也许 lambda 可以吗?是的,他们会的 - 这会按预期编译和运行:

auto r3 = curve_fit([a](double x, double b, double c) { return gaussian(x, a, b, c); }, std::array{0.444, 0.11}, xs, ys);

这是完整的diff原始代码和修改后的代码之间(没有 lambda):

7a8
> #include <array>
72c73,74
< auto internal_make_gsl_vector_ptr(const std::vector<double>& vec) -> gsl_vector*
---
> template<auto n>
> auto internal_make_gsl_vector_ptr(const std::array<double, n>& vec) -> gsl_vector*
158,159c160,161
< template <typename Callable>
< auto curve_fit(Callable f, const std::vector<double>& initial_params, const std::vector<double>& x,
---
> template <typename Callable, auto n>
> auto curve_fit(Callable f, const std::array<double, n>& initial_params, const std::vector<double>& x,
163,164c165,166
<     constexpr auto n = 3;
<     assert(initial_params.size() == n);
---
>     //    constexpr auto n = 2;
>     //    assert(initial_params.size() == n);
194a197,204
> 
> 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, a, b, c); }
> };
> 
212c222,224
<     auto r = curve_fit(gaussian, {1.0, 0.0, 1.0}, xs, ys);
---
>     auto r = curve_fit(gaussian, std::array{1.0, 0.0, 1.0}, xs, ys);
>     gaussian_fixed_a g(a);
>     auto r2 = curve_fit(g, std::array{0.444, 0.11}, xs, ys);
215a228,230
>     std::cout << "\n";
>     std::cout << "result: " << r2[0] << ' ' << r2[1] << '\n';
>     std::cout << "error : " << r2[0] - b << ' ' << r2[1] - c << '\n';
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

修复非线性最小二乘 GSL 中拟合函数的参数 的相关文章

  • 如何在 Visual Studio 2010 中增强 XAML 设计器?

    当我使用 XAML 设计器时 进入设计器和退出设计器是如此困难和缓慢 当我这样做时 Visual Studio 卡了一段时间 有什么方法可以增强 XAML 设计器和编辑器吗 Ant 保存 XAML 文件时非常慢 这通常意味着您可能有复杂的
  • c和java语言中的换行符

    现在行分隔符取决于系统 但在 C 程序中我使用 n 作为行分隔符 无论我在 Windows 还是 Linux 中运行它都可以正常工作 为什么 在java中 我们必须使用 n 因为它与系统相关 那么为什么我们在c中使用 n 作为新行 而不管我
  • 在 C# 中创建具有单独列的分隔文本

    我一直在尝试在 C 中创建一个制表符限制的文本文件 以便数据正确显示在单独的列中 Firstname Lastname Age John Smith 17 James Sawyer 31 我尝试过 t 字符 但我得到的只是 Firstnam
  • 如何为 C 分配的 numpy 数组注册析构函数?

    我想在 C C 中为 numpy 数组分配数字 并将它们作为 numpy 数组传递给 python 我可以做的PyArray SimpleNewFromData http docs scipy org doc numpy reference
  • 将内置类型转换为向量

    我的 TcpClient 类接受vector
  • 互斥体实现可以互换(独立于线程实现)

    所有互斥体实现最终都会调用相同的基本系统 硬件调用吗 这意味着它们可以互换吗 具体来说 如果我使用 gnu parallel算法 使用openmp 并且我想让他们称之为线程安全的类我可以使用boost mutex用于锁定 或者我必须编写自己
  • C++中的类查找结构体数组

    我正在尝试创建一个结构数组 它将输入字符串链接到类 如下所示 struct string command CommandPath cPath cPathLookup set an alarm AlarmCommandPath send an
  • 在 C# 中循环遍历文件文件夹的最简单方法是什么?

    我尝试编写一个程序 使用包含相关文件路径的配置文件来导航本地文件系统 我的问题是 在 C 中执行文件 I O 这将是从桌面应用程序到服务器并返回 和文件系统导航时使用的最佳实践是什么 我知道如何谷歌 并且找到了几种解决方案 但我想知道各种功
  • 单击 form2 上的按钮触发 form 1 中的方法

    我对 Windows 窗体很陌生 我想知道是否可以通过单击表单 2 中的按钮来触发表单 1 中的方法 我的表格 1 有一个组合框 我的 Form 2 有一个 保存 按钮 我想要实现的是 当用户单击表单 2 中的 保存 时 我需要检查表单 1
  • 使用 JNI 从 Java 代码中检索 String 值的内存泄漏

    我使用 GetStringUTFChars 从使用 JNI 的 java 代码中检索字符串的值 并使用 ReleaseStringUTFChars 释放该字符串 当代码在 JRE 1 4 上运行时 不会出现内存泄漏 但如果相同的代码在 JR
  • PlaySound 可在 Visual Studio 中运行,但不能在独立 exe 中运行

    我正在尝试使用 Visual Studio 在 C 中播放 wav 文件 我将文件 my wav 放入项目目录中并使用代码 PlaySound TEXT my wav NULL SND FILENAME SND SYNC 我按下播放按钮 或
  • 将 log4net 与 Autofac 结合使用

    我正在尝试将 log4net 与 Autofac 一起使用 我粘贴了这段代码http autofac readthedocs org en latest examples log4net html http autofac readthed
  • 私有模板函数

    我有一堂课 C h class C private template
  • std::async 与重载函数

    可能的重复 std bind 重载解析 https stackoverflow com questions 4159487 stdbind overload resolution 考虑以下 C 示例 class A public int f
  • 如何从main方法调用业务对象类?

    我已将代码分为业务对象 访问层 如下所示 void Main Business object public class ExpenseBO public void MakeExpense ExpensePayload payload var
  • .NET中的LinkedList是循环链表吗?

    我需要一个循环链表 所以我想知道是否LinkedList是循环链表吗 每当您想要移动列表中的 下一个 块时 以循环方式使用它的快速解决方案 current current Next current List First 电流在哪里Linke
  • 用于 C# 的 TripleDES IV?

    所以当我说这样的话 TripleDES tripledes TripleDES Create Rfc2898DeriveBytes pdb new Rfc2898DeriveBytes password plain tripledes Ke
  • 如何在按钮单击时模拟按键 - Unity

    我对 Unity 中的脚本编写非常陌生 我正在尝试创建一个按钮 一旦单击它就需要模拟按下 F 键 要拾取一个项目 这是我当前的代码 在编写此代码之前我浏览了所有统一论坛 但找不到任何有效的东西 Code using System Colle
  • memset 未填充数组

    u32 iterations 5 u32 ecx u32 malloc sizeof u32 iterations memset ecx 0xBAADF00D sizeof u32 iterations printf 8X n ecx 0
  • 当另一个线程可能设置共享布尔标志(最多一次)时,是否可以读取共享布尔标志而不锁定它?

    我希望我的线程能够更优雅地关闭 因此我尝试实现一个简单的信号机制 我不认为我想要一个完全事件驱动的线程 所以我有一个工作人员有一种方法可以使用关键部分优雅地停止它Monitor 相当于C lock我相信 绘图线程 h class Drawi

随机推荐

  • Realm Cocoa:通过 PK 查找多个对象

    潜伏已久 第一次提问 我在一个项目中使用 Realm Cocoa 来自 Realm io 并且正在努力通过 PK 执行搜索 假设我有一个名为RLMFoo它有一个主键称为bar 我还有一个 PK 列表 假设存储在一个数组中 NSArray p
  • 面试题:在php中,是123==0123吗?

    我已经回答了 这是假的 然后他问为什么 我无法回答 有人能回答吗 我很有兴趣学习它 这段代码 var dump 123 var dump 0123 会给你 int 123 int 83 这是因为0123是八进制表示法 因为0在开始时 whi
  • Erlang 和 JavaScript MD5 摘要匹配

    在这里测试 MD5 的 Javascript 实现 http www webtoolkit info javascript md5 htmlhttp www webtoolkit info javascript md5 html http
  • 分布式深度优先搜索

    我尝试在 C 中实现深度优先搜索 但我不太确定如何以分布式计算方式执行此操作 如果你们能帮我解决这个问题 我将非常感激 你可以在下面找到我的 DFS 代码 public class DFS static List
  • 在react-router :id中使用百分号(%)

    我正在尝试在react router id 中使用百分号 当使用 在 URI 中被禁止 我必须手动编码我的 URI 才能使用这个百分号 因此 使用 Link 时 我使用encodeURI 函数对 URI 进行编码 在我的页面的源代码中 我可
  • onLongPress 未按预期工作

    我有一个表面视图 使用以下代码在其上实现手势检测 surfaceview setOnTouchListener new OnSwipeTouchListener this public class OnSwipeTouchListener
  • 如果 div 为空,则忽略边距

    我有 2 个 DIV 彼此相邻水平对齐 并使用包装器居中 我使用 margin right 将 DIV2 与 DIV1 分开 DIV2 可能没有内容 如果 DIV2 没有内容 我希望忽略边距 而 DIV1 单独居中 这是我的CSS div1
  • 无法找到“org.springframework.mail.javamail.JavaMailSender”类型的 bean

    我在用spring boot 2 0 7 Release and spring boot starter mail 2 0 7 Release 我正在自动装配javaMailsender在尝试部署时 在 Windows 上工作正常的类内部U
  • 如何在 C#.NET 4.0 中编写 WMI 提供程序?

    任何人都可以帮助我使用 C net 4 0 编写电池的 WMI 提供程序吗 有一个旧的 C 示例here http www c sharpcorner com uploadfile falkor wmiproviderguide112620
  • Python 用户定义的数据类型

    我正在用 Python 编写一个 Rogue like 游戏 并定义我的Tile班级 瓷砖可以是块状的 墙壁的或地板的 我希望能够写一些类似的东西 self state Blocked 类似于如何使用布尔值 但具有三个值 有没有一种好方法可
  • 更改 Android 录音默认输入源

    我目前正在编写一个需要录制和实时处理音频数据的应用程序 为此 我使用 AudioRecord 类 这一切都很好 除了我的主要测试设备 Galaxy Nexus 上录制音频的默认设置是从后置扬声器录制 我假设大多数手机的默认录音源是背面或底部
  • GHC 中自动专业化的传递性

    From the docs http www haskell org ghc docs 7 6 3 html users guide pragmas html idp49866112对于 GHC 7 6 你 通常甚至一开始就不需要 SPEC
  • JQuery IE 生涩幻灯片动画

    我有以下代码来动画显示 隐藏 div headerClosed headerOpen live click function this next slideToggle slow 这将显示并隐藏具有以下标记的 div div class d
  • 登录失败。请检查您的网络连接并重试

    我正在尝试使用 Google Play 游戏服务制作简单的游戏 但无法登录 Google Play 游戏 我明白了error 登录失败 请检查您的网络连接 然后重试 我有 MainActivity 和三个片段 MainFragment Ga
  • 使用 5 个表生成查询

    我已经创建了我的表 我正在尝试创建一个查询 将已售表中的 sell quantity 和 on sale 表中的 sale price 相乘并相加 暂时将其称为 R1 将产品表中的 Retail price 和已售表中的 sell quan
  • 获取 Promise.race 中完成的 Promise

    上下文 我需要进行大量可并行的异步调用 想想大约 300 到 3000 个 ajax 调用 但是 我不想同时调用所有浏览器或服务器 从而给浏览器或服务器带来压力 我也不想按顺序运行它们 因为完成需要很长时间 我决定一次运行五个左右 并派生了
  • Windows Azure 服务总线队列重复检测如何工作?

    我知道您可以设置重复检测以在一段时间内使用天蓝色服务总线队列进行工作 但是 有谁知道这是否基于队列中的对象起作用 因此 如果我有一个 id 为 SO 1 的对象 该对象被放入队列并随后被消耗 重复检测是否仍然有效 我想我要问的是 是时间范围
  • “yield”在这个排列生成器中如何工作?

    def perm generator lst if len lst 1 yield lst else for i in range len lst for perm in perm generator lst i lst i 1 yield
  • 在 gdb 中显示解引用的 STL 迭代器

    我有一个映射元素的迭代器 我希望 gdb 显示该迭代器的 第一个 和 第二个 元素的值 例如 std map
  • 修复非线性最小二乘 GSL 中拟合函数的参数

    我正在编写一些代码 这些代码使用 GNU 科学库 GSL 1 的非线性最小二乘算法进行曲线拟合 我已经成功地获得了一个工作代码 该代码使用来自的 C 包装器从拟合分析中估计了正确的参数https github com Eleobert gs