C#多项式拟合实战:使用交叉验证避免过拟合的工程实践

1次阅读
没有评论

共计 1913 个字符,预计需要花费 5 分钟才能阅读完成。

image.webp

问题背景

多项式回归在工程领域应用广泛,比如处理传感器采集的非线性数据(温度 - 电阻特性曲线)、金融领域的趋势预测(股票价格波动)等场景。当数据呈现明显非线性特征时,简单线性回归无法满足精度需求,此时多项式回归能通过曲线拟合更好地描述数据关系。

C# 多项式拟合实战:使用交叉验证避免过拟合的工程实践

但多项式回归存在一个典型陷阱:随着多项式阶数升高,模型会越来越贴合训练数据,甚至完美穿过每一个训练点。这看起来很美,实际却是过拟合——模型在训练集上误差极低,但在新数据(测试集)上表现急剧恶化。比如:

  • 5 阶多项式在训练集上 R²=0.99,测试集仅 0.65
  • 模型对噪声数据点过度敏感,预测曲线出现不合理震荡

技术方案

传统最小二乘法直接最小化训练误差,而带交叉验证的方案通过以下步骤控制过拟合:

  1. 数据分割:将原始数据分为 k 个子集(通常 k = 5 或 10)
  2. 轮换验证:每次用 k - 1 个子集训练,剩余 1 个验证,重复 k 次
  3. 误差评估:综合 k 次验证结果计算平均误差

MathNet.Numerics 库提供两大核心组件:

  • Polynomial.Fit:执行最小二乘多项式拟合
  • CrossValidated:实现 k 折交叉验证框架

代码实现

数据生成与预处理

首先生成带噪声的模拟数据(正弦曲线 + 高斯噪声):

// 生成 100 个 0 -10 之间的均匀分布 x 值
var x = Generate.LinearRange(0, 10, 100);

// 真实模型:y=sin(x) + 噪声
var rng = new Random(42);
var y = x.Select(xi => Math.Sin(xi) + rng.NextDouble()*0.5).ToArray();

交叉验证实现

使用 5 折交叉验证评估 3 阶多项式模型:

// 定义评估函数 (均方误差)
double Metric(double[] actual, double[] predicted) 
{return Distance.MSE(actual, predicted);
}

// 执行交叉验证
var cv = new CrossValidated<double[]>(k: 5);
var results = cv.Evaluate(samples: x.Zip(y, (xi, yi) => new[] { xi, yi}).ToArray(),
    fit: data => 
    {
        var coeff = Polynomial.Fit(x: data.Select(d => d[0]).ToArray(),
            y: data.Select(d => d[1]).ToArray(),
            order: 3); // 3 阶多项式
        return coeff;
    },
    predict: (model, test) => 
    {return test.Select(t => Polynomial.Evaluate(t[0], model)).ToArray();},
    metric: Metric
);

Console.WriteLine($"CV 平均误差: {results.AverageError}");

阶数选择策略

通过交叉验证寻找最优阶数(示例测试 1 - 6 阶):

var orders = Enumerable.Range(1, 6);
var cvErrors = new List<double>();

foreach(var order in orders)
{
    // 同上交叉验证流程,调整 order 参数
    cvErrors.Add(/* 获取当前阶数的 CV 误差 */);
}

// 选择误差最小的阶数
var bestOrder = orders.ElementAt(cvErrors.IndexOf(cvErrors.Min()));

生产建议

过拟合可视化

阶数 训练误差 验证误差 曲线形态
1 0.82 0.85 直线
3 0.12 0.15 平滑曲线
6 0.01 0.47 剧烈震荡

性能优化

  • 内存管理 :对于超过 10,000 数据点,改用Polynomial.Fit(x,y,order,DirectRegressionMethod.QR) 避免直接求逆
  • 并行计算 :使用Task.WhenAll 并行执行不同阶数的交叉验证

常见问题

  1. 矩阵奇异值错误:当阶数过高时,添加 L2 正则化
    Polynomial.Fit(..., method: DirectRegressionMethod.Svd);
  2. 数值不稳定:对 x 值进行归一化处理
    var xScaled = x.Select(xi => (xi - xMean)/xStd).ToArray();

延伸思考

  1. 当数据采样不均匀(如 80% 数据集中在某区间)时,如何调整交叉验证策略?
  2. 对于周期性数据(如昼夜温度变化),多项式回归是否是最佳选择?
  3. 如何将本方案封装成可供 ASP.NET Core 调用的预测服务?

通过交叉验证机制,我们可以在保持模型简单性的同时获得可靠的泛化能力。实际项目中建议将最佳模型参数持久化,避免每次请求重复训练。

正文完
 0
评论(没有评论)