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

1次阅读
没有评论

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

image.webp

问题背景

多项式回归是机器学习中常用的建模方法,特别适合描述非线性关系的数据。比如预测房价与面积的关系、销售额与广告投入的关系等,这些场景下简单的线性回归可能无法很好地拟合数据。

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

但多项式回归有个致命弱点:容易过拟合。过拟合是指模型在训练集上表现很好,但在新数据上表现糟糕的现象。我们通常可以通过观察训练集和验证集的表现差异来检测过拟合——如果训练集误差很低但验证集误差很高,基本可以确定发生了过拟合。

常规的训练方式是把所有数据一次性用于训练,然后用另一部分数据测试。而交叉验证(特别是 k 折交叉验证)则把数据分成 k 份,轮流用 k - 1 份训练,1 份验证,最后取平均值。这种方法能更好地评估模型的泛化能力。

技术实现

1. 环境准备

首先需要安装 MathNet.Numerics 库:

Install-Package MathNet.Numerics

2. 数据标准化

多项式拟合对数据尺度敏感,建议先做标准化:

public static Tuple<double[], double[]> Standardize(double[] x, double[] y)
{var meanX = x.Average();
    var stdX = Math.Sqrt(x.Select(v => Math.Pow(v - meanX, 2)).Average());

    var meanY = y.Average();
    var stdY = Math.Sqrt(y.Select(v => Math.Pow(v - meanY, 2)).Average());

    return Tuple.Create(x.Select(v => (v - meanX) / stdX).ToArray(),
        y.Select(v => (v - meanY) / stdY).ToArray());
}

3. 多项式特征生成

public static double[][] GeneratePolynomialFeatures(double[] x, int degree)
{
    return x.Select(v => 
        Enumerable.Range(0, degree + 1)
                 .Select(p => Math.Pow(v, p))
                 .ToArray())
           .ToArray();}

4. 最小二乘法拟合

public static double[] FitPolynomial(double[][] X, double[] y)
{var matrixX = Matrix<double>.Build.DenseOfRowArrays(X);
    var vectorY = Vector<double>.Build.Dense(y);

    // 使用伪逆求解最小二乘问题
    return matrixX.PseudoInverse() * vectorY;}

5. k 折交叉验证实现

public static double[] CrossValidate(double[] x, double[] y, 
    int degree, int folds = 5)
{var indices = Enumerable.Range(0, x.Length).ToArray();
    var rng = new Random();
    indices = indices.OrderBy(i => rng.Next()).ToArray();

    var foldSize = x.Length / folds;
    var coefficients = new List<double[]>();

    for (int i = 0; i < folds; i++)
    {
        // 划分训练集和验证集
        var valIndices = indices.Skip(i * foldSize).Take(foldSize);
        var trainIndices = indices.Except(valIndices);

        var trainX = trainIndices.Select(idx => x[idx]).ToArray();
        var trainY = trainIndices.Select(idx => y[idx]).ToArray();

        var X = GeneratePolynomialFeatures(trainX, degree);
        coefficients.Add(FitPolynomial(X, trainY));
    }

    // 取各折系数的平均值
    return Enumerable.Range(0, degree + 1)
           .Select(i => coefficients.Select(c => c[i]).Average())
           .ToArray();}

评估优化

1. 误差对比

建议绘制训练集和验证集的 MSE 随多项式阶数变化的曲线:

var degrees = Enumerable.Range(1, 10);
var trainErrors = new List<double>();
var valErrors = new List<double>();

foreach (var degree in degrees)
{var coeffs = CrossValidate(x, y, degree);

    // 计算训练集误差
    var trainX = GeneratePolynomialFeatures(x, degree);
    var predictedY = trainX.Select(f => coeffs.Zip(f, (c, v) => c * v).Sum()).ToArray();
    var trainMse = y.Zip(predictedY, (a, b) => Math.Pow(a - b, 2)).Average();

    // 计算验证集误差(使用交叉验证中的验证集)// ...

    trainErrors.Add(trainMse);
    valErrors.Add(valMse);
}

2. 多项式阶数选择

通常选择验证误差最小的阶数。实践中,当验证误差开始上升时就应该停止增加阶数。

3. 正则化补充

对于高阶多项式,建议加入 L2 正则化(岭回归):

public static double[] FitPolynomialWithRegularization(double[][] X, double[] y, double lambda)
{var matrixX = Matrix<double>.Build.DenseOfRowArrays(X);
    var vectorY = Vector<double>.Build.Dense(y);

    var identity = Matrix<double>.Build.DenseIdentity(X[0].Length);
    return (matrixX.Transpose() * matrixX + lambda * identity).Inverse() 
           * matrixX.Transpose() * vectorY;}

生产建议

  1. 内存优化:对于大数据集,建议分批处理数据而不是一次性加载
  2. 性能陷阱:多项式阶数超过 20 时计算可能不稳定,建议限制最大阶数
  3. 监控指标:生产环境建议记录:
  4. 模型拟合时间
  5. 预测延迟
  6. 特征重要性
  7. 实时预测误差

延伸思考

当特征维度 >1000 时,可以考虑:
1. 使用主成分分析 (PCA) 降维
2. 改用随机森林等非线性模型
3. 使用梯度下降优化替代直接矩阵求逆

拓展阅读:
ML.NET 官方文档
MathNet.Numerics 文档

希望这篇指南能帮助你在 C# 中实现稳健的多项式回归模型。记住,模型复杂度不是越高越好,找到那个 ” 刚刚好 ” 的平衡点才是关键。

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