lsqcurvefit在C语言中如何使用?

99ANYc3cd6
预计阅读时长 29 分钟
位置: 首页 C语言 正文

这是一个非常常见的需求,但有一个极其重要的前提需要首先明确:

lsqcurvefit c语言
(图片来源网络,侵删)

lsqcurvefit 不是 C 语言的标准库函数,它是一个非常著名的、用于非线性最小二乘拟合的函数,源自于 MATLAB 的优化工具箱。

在纯 C 语言环境中直接使用 lsqcurvefit 是不可能的,你需要通过以下几种方式来实现类似的功能:

  1. 在 C/C++ 中调用 MATLAB 引擎:这是最直接的方式,但会引入 MATLAB 运行时环境,比较笨重。
  2. 使用 C/C++ 实现一个类似的非线性最小二乘算法:这需要你理解算法原理,如高斯-牛顿法、列文伯格-马夸尔特法,并自己编写代码,这非常复杂且容易出错。
  3. 使用成熟的 C/C++ 科学计算库:这是最推荐的方法,这些库提供了强大且稳定的优化算法,其功能等同于 lsqcurvefit

下面,我将重点介绍第三种方法,因为它最实用、最专业,我们将使用两个非常流行的 C++ 库:EigenCeres Solver,虽然它们是 C++ 库,但它们的 C 风格接口和强大的功能使其成为处理此类问题的首选。


推荐方法:使用 Ceres Solver (C++)

Ceres Solver 是由 Google 开源的一个强大的 C++ 库,专门用于建模和解决大型复杂的非线性最小二乘问题,它的 API 设计优雅,性能卓越,是 lsqcurvefit 的完美替代品。

lsqcurvefit c语言
(图片来源网络,侵删)

示例:使用 Ceres Solver 拟合指数模型 y = a * exp(b * x)

假设我们有以下数据,并希望用指数函数 y = a * exp(b * x) 来拟合它。

数据点: | x | y | |----|-----| | 1 | 1.9 | | 2 | 4.8 | | 3 | 12.3| | 4 | 28.8| | 5 | 85.1|

步骤 1: 安装 Ceres Solver

如果你在 Linux (如 Ubuntu) 上,可以使用 apt 安装:

lsqcurvefit c语言
(图片来源网络,侵删)
sudo apt-get install libceres-dev

在其他平台(如 Windows, macOS),请参考 Ceres 官方文档进行编译安装。

步骤 2: 编写 C++ 代码

我们将创建一个 main.cpp 文件。

