新闻详情

新闻详情

首页 / 资讯中心 / 详情

多项式拟合正弦曲线:过拟合与正则化实验

发布时间:2026/10/2 1:48:33来源:尧图网络
多项式拟合正弦曲线:过拟合与正则化实验
简介这份资源面向机器学习初学者与课程实验学习者围绕多项式拟合正弦曲线这一经典任务提供完整的Python实现与实验报告。内容涵盖最小二乘法解析解、带2范数惩罚项的正则化优化、梯度下降与共轭梯度法的手写实现并引导读者通过调整数据量、超参数与多项式阶数直观理解过拟合现象及其克服方法全程不依赖PyTorch、TensorFlow等自动微分框架。压缩包共4个文件包含3个py源码与1份doc实验报告源码分别负责数据生成、梯度下降与最小二乘求解报告则记录实验目的、环境与结果分析整体约581KB结构清晰便于对照学习。目前已有183人学习下载。读者可借此掌握损失函数优化、正则化与迭代求解的完整流程并参考报告中的对比实验快速完成同类课程作业或巩固机器学习基础。1. 多项式拟合正弦曲线从过拟合到正则化的完整实验路径用高阶多项式去逼近一个正弦函数是机器学习入门里最经典也最容易翻车的实验之一。很多人第一次跑这个实验时训练集上的均方误差已经降到小数点后好几位画出来的曲线却在高次项区域剧烈震荡测试集误差反而比一次线性拟合还大。这个现象背后就是过拟合而多项式拟合正弦曲线恰好是观察过拟合、理解正则化、验证模型复杂度与泛化能力之间关系的最小可复现系统。本文面向正在做机器学习实验、需要提交源代码和实验报告的读者从数据生成、模型构建、训练评估到正则化对比给出完整可运行的 Python 实现和参数解释。无论你是刚接触机器学习的新手还是想找一个干净实验来验证正则化效果的熟手这套流程都能直接跑通并复现出教科书级别的结论。2. 实验环境搭建与数据生成把正弦曲线变成可训练的数据集2.1 环境依赖与版本选择这个实验对环境的依赖非常轻核心只需要 NumPy 做数值计算、Matplotlib 做可视化、scikit-learn 做多项式特征展开和线性回归。如果你还在纠结 python 安装教程或者 vscode python 环境配置建议直接用 Anaconda 创建一个独立环境避免和系统里已有的包版本冲突。我一般会固定以下版本组合因为不同版本之间PolynomialFeatures的行为没有变化但 Matplotlib 的绘图 API 在 3.5 之后有一些默认样式调整固定版本可以保证实验报告里的图和你本地跑出来的一致。conda create -n polyfit python3.10 conda activate polyfit pip install numpy1.24.3 matplotlib3.7.2 scikit-learn1.3.0这三行命令分别创建环境、激活环境、安装依赖。NumPy 负责生成正弦曲线的采样点和加入高斯噪声Matplotlib 负责画出拟合曲线和误差曲线scikit-learn 提供多项式特征生成器和线性回归模型。版本号不是必须完全一致但建议 Python 不要低于 3.8否则 scikit-learn 的新版本可能无法安装。2.2 正弦曲线采样与噪声注入正弦函数本身是确定性的如果直接拿无噪声的采样点做拟合高阶多项式可以无限逼近过拟合现象反而不明显。所以第一步是在正弦值上叠加高斯噪声模拟真实场景中观测数据的随机误差。采样范围选 $[-\pi, \pi]$ 还是 $[0, 2\pi]$ 会影响边界处的拟合难度我一般用 $[-\pi, \pi]$因为两端对称画图好看而且边界震荡更容易暴露过拟合问题。import numpy as np np.random.seed(42) # 固定随机种子保证每次生成的数据一致 # 在 [-pi, pi] 区间均匀采样 30 个点 n_samples 30 X np.linspace(-np.pi, np.pi, n_samples).reshape(-1, 1) # 生成正弦值并加入高斯噪声 y_true np.sin(X).ravel() noise_std 0.15 # 噪声标准差控制数据波动程度 y y_true np.random.normal(0, noise_std, sizen_samples) # 打印前 5 个样本看看数据长什么样 for i in range(5): print(fx{X[i,0]:.4f}, sin(x){y_true[i]:.4f}, y{y[i]:.4f})这段代码的关键参数是n_samples和noise_std。n_samples30是一个刻意偏小的样本量目的是让高阶多项式有足够的自由度去拟合噪声。如果你把样本量加到 200过拟合现象会明显减弱因为数据约束变多了。noise_std0.15是我反复试出来的一个值太小了过拟合不明显太大了正弦信号本身都被淹没。np.random.seed(42)是后悔药没有它每次跑出来的数据都不一样实验报告里的图就对不上了。2.3 训练集与测试集划分做这个实验最容易犯的错误就是只在训练集上评估。多项式阶数越高训练误差一定越小但这不代表模型更好。必须留出一部分数据做测试才能看到泛化误差随阶数变化的真实趋势。我一般按 7:3 划分测试集占 30%因为总样本只有 30 个测试集太小的话评估结果波动会很大。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) print(f训练集大小: {X_train.shape[0]}, 测试集大小: {X_test.shape[0]})test_size0.3表示 30% 做测试random_state42保证每次划分结果一致。注意这里的random_state和前面生成数据的种子是独立的但都固定成 42 只是为了方便记忆。划分之后训练集大约 21 个点测试集 9 个点。样本量确实很小但这正是这个实验的设计意图在小样本下观察模型复杂度对泛化的影响。3. 多项式拟合的实现从特征展开到模型训练3.1 用 PolynomialFeatures 做特征展开多项式拟合的本质是把原始输入 $x$ 扩展成 $[1, x, x^2, x^3, \dots, x^d]$然后在这个高维特征空间里做线性回归。scikit-learn 的PolynomialFeatures就是干这个的不需要手写循环去构造幂次项。这里有一个容易忽略的细节include_bias参数默认是 True会自动加一列全 1 的特征作为截距项。如果你后面用的LinearRegression也会自动拟合截距那就重复了所以我一般把include_bias设为 False让线性回归自己去处理截距。from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline def fit_polynomial(degree, X_train, y_train): 给定阶数返回训练好的多项式拟合模型 model Pipeline([ (poly, PolynomialFeatures(degreedegree, include_biasFalse)), (linear, LinearRegression()) ]) model.fit(X_train, y_train) return modelPipeline把特征展开和线性回归串在一起好处是后面做预测时不用手动先转换再预测直接调model.predict(X)就行。degree就是多项式的最高次幂也是这个实验里最核心的超参数。include_biasFalse的原因上面说了避免和LinearRegression的截距项冲突。这个函数封装好之后换不同的degree只需要改一个参数。3.2 不同阶数下的拟合效果对比接下来把阶数从 1 取到 15分别训练模型记录训练误差和测试误差。这里用均方误差MSE作为评估指标因为它对大的偏差惩罚更重能更明显地反映出过拟合时测试误差的飙升。from sklearn.metrics import mean_squared_error degrees [1, 3, 5, 7, 9, 11, 13, 15] train_errors [] test_errors [] for d in degrees: model fit_polynomial(d, X_train, y_train) y_train_pred model.predict(X_train) y_test_pred model.predict(X_test) train_mse mean_squared_error(y_train, y_train_pred) test_mse mean_squared_error(y_test, y_test_pred) train_errors.append(train_mse) test_errors.append(test_mse) print(f阶数{d:2d} | 训练MSE{train_mse:.6f} | 测试MSE{test_mse:.6f})跑完这段代码你会看到类似这样的输出阶数 1 的时候训练和测试误差都在 0.1 左右阶数升到 5 左右测试误差降到最低继续升到 11 以上训练误差趋近于 0但测试误差可能跳到 1.0 甚至更大。这就是过拟合的典型信号。mean_squared_error的参数就是真实值和预测值两个数组返回标量。如果你发现测试误差在某些阶数上突然变得极大不用慌那是多项式在边界处震荡导致的属于正常现象。3.3 可视化把拟合曲线和误差曲线画出来数字看多了容易麻木画图才能直观感受到过拟合长什么样。我一般画两张图一张是不同阶数的拟合曲线叠在原始数据上另一张是训练误差和测试误差随阶数变化的曲线。import matplotlib.pyplot as plt # 生成用于画平滑曲线的密集采样点 X_plot np.linspace(-np.pi, np.pi, 200).reshape(-1, 1) plt.figure(figsize(12, 5)) # 左图拟合曲线对比 plt.subplot(1, 2, 1) plt.scatter(X_train, y_train, colorblack, label训练数据, zorder5) plt.scatter(X_test, y_test, colorgray, markers, label测试数据, zorder5) plt.plot(X_plot, np.sin(X_plot), --, colorgreen, label真实 sin(x)) for d in [1, 5, 15]: model fit_polynomial(d, X_train, y_train) plt.plot(X_plot, model.predict(X_plot), labelf阶数{d}) plt.ylim(-2, 2) plt.legend() plt.title(不同阶数多项式拟合效果) # 右图误差随阶数变化 plt.subplot(1, 2, 2) plt.plot(degrees, train_errors, o-, label训练MSE) plt.plot(degrees, test_errors, s-, label测试MSE) plt.xlabel(多项式阶数) plt.ylabel(均方误差) plt.legend() plt.title(训练误差与测试误差随阶数变化) plt.tight_layout() plt.savefig(polyfit_result.png, dpi150) plt.show()左图里阶数 1 的直线明显欠拟合阶数 5 的曲线贴合正弦走势阶数 15 的曲线在两端疯狂震荡甚至跑到 ±2 以外。右图里训练误差单调下降测试误差先降后升形成一个 U 形。X_plot用 200 个点是为了让曲线看起来平滑如果直接用训练集的 21 个点画折线感会很重。plt.ylim(-2, 2)是为了防止高阶多项式的极端值把纵轴拉得太长导致其他曲线挤在一起看不清。4. 正则化与模型选择把过拟合按回去4.1 岭回归与 Lasso 的引入看到阶数 15 的曲线震荡成那样第一反应可能是降低阶数。但降低阶数等于直接限制模型容量有时候我们确实需要高阶项来捕捉复杂模式只是不希望系数无限膨胀。正则化就是干这个的在损失函数里加一项对系数的惩罚让模型在拟合数据和保持系数平滑之间做权衡。岭回归加的是 L2 惩罚Lasso 加的是 L1 惩罚。这个实验里我一般用岭回归因为 L2 惩罚会让系数整体缩小但不会变成零画出来的曲线更平滑。from sklearn.linear_model import Ridge def fit_ridge(degree, alpha, X_train, y_train): 给定阶数和正则化强度返回岭回归模型 model Pipeline([ (poly, PolynomialFeatures(degreedegree, include_biasFalse)), (ridge, Ridge(alphaalpha)) ]) model.fit(X_train, y_train) return modelalpha是正则化强度越大惩罚越重。alpha0就退化成普通线性回归alpha趋近无穷大时所有系数都被压到接近零模型变成一条水平线。实际调参时我一般从 0.001 开始试到 10按对数刻度取几个值。4.2 正则化强度对拟合曲线的影响固定阶数为 15改变alpha看曲线怎么从震荡变平滑。这个对比能让你直观理解正则化到底在做什么。alphas [0.0001, 0.001, 0.01, 0.1, 1.0] plt.figure(figsize(12, 5)) plt.scatter(X_train, y_train, colorblack, label训练数据, zorder5) plt.plot(X_plot, np.sin(X_plot), --, colorgreen, label真实 sin(x)) for a in alphas: model fit_ridge(15, a, X_train, y_train) plt.plot(X_plot, model.predict(X_plot), labelfalpha{a}) plt.ylim(-2, 2) plt.legend() plt.title(阶数15 时不同正则化强度的拟合曲线) plt.savefig(ridge_compare.png, dpi150) plt.show()alpha0.0001的时候曲线和普通高阶多项式差不多还是震荡alpha0.01开始明显变平滑alpha1.0的时候曲线已经接近低阶多项式的效果但又不完全是一条直线。这个渐变过程说明正则化不是一刀切地限制模型而是通过惩罚系数来调节有效复杂度。注意alpha的取值跨度很大因为多项式特征的量级随阶数指数增长高阶项的系数本身很小需要很小的alpha才能产生可见效果。4.3 用交叉验证选正则化参数手动试alpha虽然直观但不够严谨。实验报告里最好用交叉验证来选参数这样结论更有说服力。scikit-learn 的RidgeCV可以直接做这件事它内部用留一法或 K 折交叉验证来评估每个alpha的效果。from sklearn.linear_model import RidgeCV # 候选 alpha 列表按对数刻度取 alphas_cv np.logspace(-4, 2, 20) model_cv Pipeline([ (poly, PolynomialFeatures(degree15, include_biasFalse)), (ridgecv, RidgeCV(alphasalphas_cv, cv5)) ]) model_cv.fit(X_train, y_train) best_alpha model_cv.named_steps[ridgecv].alpha_ print(f交叉验证选出的最优 alpha: {best_alpha:.6f}) y_test_pred model_cv.predict(X_test) print(f测试集 MSE: {mean_squared_error(y_test, y_test_pred):.6f})np.logspace(-4, 2, 20)生成从 $10^{-4}$ 到 $10^2$ 的 20 个对数等距值覆盖了从几乎不正则化到强正则化的范围。cv5表示 5 折交叉验证虽然样本只有 21 个每折大约 4 个点但在这个实验里足够看出趋势了。RidgeCV选出的alpha通常不会太大因为数据本身的正弦模式并不复杂过大的正则化会导致欠拟合。跑完之后把最优alpha对应的测试 MSE 和之前手动试的结果对比一下如果接近说明调参方向是对的。5. 避坑与排查这个实验里最容易翻车的五个地方5.1 现象测试误差比训练误差还小原因数据划分时测试集恰好落在正弦曲线比较平缓的区域或者测试集样本太少导致评估结果偶然性大。这个实验总共只有 30 个点测试集 9 个点波动是正常的。解决换几个不同的random_state重新划分看测试误差的趋势是否一致。如果某个随机种子下测试误差异常低不要直接采信多跑几次取平均。或者在实验报告里说明样本量限制把结论限定在趋势层面而不是具体数值。5.2 现象高阶多项式预测时出现极端值原因多项式在训练数据范围之外会快速发散即使是在 $[-\pi, \pi]$ 内部靠近边界的地方也可能因为系数过大而震荡。X_plot如果用np.linspace生成端点处最容易暴露这个问题。解决画图时限制纵轴范围比如plt.ylim(-2, 2)避免极端值把其他曲线压扁。如果需要在边界外预测必须非常谨慎多项式外推基本不可靠。另外可以在PolynomialFeatures之后加标准化但要注意标准化应该放在Pipeline里用训练集的统计量去转换测试集。5.3 现象RidgeCV 选出的 alpha 和手动试出来的差很多原因RidgeCV内部的交叉验证划分方式和手动划分不同而且alpha候选列表的粒度也会影响结果。如果候选列表太稀疏可能跳过真正最优的值。解决把alphas_cv取密一点比如np.logspace(-4, 2, 50)。另外注意RidgeCV默认的cv参数在样本量很小时可能不稳定可以显式指定cv5或cv3。如果结果还是差异大以交叉验证的结果为准因为它的评估更系统。5.4 现象训练误差一直降但测试误差不降反升加了正则化也没用原因可能是数据本身的噪声太大正弦信号被淹没这时候任何模型都无法在测试集上表现好。检查noise_std是不是设得太大了0.15 是一个参考值如果设到 0.5 以上正弦模式基本看不出来。解决把noise_std调小到 0.1 左右重新生成数据或者增加样本量到 50 以上。这个实验的目的是观察过拟合不是挑战极端噪声环境控制变量很重要。5.5 现象Matplotlib 画出来的图中文显示成方框原因Matplotlib 默认字体不支持中文需要手动指定中文字体。解决在画图之前加两行配置。Windows 下用SimHeiMac 下用Arial Unicode MSLinux 下用WenQuanYi Micro Hei。如果找不到可用字体直接把图里的中文标签改成英文省事且不会出错。plt.rcParams[font.sans-serif] [SimHei] # Windows 示例 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题6. 把实验报告写出技术深度三个让结论更硬核的进阶技巧第一个技巧是画学习曲线。固定阶数为 15把训练集大小从 5 逐步增加到 21每个大小下训练多次取平均画出训练误差和验证误差随样本量变化的曲线。如果两条曲线随着样本增加逐渐靠拢说明过拟合可以通过更多数据缓解如果始终有 gap说明模型复杂度本身太高需要正则化或降阶。这个图比单纯的误差-阶数曲线更有说服力因为它同时展示了数据量和模型复杂度的交互作用。from sklearn.model_selection import learning_curve train_sizes, train_scores, val_scores learning_curve( fit_polynomial(15, X_train, y_train), X_train, y_train, train_sizesnp.linspace(0.3, 1.0, 8), cv5, scoringneg_mean_squared_error ) train_mse_mean -train_scores.mean(axis1) val_mse_mean -val_scores.mean(axis1) plt.plot(train_sizes, train_mse_mean, o-, label训练MSE) plt.plot(train_sizes, val_mse_mean, s-, label验证MSE) plt.xlabel(训练集大小) plt.ylabel(均方误差) plt.legend() plt.title(学习曲线阶数15) plt.savefig(learning_curve.png, dpi150) plt.show()learning_curve的train_sizes参数控制每次用多少比例的训练数据cv5做交叉验证scoringneg_mean_squared_error是因为 scikit-learn 的评分函数约定越大越好所以均方误差取了负号。跑完这张图你会发现训练误差随着样本增加略微上升验证误差下降两条线在样本量接近满额时还有明显差距说明阶数 15 对这个数据量来说确实太大了。第二个技巧是对比不同正则化方法。把 Ridge、Lasso 和 ElasticNet 放在同一张图上固定阶数 15各自用交叉验证选最优参数然后比较测试 MSE。Lasso 会把一些系数压到零相当于自动做特征选择画出来的曲线可能在某些区域出现折角。ElasticNet 介于两者之间。这个对比能让你在实验报告里写出“L2 惩罚更适合本实验”这样的具体结论而不是泛泛地说正则化有用。第三个技巧是分析系数随阶数的变化。把不同阶数下拟合得到的系数向量打印出来观察高阶项的系数量级。阶数 5 的时候系数都在个位数阶数 15 的时候系数可能达到几百甚至上千。正则化之后这些系数会被显著压缩。这个分析能从数值层面解释为什么高阶多项式容易震荡系数太大输入稍微变化一点输出就剧烈波动。for d in [5, 10, 15]: model fit_polynomial(d, X_train, y_train) coefs model.named_steps[linear].coef_ print(f阶数{d}, 系数绝对值最大值{np.max(np.abs(coefs)):.2f}, f系数绝对值均值{np.mean(np.abs(coefs)):.2f})跑出来你会看到阶数 15 的系数最大值可能是阶数 5 的几十倍。这个数字比任何文字描述都更能说明问题。写实验报告的时候把这张表放进去审阅的人一眼就能看懂过拟合的数值表现。我自己做这个实验最大的教训是不要一上来就调模型先把数据生成和划分固定死把随机种子记下来。这个实验的变量太多样本量、噪声、划分比例、阶数、正则化强度任何一个变了结论都可能不一样。我一般会先跑一遍基线把数据图和误差曲线存下来后面每改一个参数就和基线对比。这样即使中间某一步翻车了也能快速定位是哪个变量引起的。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

