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

lsqcurvefit 不是 C 语言的标准库函数,它是一个非常著名的、用于非线性最小二乘拟合的函数,源自于 MATLAB 的优化工具箱。
在纯 C 语言环境中直接使用 lsqcurvefit 是不可能的,你需要通过以下几种方式来实现类似的功能:
- 在 C/C++ 中调用 MATLAB 引擎:这是最直接的方式,但会引入 MATLAB 运行时环境,比较笨重。
- 使用 C/C++ 实现一个类似的非线性最小二乘算法:这需要你理解算法原理,如高斯-牛顿法、列文伯格-马夸尔特法,并自己编写代码,这非常复杂且容易出错。
- 使用成熟的 C/C++ 科学计算库:这是最推荐的方法,这些库提供了强大且稳定的优化算法,其功能等同于
lsqcurvefit。
下面,我将重点介绍第三种方法,因为它最实用、最专业,我们将使用两个非常流行的 C++ 库:Eigen 和 Ceres Solver,虽然它们是 C++ 库,但它们的 C 风格接口和强大的功能使其成为处理此类问题的首选。
推荐方法:使用 Ceres Solver (C++)
Ceres Solver 是由 Google 开源的一个强大的 C++ 库,专门用于建模和解决大型复杂的非线性最小二乘问题,它的 API 设计优雅,性能卓越,是 lsqcurvefit 的完美替代品。

示例:使用 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 安装:

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
你可以看到,最终拟合出的参数 a 和 b 非常接近真实值(假设真实模型是 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 设计直观,并且通过自动求导极大地简化了开发工作,让你可以专注于定义你的数学模型,而不是复杂的优化算法实现。
