C++代码实现MATLAB中的fitnlm函数功能

cpp 复制代码
// fitnlm.cpp ------ 对标 MATLAB fitnlm 的纯 C++ 实现
#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <limits>
#include <iomanip>
#include <cstdlib>
#include <algorithm>
#include <string>

// ============================================================
// 矩阵类型(行优先)
// ============================================================
struct Matrix {
    int rows, cols;
    std::vector<double> data;
    Matrix(int r = 0, int c = 0, double v = 0.0) : rows(r), cols(c), data(r * c, v) {}
    double& operator()(int i, int j)       { return data[i * cols + j]; }
    double  operator()(int i, int j) const { return data[i * cols + j]; }
};

static double dot(const std::vector<double>& a, const std::vector<double>& b) {
    double s = 0.0; for (size_t i = 0; i < a.size(); ++i) s += a[i] * b[i]; return s;
}

// Cholesky 分解求解对称正定方程组 Ax=b
static bool solveSPD(const Matrix& A, const std::vector<double>& b, std::vector<double>& x) {
    int n = A.rows;
    Matrix L(n, n, 0.0);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j <= i; ++j) {
            double sum = A(i, j);
            for (int k = 0; k < j; ++k) sum -= L(i, k) * L(j, k);
            if (i == j) {
                if (sum <= 0.0) return false;
                L(i, j) = std::sqrt(sum);
            } else L(i, j) = sum / L(j, j);
        }
    }
    std::vector<double> y(n, 0.0);
    for (int i = 0; i < n; ++i) {
        double sum = b[i];
        for (int k = 0; k < i; ++k) sum -= L(i, k) * y[k];
        y[i] = sum / L(i, i);
    }
    x.assign(n, 0.0);
    for (int i = n - 1; i >= 0; --i) {
        double sum = y[i];
        for (int k = i + 1; k < n; ++k) sum -= L(k, i) * x[k];
        x[i] = sum / L(i, i);
    }
    return true;
}

// ============================================================
// 数值雅可比(前向差分,步长对齐 MATLAB 的 DerivStep = eps^(1/3))
// ============================================================
static Matrix numericalJacobian(
    const std::function<std::vector<double>(const std::vector<double>&)>& f,
    const std::vector<double>& beta, const std::vector<double>& r0)
{
    int m = (int)r0.size(), n = (int)beta.size();
    Matrix J(m, n, 0.0);
    const double derivStep = std::cbrt(std::numeric_limits<double>::epsilon());
    for (int j = 0; j < n; ++j) {
        double h = derivStep * std::max(1.0, std::abs(beta[j]));
        std::vector<double> betaP = beta; betaP[j] += h;
        std::vector<double> rP = f(betaP);
        for (int i = 0; i < m; ++i) J(i, j) = (rP[i] - r0[i]) / h;
    }
    return J;
}

// ============================================================
// LM 求解器(与 MATLAB nlinfit/fitnlm 非稳健估计一致)
// ============================================================
static bool levenbergMarquardt(
    const std::function<std::vector<double>(const std::vector<double>&)>& f,
    std::vector<double>& beta, int maxIter = 200,
    double tolFun = 1e-8, double tolX = 1e-8)
{
    int n = (int)beta.size();
    std::vector<double> r = f(beta);
    int m = (int)r.size();
    double S = dot(r, r);
    double lambda = 1e-3;

    for (int iter = 0; iter < maxIter; ++iter) {
        Matrix J = numericalJacobian(f, beta, r);
        Matrix JtJ(n, n, 0.0); std::vector<double> Jtr(n, 0.0);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                double s = 0.0; for (int k = 0; k < m; ++k) s += J(k, i) * J(k, j);
                JtJ(i, j) = s;
            }
            double s = 0.0; for (int k = 0; k < m; ++k) s += J(k, i) * r[k];
            Jtr[i] = s;
        }

        bool accepted = false;
        std::vector<double> newBeta = beta;
        std::vector<double> newR;
        double newS = S;
        for (int attempt = 0; attempt < 30; ++attempt) {
            Matrix A = JtJ;
            for (int i = 0; i < n; ++i) A(i, i) += lambda * (JtJ(i, i) + 1e-12);
            std::vector<double> delta;
            if (!solveSPD(A, Jtr, delta)) { lambda *= 10.0; continue; }
            for (int i = 0; i < n; ++i) delta[i] = -delta[i]; /* GN/LM 步: (J'J+λI)δ = -J'r */
            for (int i = 0; i < n; ++i) newBeta[i] = beta[i] + delta[i];
            newR = f(newBeta);
            newS = dot(newR, newR);
            if (newS < S) {
                accepted = true;
                if (S - newS < tolFun * std::max(1.0, S)) {
                    beta = newBeta; r = newR; S = newS; return true;
                }
                lambda = std::max(lambda * 0.1, 1e-15);
                break;
            } else lambda *= 10.0;
        }
        if (!accepted) return false;

        double dX = 0.0, xNorm = 0.0;
        for (int i = 0; i < n; ++i) { double d = newBeta[i] - beta[i]; dX += d * d; xNorm += beta[i] * beta[i]; }
        beta = newBeta; r = newR; S = newS;
        if (std::sqrt(dX) < tolX * (std::sqrt(xNorm) + tolX)) return true;
    }
    return false;
}