电网与燃气协同:调峰与热电联产的技术实践 2026/10/2 4:27:02

电网与燃气协同:调峰与热电联产的技术实践

开年第一周,我就被一个电话拽回了现场。某个省级园区的能源调度中心打来求助,说前一天晚上电网负荷尖峰时段,燃气调峰机组出力顶不上,差的那几十兆瓦全靠外购电高价扛过去,一晚上多花了近二十万。细问下来,…

阅读更多 →
Claude Code集成Veo MCP视频生成工作流详解 2026/10/2 4:26:56

Claude Code集成Veo MCP视频生成工作流详解

1. 项目概述:这不是“调用API”,而是一次工作流重构你有没有试过在写代码时,突然需要一段演示视频——比如给客户展示某个UI交互逻辑、给新同事解释一个复杂的数据流向、或者快速生成一个技术方案的可视化说明?传统做法是切出浏览…

阅读更多 →
Word自动编号原理:题注、多级列表与交叉引用协同机制 2026/10/2 4:26:23

Word自动编号原理:题注、多级列表与交叉引用协同机制

1. 这不是“点几下就能好”的功能,而是Word里最被低估的底层排版逻辑很多人第一次在论文里遇到“图3-2”“公式(4.1)”“表5-1”这种编号时,第一反应是手动敲——结果改个章节顺序,全篇编号崩盘,引用错位,交叉引用变成…