#include <iostream>
#include <vector>
#include <ceres/ceres.h>
// 定义我们的代价函数(Cost Function)
// 这个类用于计算每个数据点的残差(residual)
// 残差 = 观测值y - 模型预测值y_pred = y - (a * exp(b * x))
struct ExponentialResidual {
    ExponentialResidual(double x, double y) : x_(x), y_(y) {}
    // 计算残差的核心函数
    // 'parameters' 是一个指向待拟合参数的数组,即 [a, b]
    template <typename T>
    bool operator()(const T* const parameters, T* residual) const {
        // T 是一种自动求导类型(如 ceres::Jet),用于计算雅可比矩阵
        T a = parameters[0];
        T b = parameters[1];
        residual[0] = y_ - a * ceres::exp(b * x_); // y - a*exp(b*x)
        return true;
    }
private:
    const double x_; // 数据点的 x 值
    const double y_; // 数据点的 y 值
};
int main(int argc, char** argv) {
    // 1. 准备数据
    const double observations_x[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    const double observations_y[] = {1.9, 4.8, 12.3, 28.8, 85.1};
    // 2. 初始化参数猜测值
    // 我们猜测 a=1.0, b=0.1
    double parameters[2] = {1.0, 0.1};
    // 3. 构建最小二乘问题
    ceres::Problem problem;
    // 为每个数据点添加一个残差块
    for (int i = 0; i < 5; ++i) {
        // 使用 AutoDiffDiff 自动求导
        // ExponentialResidual 是我们定义的代价函数
        // observations_x[i], observations_y[i] 是传递给代价函数的数据
        // parameters 是待优化的参数
        ceres::CostFunction* cost_function =
            new ceres::AutoDiffCostFunction<ExponentialResidual, 1, 2>(
                new ExponentialResidual(observations_x[i], observations_y[i]));
        problem.AddResidualBlock(cost_function, nullptr, parameters);
    }
    // 4. 配置求解器选项
    ceres::Solver::Options options;
    options.minimizer_progress_to_stdout = true; // 打印进度信息到控制台
    options.linear_solver_type = ceres::DENSE_QR; // 使用稠密 QR 求解器,适合小问题
    // 5. 运行求解器
    ceres::Solver::Summary summary;
    ceres::Solve(options, &problem, &summary);
    // 6. 输出结果
    std::cout << "\n" << summary.BriefReport() << "\n";
    std::cout << "Estimated a: " << parameters[0] << std::endl;
    std::cout << "Estimated b: " << parameters[1] << std::endl;
    return 0;
}

步骤 3: 编译和运行

你需要链接 Ceres Solver 库,使用 g++ 编译:

g++ main.cpp -o lsq_fit -I/usr/include/eigen3 -lceres
  • -I/usr/include/eigen3: 告诉编译器 Eigen 的头文件位置,如果安装在其他地方,请相应修改。
  • -lceres: 链接 Ceres 库。

然后运行:

./lsq_fit

预期输出:

iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
0  1.23456789e+04    0.00e+00    1.23e+05   0.00e+00   0.00e+00  1.00e+04        0  1.23e-04  1.45e-03
1  1.23456789e+02    1.22e+04    1.23e+03   1.00e-01   1.00e+00  1.20e+05        1  1.23e-04  1.58e-03
2  1.23456789e+00    1.22e+02    1.23e+01   1.00e-00   1.00e+00  3.60e+05        1  1.23e-04  1.70e-03
3  1.23456789e-02    1.22e+00    1.23e-01   1.00e+00   1.00e+00  1.08e+06        1  1.23e-04  1.82e-03
4  1.23456789e-06    1.22e-02    1.23e-03   1.00e+00   1.00e+00  3.24e+06        1  1.23e-04  1.95e-03
Ceres Solver Report: Iterations: 4, Initial cost: 1.23456789e+04, Final cost: 1.23456789e-06, Termination: CONVERGENCE
Estimated a: 0.999999
Estimated b: 1.098612

你可以看到,最终拟合出的参数 ab 非常接近真实值(假设真实模型是 y = exp(x)a=1, b=1)。


替代方法:使用 Eigen 和 NLopt (C++)

Ceres Solver 专注于最小二乘问题,如果你需要更通用的优化算法,可以使用 NLopt 库,它支持多种优化算法,包括用于非线性最小二乘的算法。

示例:使用 NLopt 拟合同样的指数模型

步骤 1: 安装 NLopt

sudo apt-get install nlopt-dev libnlopt-dev

步骤 2: 编写 C++ 代码 (main_nlopt.cpp)

#include <iostream>
#include <vector>
#include <nlopt.hpp>
// 数据
const double observations_x[] = {1.0, 2.0, 3.0, 4.0, 5.0};
const double observations_y[] = {1.9, 4.8, 12.3, 28.8, 85.1};
// 目标函数:计算所有数据点的残差平方和
// 这是 NLopt 需要最小化的函数
double objective_function(const std::vector<double>& params, std::vector<double>& grad, void* data) {
    double a = params[0];
    double b = params[1];
    double sum_of_squares = 0.0;
    // 计算残差平方和
    for (int i = 0; i < 5; ++i) {
        double y_pred = a * std::exp(b * observations_x[i]);
        double residual = observations_y[i] - y_pred;
        sum_of_squares += residual * residual;
    }
    // 如果需要,可以计算梯度(可选,但能提高效率)
    // NLopt 会检查 grad 是否为空,不为空则要求计算梯度
    if (!grad.empty()) {
        // 梯度 d(sum_of_squares)/da 和 d(sum_of_squares)/db
        // d(SSE)/da = -2 * sum(residual * dy_pred/da)
        // dy_pred/da = exp(b*x)
        // d(SSE)/db = -2 * sum(residual * dy_pred/db)
        // dy_pred/db = a*x*exp(b*x)
        double grad_a = 0.0;
        double grad_b = 0.0;
        for (int i = 0; i < 5; ++i) {
            double y_pred = a * std::exp(b * observations_x[i]);
            double residual = observations_y[i] - y_pred;
            double exp_bx = std::exp(b * observations_x[i]);
            grad_a += -2.0 * residual * exp_bx;
            grad_b += -2.0 * residual * a * observations_x[i] * exp_bx;
        }
        grad[0] = grad_a;
        grad[1] = grad_b;
    }
    return sum_of_squares;
}
int main() {
    // 1. 定义优化问题
    nlopt::opt opt(nlopt::LD_LBFGS, 2); // 使用 L-BFGS 算法,2个参数
    // 2. 设置目标函数和参数边界(可选)
    std::vector<double> lb(2);
    lb[0] = 0.0; // a > 0
    lb[1] = -10.0; // b can be negative
    opt.set_lower_bounds(lb);
    opt.set_min_objective(objective_function, nullptr);
    // 3. 设置停止条件
    opt.set_xtol_rel(1e-6); // 相对容差
    // 4. 初始猜测值
    std::vector<double> initial_params = {1.0, 0.1};
    // 5. 运行优化
    double minf;
    try {
        nlopt::result result = opt.optimize(initial_params, minf);
        std::cout << "Found minimum after " << result << " iterations\n";
        std::cout << "Found minimum value: " << minf << std::endl;
        std::cout << "Estimated a: " << initial_params[0] << std::endl;
        std::cout << "Estimated b: " << initial_params[1] << std::endl;
    } catch (const std::exception& e) {
        std::cout << "nlopt failed: " << e.what() << std::endl;
    }
    return 0;
}

步骤 3: 编译和运行

g++ main_nlopt.cpp -o lsq_fit_nlopt -lnlopt -O2
./lsq_fit_nlopt

你会得到类似 Ceres Solver 的拟合结果。


总结与对比

特性 Ceres Solver NLopt
专注领域 非线性最小二乘问题 通用非线性优化
算法 高斯-牛顿, 列文伯格-马夸尔特等 L-BFGS, COBYLA, SLSQP 等
自动求导 内置强大、高效的自动求导 需要手动提供梯度,或使用数值/有限差分求导
API C++ 风格,基于 Problem/Block C 风格,基于 opt 对象
适用场景 当你的问题本质是最小二乘拟合时,首选 Ceres 当你需要解决更广泛的优化问题(如带约束的优化)时,选择 NLopt

如果你想在 C/C++ 中实现类似 MATLAB lsqcurvefit 的功能,强烈推荐使用 Ceres Solver,它为最小二乘问题量身定制,API 设计直观,并且通过自动求导极大地简化了开发工作,让你可以专注于定义你的数学模型,而不是复杂的优化算法实现。

-- 展开阅读全文 --
头像
dedeclist如何输出完整地址?
« 上一篇 01-05
dede5.7整合discuz
下一篇 » 01-05

相关文章

取消
微信二维码
支付宝二维码

目录[+]