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