阅读更多 →
基于MPC的储能微网双层能量管理:从原理到工程落地实践 2026/10/2 4:26:23

基于MPC的储能微网双层能量管理:从原理到工程落地实践

很多人一看到“双层模型预测控制”“能量管理”这种词,第一反应是这是纯学术圈的东西,和工程实践离得远。但说实话,我刚接触含储能微网的优化调度时也有点犯怵,等真正把模型预测控制(MPC)跑起来、和储能逆变…

阅读更多 →
SMP语言小数据系统实战:从记录、表到增删改查 2026/10/2 4:26:23

SMP语言小数据系统实战:从记录、表到增删改查

在正式聊小数据系统之前,我想先描述一个场景。我见过不少刚开始学SMP的人,一听到“数据系统”四个字,脑子里蹦出来的就是大数据、分布式、消息队列、缓存集群这些东西,然后下意识地要把一套重型方案往自己那几百条数据的程序里塞。…

阅读更多 →
Go并发编程详解:sync.Cond条件变量的原理与实战 2026/10/2 4:26:23

Go并发编程详解:sync.Cond条件变量的原理与实战

1. 先搞清楚sync.Cond到底解决什么问题在Go的并发编程里,锁能保证同一时刻只有一个协程访问共享数据,但很多场景下,我们不只是要“互斥”,而是要“等待某个条件成立后再继续干活”。比如:一个生产者往队列里放数据&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