C#多项式拟合实战:使用交叉验证避免过拟合的完整指南

1次阅读
没有评论

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

image.webp

引言

多项式拟合是机器学习中一种简单但强大的回归方法,它通过构造多项式函数来拟合数据点。比如在预测房价时,房屋面积和价格的关系可能不是简单的直线,而是一个曲线。这时候多项式回归就能派上用场了。

但是多项式回归有一个致命弱点——容易过拟合。所谓过拟合,就是模型在训练数据上表现很好,但在新数据上表现很差。这就像是一个学生死记硬背了所有练习题答案,但遇到新题目就不会做了。

C# 多项式拟合实战:使用交叉验证避免过拟合的完整指南

验证方法的选择

在评估模型性能时,我们需要把数据分成训练集和验证集。常见的分割方法有:

  1. 留出法(Hold-out):简单地将数据分为训练集和验证集两部分
  2. 优点:计算量小
  3. 缺点:结果受数据分割影响大,特别是数据量少时

  4. k 折交叉验证(k-fold Cross Validation):将数据分成 k 份,轮流用 k - 1 份训练,1 份验证

  5. 优点:充分利用有限数据,结果更稳定
  6. 缺点:计算量大

对于小数据集(样本量 <1000),k 折交叉验证是更好的选择,因为它能更准确地评估模型性能。

实战代码

首先安装必要的 NuGet 包:

Install-Package MathNet.Numerics
Install-Package MathNet.Numerics.Data.Text
Install-Package ScottPlot

1. 生成多项式特征矩阵

using MathNet.Numerics.LinearAlgebra;

/// <summary>
/// 生成多项式特征矩阵
/// </summary>
/// <param name="x"> 输入向量 </param>
/// <param name="degree"> 多项式次数 </param>
/// <returns> 特征矩阵 </returns>
public static Matrix<double> PolynomialFeatures(Vector<double> x, int degree)
{var features = Matrix<double>.Build.Dense(x.Count, degree + 1);

    for (int i = 0; i <= degree; i++)
    {features.SetColumn(i, x.PointwisePower(i).ToArray());
    }

    return features;
}

2. k 折交叉验证实现

using System;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Statistics;

/// <summary>
/// 执行 k 折交叉验证
/// </summary>
/// <param name="X"> 特征矩阵 </param>
/// <param name="y"> 目标向量 </param>
/// <param name="k"> 折数 </param>
/// <param name="degree"> 多项式次数 </param>
/// <param name="seed"> 随机种子 </param>
/// <returns> 平均验证误差 </returns>
public static double KFoldCrossValidation(
    Matrix<double> X, 
    Vector<double> y, 
    int k = 5, 
    int degree = 2, 
    int? seed = null)
{var rand = seed.HasValue ? new Random(seed.Value) : new Random();
    var indices = Enumerable.Range(0, X.RowCount).ToArray();

    // 打乱索引
    for (int i = 0; i < indices.Length; i++)
    {int j = rand.Next(i, indices.Length);
        (indices[i], indices[j]) = (indices[j], indices[i]);
    }

    var foldSize = (int)Math.Ceiling((double)X.RowCount / k);
    var errors = new List<double>();

    for (int fold = 0; fold < k; fold++)
    {
        // 划分训练集和验证集
        var valStart = fold * foldSize;
        var valEnd = Math.Min(valStart + foldSize, X.RowCount);

        var trainIndices = indices.Take(valStart)
            .Concat(indices.Skip(valEnd))
            .ToArray();
        var valIndices = indices.Skip(valStart)
            .Take(valEnd - valStart)
            .ToArray();

        var X_train = X.SubMatrix(trainIndices);
        var y_train = y.SubVector(trainIndices);
        var X_val = X.SubMatrix(valIndices);
        var y_val = y.SubVector(valIndices);

        // 训练模型
        var weights = X_train.QR().Solve(y_train);

        // 计算验证误差
        var predictions = X_val * weights;
        var error = predictions.Subtract(y_val).L2Norm() / Math.Sqrt(valIndices.Length);
        errors.Add(error);
    }

    return errors.Mean();}

3. 可视化误差曲线

using ScottPlot;

// 准备数据
var degrees = Enumerable.Range(1, 10).ToArray();
var trainErrors = new double[degrees.Length];
var valErrors = new double[degrees.Length];

for (int i = 0; i < degrees.Length; i++)
{var degree = degrees[i];
    var X_poly = PolynomialFeatures(x, degree);

    // 训练误差
    var weights = X_poly.QR().Solve(y);
    var predictions = X_poly * weights;
    trainErrors[i] = predictions.Subtract(y).L2Norm() / Math.Sqrt(X_poly.RowCount);

    // 交叉验证误差
    valErrors[i] = KFoldCrossValidation(X_poly, y, k: 5, degree: degree, seed: 42);
}

// 绘制图表
var plt = new Plot(800, 600);
plt.AddScatter(degrees.Select(d => (double)d).ToArray(), trainErrors, 
    label: "训练误差", color: System.Drawing.Color.Blue);
plt.AddScatter(degrees.Select(d => (double)d).ToArray(), valErrors, 
    label: "验证误差", color: System.Drawing.Color.Red);
plt.XLabel("多项式次数");
plt.YLabel("均方根误差");
plt.Legend();
plt.SaveFig("error_curve.png");

避坑指南

  1. 特征缩放的重要性
  2. 高阶多项式会产生极大 / 极小的特征值
  3. 建议对特征进行标准化:

    var x_scaled = (x - x.Mean()) / x.StandardDeviation();

  4. 随机种子的影响

  5. 交叉验证结果会受到数据分割的影响
  6. 设置固定随机种子 (如上面的 seed:42) 可以确保结果可复现

  7. 小样本量的处理

  8. 当样本量 <100 时,建议:
    • 使用留一法(LOO),即 k 等于样本量
    • 增加交叉验证重复次数
    • 考虑使用更简单的模型

思考题

  1. 如何实现早停策略自动选择最优多项式次数?
  2. 可以监控验证误差,当其开始上升时停止增加多项式次数

  3. 岭回归与交叉验证在防治过拟合上各有什么优缺点?

  4. 岭回归通过正则化直接约束模型复杂度
  5. 交叉验证通过验证集间接评估模型泛化能力
  6. 两者可以结合使用效果更佳

希望这篇教程能帮助你掌握多项式拟合的核心技巧。记住,模型不是越复杂越好,找到偏差和方差的最佳平衡点才是关键。

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