基于Eigen的多项式拟合与评估(支持多维度和任意次数)

1、简要说明

本文实现了一个高效的多维多项式拟合模块,支持任意次数和维度的多项式回归,用法参考test_multi_fit()函数,有以下特点:

  • 多项式拟合:通过生成特征矩阵A和系数向量coeff拟合多维多项式回归模型,支持任意次数的多项式拟合。
  • 预测功能:根据拟合的多项式模型和输入特征矩阵进行预测,返回预测值向量。
  • 模型评估:计算R²、RMSE和MAE等评估指标,评估模型性能。
  • 多维支持:支持任意维度的特征数据,通过递归生成多项式特征的指数组合。
  • 异常处理:包含输入验证和异常处理机制,如输入输出行数不一致、多项式次数为负等。

2、polynomial_fiting.h

cpp 复制代码
#ifndef POLYNOMIAL_FITING_H
#define POLYNOMIAL_FITING_H

#include <Eigen/Dense>
#include <vector>

struct EvaluationMetrics
{
    double r2;
    double rmse;
    double mae;
};

class PolynomialFit
{
public:
    static Eigen::VectorXd polyFitND(
        const Eigen::MatrixXd& input,
        const Eigen::VectorXd& output,
        int degree
    );

    static Eigen::VectorXd predict(
        const Eigen::MatrixXd& input,
        const Eigen::VectorXd& coeff,
        int degree
    );

    static EvaluationMetrics evaluate(
        const Eigen::VectorXd& y_true,
        const Eigen::VectorXd& y_pred
    );

    static int test_multi_fit();

private:
    static void generateExponents(
        int remaining,
        int current_dim,
        std::vector<int>& exponents,
        const Eigen::VectorXd& point,
        Eigen::MatrixXd& A,
        int row,
        int& col
    );

    static void generateExponents(
        int remaining,
        int current_dim,
        std::vector<int>& exponents,
        const Eigen::VectorXd& point,
        double& result,
        const Eigen::VectorXd& coeff,
        int& term_idx
    );

    static int calculateNumTerms(
        int degree,
        int dim
    );

    //    维度\次数 |  2次 |   3次 |    4次 |     5次
    //    ----------|------|-------|--------|---------
    //     1维      |    3 |     4 |      5 |       6
    //     2维      |    6 |    10 |     15 |      21
    //     3维      |   10 |    20 |     35 |      56
    //     4维      |   15 |    35 |     70 |     126
    //     5维      |   21 |    56 |    126 |     252
    //     6维      |   28 |    84 |    210 |     462
    //     7维      |   36 |   120 |    330 |     792
    //     8维      |   45 |   165 |    495 |    1287
    //     9维      |   55 |   220 |    715 |    2002
    //    10维      |   66 |   286 |   1001 |    3003

};

#endif // POLYNOMIAL_FITING_H

3、polynomial_fiting.cpp

cpp 复制代码
#include "polynomial_fiting.h"
#include <iostream>
#include <stdexcept>
#include <cmath>

using namespace Eigen;

void PolynomialFit::generateExponents(
        int remaining,
        int current_dim,
        std::vector<int>& exponents,
        const VectorXd& point,
        MatrixXd& A,
        int row,
        int& col
        )
{
    if (remaining == 0)
    {
        double term = 1.0;
        for (int i = 0; i < exponents.size(); ++i)
        {
            term *= pow(point(i), exponents[i]);
        }
        A(row, col++) = term;
        return;
    }

    if (current_dim >= exponents.size())
    {
        return;
    }

    for (int i = 0; i <= remaining; ++i)
    {
        exponents[current_dim] = i;
        generateExponents(
                    remaining - i,
                    current_dim + 1,
                    exponents,
                    point,
                    A,
                    row,
                    col
                    );
    }
}

void PolynomialFit::generateExponents(
        int remaining,
        int current_dim,
        std::vector<int>& exponents,
        const VectorXd& point,
        double& result,
        const VectorXd& coeff,
        int& term_idx
        )
{
    if (remaining == 0)
    {
        double term = 1.0;
        for (int i = 0; i < exponents.size(); ++i)
        {
            term *= pow(point(i), exponents[i]);
        }
        result += coeff(term_idx++) * term;
        return;
    }

    if (current_dim >= exponents.size())
    {
        return;
    }

    for (int i = 0; i <= remaining; ++i)
    {
        exponents[current_dim] = i;
        generateExponents(
                    remaining - i,
                    current_dim + 1,
                    exponents,
                    point,
                    result,
                    coeff,
                    term_idx
                    );
    }
}