// ============================================================
// 拟合结果结构体(对应 NonLinearModel 的属性)
// ============================================================
struct NonLinearModel {
    std::vector<double> Estimate;
    std::vector<double> SE;
    std::vector<double> tStat;
    std::vector<double> pValue;
    std::vector<std::string> CoeffNames;
    double RMSE = 0.0;
    double Rsquared_Ordinary = 0.0;
    double Rsquared_Adjusted = 0.0;
    double SSE = 0.0;
    double SST = 0.0;
    double DFE = 0.0;
    int    NumObservations = 0;
    int    NumCoefficients = 0;
    std::vector<double> Residuals;
    std::vector<double> Fitted;
    bool   converged = false;
};

// t 分布双尾 p 值
static double tDistPValue(double t, double df) {
    if (df <= 0) return 1.0;
    double x = df / (df + t * t);
    auto betacf = [](double a, double b, double x) {
        const int MAXIT = 200; const double EPS = 3e-12, FPMIN = 1e-300;
        double qab = a + b, qap = a + 1, qam = a - 1;
        double c = 1.0, d = 1.0 - qab * x / qap;
        if (std::abs(d) < FPMIN) d = FPMIN;
        d = 1.0 / d; double h = d;
        for (int m = 1; m <= MAXIT; ++m) {
            int m2 = 2 * m;
            double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
            d = 1.0 + aa * d; if (std::abs(d) < FPMIN) d = FPMIN;
            c = 1.0 + aa / c; if (std::abs(c) < FPMIN) c = FPMIN;
            d = 1.0 / d; h *= d * c;
            aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
            d = 1.0 + aa * d; if (std::abs(d) < FPMIN) d = FPMIN;
            c = 1.0 + aa / c; if (std::abs(c) < FPMIN) c = FPMIN;
            d = 1.0 / d; double del = d * c; h *= del;
            if (std::abs(del - 1.0) < EPS) break;
        }
        return h;
    };
    auto betai = [&](double a, double b, double x) {
        if (x <= 0.0) return 0.0; if (x >= 1.0) return 1.0;
        double bt = std::exp(std::lgamma(a + b) - std::lgamma(a) - std::lgamma(b)
                             + a * std::log(x) + b * std::log(1.0 - x));
        if (x < (a + 1.0) / (a + b + 2.0)) return bt * betacf(a, b, x) / a;
        return 1.0 - bt * betacf(b, a, 1.0 - x) / b;
    };
    return betai(0.5 * df, 0.5, x);
}

// ============================================================
// fitnlm 高层接口
// ============================================================
static NonLinearModel fitnlm(
    const std::vector<double>& X, const std::vector<double>& y,
    const std::function<double(double, const std::vector<double>&)>& modelfun,
    const std::vector<double>& beta0,
    const std::vector<std::string>& coeffNames = {})
{
    int nObs = (int)X.size(), nPar = (int)beta0.size();
    NonLinearModel mdl;
    mdl.NumObservations = nObs;
    mdl.NumCoefficients = nPar;
    mdl.DFE = nObs - nPar;
    mdl.CoeffNames = coeffNames;
    if (mdl.CoeffNames.empty())
        for (int i = 0; i < nPar; ++i) mdl.CoeffNames.push_back("b" + std::to_string(i + 1));

    auto residual = [&](const std::vector<double>& beta) {
        std::vector<double> r(nObs);
        for (int i = 0; i < nObs; ++i) r[i] = y[i] - modelfun(X[i], beta);
        return r;
    };

    std::vector<double> beta = beta0;
    mdl.converged = levenbergMarquardt(residual, beta);
    mdl.Estimate = beta;

    mdl.Fitted.resize(nObs); mdl.Residuals.resize(nObs);
    double yMean = 0.0; for (double v : y) yMean += v; yMean /= nObs;
    mdl.SSE = 0.0; mdl.SST = 0.0;
    for (int i = 0; i < nObs; ++i) {
        mdl.Fitted[i] = modelfun(X[i], beta);
        mdl.Residuals[i] = y[i] - mdl.Fitted[i];
        mdl.SSE += mdl.Residuals[i] * mdl.Residuals[i];
        mdl.SST += (y[i] - yMean) * (y[i] - yMean);
    }
    mdl.RMSE = std::sqrt(mdl.SSE / mdl.DFE);
    mdl.Rsquared_Ordinary = 1.0 - mdl.SSE / mdl.SST;
    mdl.Rsquared_Adjusted = 1.0 - (mdl.SSE / mdl.DFE) / (mdl.SST / (nObs - 1));

    std::vector<double> rFinal = residual(beta);
    Matrix J = numericalJacobian(residual, beta, rFinal);
    Matrix JtJ(nPar, nPar, 0.0);
    for (int i = 0; i < nPar; ++i)
        for (int j = 0; j < nPar; ++j) {
            double s = 0.0; for (int k = 0; k < nObs; ++k) s += J(k, i) * J(k, j);
            JtJ(i, j) = s;
        }
    double MSE = mdl.SSE / mdl.DFE;
    Matrix cov(nPar, nPar, 0.0);
    for (int i = 0; i < nPar; ++i) {
        std::vector<double> e(nPar, 0.0); e[i] = 1.0;
        std::vector<double> col;
        if (solveSPD(JtJ, e, col))
            for (int j = 0; j < nPar; ++j) cov(i, j) = col[j] * MSE;
    }
    mdl.SE.resize(nPar); mdl.tStat.resize(nPar); mdl.pValue.resize(nPar);
    for (int i = 0; i < nPar; ++i) {
        mdl.SE[i] = std::sqrt(std::max(0.0, cov(i, i)));
        mdl.tStat[i] = mdl.Estimate[i] / mdl.SE[i];
        mdl.pValue[i] = tDistPValue(std::abs(mdl.tStat[i]), mdl.DFE);
    }
    return mdl;
}

// ============================================================
// 测试主程序
// ============================================================
int main() {
    srand(42);

    const int m = 20;
    std::vector<double> x(m), y(m);
    const double a_true = 2.5, b_true = 0.8;
    for (int i = 0; i < m; ++i) {
        x[i] = i * 0.2;
        double noise = 0.05 * ((rand() / (double)RAND_MAX) - 0.5);
        y[i] = a_true * std::exp(b_true * x[i]) + noise;
    }

    auto modelfun = [](double xi, const std::vector<double>& beta) {
        return beta[0] * std::exp(beta[1] * xi);
    };
    std::vector<double> beta0 = {1.0, 0.5};

    NonLinearModel mdl = fitnlm(x, y, modelfun, beta0);

    std::cout << std::fixed << std::setprecision(4);
    std::cout << "Nonlinear regression model:\n";
    std::cout << "  y ~ b1*exp(b2*x)\n\n";
    std::cout << "Estimated Coefficients:\n";
    std::cout << std::setw(6) << "  " << std::setw(12) << "Estimate"
              << std::setw(10) << "SE" << std::setw(10) << "tStat"
              << std::setw(12) << "pValue" << "\n";
    for (int i = 0; i < mdl.NumCoefficients; ++i) {
        std::cout << std::setw(6) << mdl.CoeffNames[i]
                  << std::setw(12) << mdl.Estimate[i]
                  << std::setw(10) << mdl.SE[i]
                  << std::setw(10) << mdl.tStat[i]
                  << std::setw(12) << mdl.pValue[i] << "\n";
    }
    std::cout << "\nNumber of observations: " << mdl.NumObservations
              << ", Error degrees of freedom: " << (int)mdl.DFE << "\n";
    std::cout << "Root Mean Squared Error: " << mdl.RMSE << "\n";
    std::cout << "R-Squared: " << mdl.Rsquared_Ordinary
              << ", Adjusted R-Squared " << mdl.Rsquared_Adjusted << "\n";
    std::cout << "\nLM 收敛: " << (mdl.converged ? "成功" : "未收敛") << "\n";
    std::cout << "真实参数: a = " << a_true << ", b = " << b_true << "\n";
    return 0;
}
相关推荐
雪落漂泊1 小时前
C++算法:双指针篇(下)
数据结构·c++·算法
白帽攻防录9 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全
十年Java程序媛10 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
2603_9651481110 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化
MATLAB代码顾问10 小时前
【硬核推导+源码复现】永磁同步电机(PMSM)矢量控制(FOC)全流程解析:数学建模、PI参数整定与七段式SVPWM仿真
数学建模·matlab
生态学者11 小时前
Global Change Biology | 森林越“步调一致”,反而越怕干旱?树木生长与叶片活动的同步之谜
大数据·人工智能·算法·r语言·微信公众平台
所以遗憾是什么呢?11 小时前
【题解】Educational Codeforces Round 195 (Rated for Div. 2) ABCDE
c++·动态规划·acm·icpc·ccpc·xcpc·codeforecs
xixiaoyunya11 小时前
Python 异步编程入门:用 asyncio 轻松实现并发任务
开发语言·python·php
lsswear11 小时前
Agent 框架新手入门与实战部署指南
大数据·人工智能·算法