int PolynomialFit::calculateNumTerms(
        int degree,
        int dim
        )
{
    int num = 1;
    for (int i = 1; i <= degree + dim; ++i)
    {
        num *= i;
    }
    for (int i = 1; i <= dim; ++i)
    {
        num /= i;
    }
    for (int i = 1; i <= degree; ++i)
    {
        num /= i;
    }
    return num;
}

VectorXd PolynomialFit::polyFitND(
        const MatrixXd& input,
        const VectorXd& output,
        int degree
        )
{
    if (input.rows() != output.rows())
    {
        throw std::invalid_argument("输入输出行数必须一致");
    }
    if (input.cols() < 1)
    {
        throw std::invalid_argument("输入必须是多维矩阵");
    }
    if (degree < 0)
    {
        throw std::invalid_argument("多项式次数必须非负");
    }
    if (input.hasNaN())
    {
        throw std::runtime_error("输入矩阵包含NaN值");
    }
    if (output.hasNaN())
    {
        throw std::runtime_error("输出向量包含NaN值");
    }

    int n = input.rows();
    int dim = input.cols();
    int num_terms = calculateNumTerms(degree, dim);

    if (n < num_terms)
    {
        std::cout << n << "<" << num_terms << std::endl;
        throw std::runtime_error("样本数不足支持该次数的拟合");
    }

    MatrixXd A(n, num_terms);
    std::vector<int> exponents(dim, 0);

    for (int i = 0; i < n; ++i)
    {
        VectorXd point = input.row(i);
        int col = 0;
        for (int k = 0; k <= degree; ++k)
        {
            std::fill(exponents.begin(), exponents.end(), 0);
            generateExponents(k, 0, exponents, point, A, i, col);
        }
    }

    return A.jacobiSvd(ComputeThinU | ComputeThinV).solve(output);
}

VectorXd PolynomialFit::predict(
        const MatrixXd& input,
        const VectorXd& coeff,
        int degree
        )
{
    int n = input.rows();
    int dim = input.cols();
    VectorXd predictions(n);

    for (int i = 0; i < n; ++i)
    {
        VectorXd point = input.row(i);
        double pred = 0.0;
        int term_idx = 0;
        std::vector<int> exponents(dim, 0);

        for (int k = 0; k <= degree; ++k)
        {
            std::fill(exponents.begin(), exponents.end(), 0);
            generateExponents(k, 0, exponents, point, pred, coeff, term_idx);
        }

        predictions(i) = pred;
    }

    return predictions;
}

EvaluationMetrics PolynomialFit::evaluate(
        const VectorXd& y_true,
        const VectorXd& y_pred
        )
{
    if (y_true.size() != y_pred.size())
    {
        throw std::invalid_argument("真实值和预测值长度必须一致");
    }

    int n = y_true.size();
    double ss_res = (y_true - y_pred).array().square().sum();
    double ss_tot = (y_true.array() - y_true.mean()).square().sum();

    EvaluationMetrics metrics;
    metrics.r2 = 1.0 - ss_res / ss_tot;
    metrics.rmse = sqrt(ss_res / n);
    metrics.mae = (y_true - y_pred).array().abs().sum() / n;

    return metrics;
}

int PolynomialFit::test_multi_fit()
{
    try
    {
        // 增加样本数量,确保满足最小样本要求
        // 对于2次多项式3维输入,最小需要10个样本
        Eigen::MatrixXd input(10, 3);
        input << 1, 2, 3,
                4, 5, 6,
                7, 8, 9,
                10, 11, 12,
                13, 14, 15,
                16, 17, 18,
                19, 20, 21,
                22, 23, 24,
                25, 26, 27,
                28, 29, 30;

        Eigen::VectorXd output(10);
        output << 10, 20, 30, 40, 50, 60, 70, 80, 90, 100;

        // 拟合
        int degree = 2;
        Eigen::VectorXd coeff = PolynomialFit::polyFitND(input, output, degree);

        // 预测
        Eigen::VectorXd predictions = PolynomialFit::predict(input, coeff, degree);

        // 评估
        EvaluationMetrics metrics = PolynomialFit::evaluate(output, predictions);

        // 输出结果
        std::cout << "多项式系数:\n" << coeff.transpose() << std::endl;
        std::cout << "评估指标:" << std::endl;
        std::cout << "R2: " << metrics.r2 << std::endl;
        std::cout << "RMSE: " << metrics.rmse << std::endl;
        std::cout << "MAE: " << metrics.mae << std::endl;
    }
    catch (const std::exception& e)
    {
        std::cerr << "错误: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
相关推荐
亚图跨际1 年前
Python和R均方根误差平均绝对误差算法模型
回归模型·误差指标·归一化均方根误差·生态状态指标·神经网络成本误差·气体排放气候模型·多项式拟合