ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多项式拟合正弦曲线实战:最小二乘、正则化与梯度下降手写实现

多项式拟合正弦曲线实战:最小二乘、正则化与梯度下降手写实现 简介这份资源面向机器学习初学者与课程实验学习者围绕多项式拟合正弦曲线这一经典任务提供完整的Python实现与实验报告。内容涵盖最小二乘法解析解、带2范数惩罚项的正则化优化、梯度下降与共轭梯度法的手写实现并引导读者通过调整数据量、超参数与多项式阶数直观理解过拟合现象及其克服方法。资源包共4个文件包含3个py源码与1份doc实验报告压缩包约581KB源码含详细注释便于对照学习算法细节与矩阵求逆、梯度迭代等关键步骤。目前已有183人学习下载。读者可获得可直接运行的实验代码、完整实验报告以及从数据生成、加噪到模型对比的完整流程适合作为机器学习课程作业参考或算法入门练手材料。1. 从一份 lab1.zip 说起多项式拟合正弦曲线到底在练什么很多人第一次接触机器学习是从「用多项式去逼近一条正弦曲线」开始的。听起来简单但它几乎把监督学习的核心矛盾全塞进了一个实验里数据带噪声、模型阶数可调、损失函数分有无正则、求解分解析解和迭代法最后还要你亲手解释什么叫过拟合。这份 lab1.zip 就是围绕这条主线展开的里面包含 init_data.py、gradient_descent.py、least_square.py 三个脚本和一份实验报告环境是 Windows10 Python3.7 PyCharm2021.2。它不依赖 PyTorch、TensorFlow 这类自动微分框架梯度要自己推、迭代要自己写适合想真正搞懂「最小二乘、正则化、梯度下降、共轭梯度」这几件事底层怎么跑的人。如果你正在做机器学习期末实验或者想找一个不调库、纯手写优化的小项目练手这份资源能直接省掉搭骨架的时间。2. 数据生成与最小二乘解析解先把基准线跑通2.1 init_data.py 在做什么正弦采样加高斯噪声拿到这份代码第一步不是急着拟合而是先看清楚数据是怎么造出来的。init_data.py 的职责很单一在 [0, 2π] 区间上均匀采样算出对应的 sin 值再叠加一个高斯噪声。这一步决定了后面所有实验的上限——噪声方差设多大、采样点取多少直接影响到过拟合出现的早晚。常见做法是用 numpy 完成采样和加噪核心逻辑大致如下import numpy as np def generate_data(n_samples20, noise_std0.2, seed42): np.random.seed(seed) # 固定随机种子保证实验可复现 x np.linspace(0, 2 * np.pi, n_samples) # 在 [0, 2π] 上均匀取点 y_true np.sin(x) # 无噪声的真实曲线 noise np.random.normal(0, noise_std, sizex.shape) # 高斯噪声 y y_true noise # 观测值 真实值 噪声 return x, y, y_true逻辑说明np.linspace保证采样点在区间内均匀分布避免随机采样带来的分布不均np.random.seed是实验可复现的关键换种子会得到不同曲线写报告时最好固定。参数方面n_samples控制数据量实验要求里明确要「用不同的数据量比较效果」所以这个值应该做成可调noise_std控制噪声强度0.1 到 0.3 之间是比较合理的区间太小看不出过拟合太大连真实趋势都被淹没。提示噪声种子和数据量这两个参数建议在报告里做成表格对比而不是只跑一组。过拟合的结论必须建立在「同一模型、不同数据量」的对照上才有说服力。2.2 least_square.py无正则与有正则的解析解最小二乘的解析解是这个实验的理论核心。给定多项式基函数构造的设计矩阵 Φ无正则的损失是 ‖Φw − y‖²令梯度为零得到 w (ΦᵀΦ)⁻¹Φᵀy加入 2 范数惩罚后损失变成 ‖Φw − y‖² λ‖w‖²解变为 w (ΦᵀΦ λI)⁻¹Φᵀy。这两行公式就是 least_square.py 要落地的东西。import numpy as np def design_matrix(x, degree): # 构造 [1, x, x^2, ..., x^degree] 的范德蒙德矩阵 return np.vander(x, degree 1, increasingTrue) def least_square_closed_form(x, y, degree, lam0.0): Phi design_matrix(x, degree) # 设计矩阵形状 (N, degree1) I np.eye(Phi.shape[1]) # 单位阵用于正则项 # 有正则时加 lam*I无正则时 lam0 退化为普通最小二乘 w np.linalg.solve(Phi.T Phi lam * I, Phi.T y) return w逻辑说明用np.linalg.solve而不是直接求逆数值上更稳避免显式求逆带来的精度损失。lam0时这段代码就是无正则解lam0时就是带 2 范数惩罚的解一个函数覆盖两种 loss方便对照。参数上degree是多项式阶数实验要求里要「用高阶多项式拟合」可以取 3、5、9、15 做对比lam是正则系数常见取值 1e-6 到 1e-1 之间按对数刻度扫。注意当 degree 很高而数据量很小时ΦᵀΦ 会接近奇异直接求解会出现数值不稳定这也是为什么高阶无正则模型容易「炸」。加 λI 本质上是在改善条件数这一点在报告里值得单独写一段。3. 梯度下降与共轭梯度自己推梯度、自己写迭代3.1 gradient_descent.py手写梯度下降的收敛控制实验要求明确不许用自动微分梯度必须自己求。对无正则损失 L (1/2N)‖Φw − y‖²梯度是 (1/N)Φᵀ(Φw − y)加正则后梯度多一项 λw。梯度下降的更新就是 w ← w − η∇L。看起来简单但学习率 η 选不好要么震荡不收敛要么慢到跑不完。import numpy as np def gradient_descent(x, y, degree, lr0.01, epochs5000, lam0.0, tol1e-8): Phi np.vander(x, degree 1, increasingTrue) N, D Phi.shape w np.zeros(D) # 从零初始化 loss_history [] for epoch in range(epochs): pred Phi w grad (1.0 / N) * (Phi.T (pred - y)) lam * w # 无正则时 lam0 w - lr * grad loss (1.0 / (2 * N)) * np.sum((pred - y) ** 2) 0.5 * lam * np.sum(w ** 2) loss_history.append(loss) if epoch 0 and abs(loss_history[-1] - loss_history[-2]) tol: break # 损失变化过小提前停止 return w, loss_history逻辑说明梯度里除以 N 是做平均避免数据量变化时梯度尺度跟着变这一点在「不同数据量对比」时尤其重要。lr是学习率高阶多项式下特征尺度差异大建议先做特征归一化或者把 lr 调到 1e-3 量级epochs和tol配合做提前停止避免无意义的空转。loss_history一定要留下来画收敛曲线是报告里证明「迭代确实在下降」的直接证据。提示如果损失曲线出现明显震荡八成是学习率过大如果曲线几乎水平但损失还很高多半是学习率过小或者迭代次数不够。这两种现象在报告里都可以作为调参讨论的素材。3.2 共轭梯度法为什么它比梯度下降快梯度下降沿负梯度方向走相邻两步的方向容易正交导致在狭长山谷里来回折返。共轭梯度法通过构造一组关于 ΦᵀΦ 共轭的搜索方向理论上对二次型问题能在 D 步内收敛D 是参数维度。对于最小二乘这种本身就是二次型的问题共轭梯度天然合适。import numpy as np def conjugate_gradient(x, y, degree, lam0.0, max_iterNone, tol1e-10): Phi np.vander(x, degree 1, increasingTrue) N, D Phi.shape A (1.0 / N) * (Phi.T Phi) lam * np.eye(D) # 二次型矩阵 b (1.0 / N) * (Phi.T y) w np.zeros(D) r b - A w # 初始残差 p r.copy() # 初始搜索方向 if max_iter is None: max_iter D # 理论上 D 步内收敛 for _ in range(max_iter): Ap A p alpha (r r) / (p Ap 1e-12) # 步长加小量防除零 w alpha * p r_new r - alpha * Ap if np.linalg.norm(r_new) tol: break beta (r_new r_new) / (r r 1e-12) p r_new beta * p r r_new return w逻辑说明A 就是正则化后的 ΦᵀΦ/Nb 是 Φᵀy/N整个问题化归为解 Aw b。alpha和beta是共轭梯度法的两个核心系数分别控制步长和方向更新。max_iter默认取参数维度 D因为理论上 D 步收敛tol控制残差阈值。和梯度下降相比共轭梯度不需要设学习率收敛更快但对数值精度更敏感p Ap接近零时要加小量保护。注意共轭梯度在无正则、高阶、小数据的情况下同样会受病态矩阵影响残差可能降不下去。这时候要么加正则要么降低阶数别硬跑。4. 过拟合的复现与排查把现象和原因对上号4.1 用阶数和数据量把过拟合「逼」出来过拟合不是一句概念而是要能在图上指出来。做法很直接固定数据量比如 20 个点把多项式阶数从 3 拉到 15观察拟合曲线。低阶时曲线平滑但欠拟合高阶时曲线穿过每个噪声点、在数据点之间剧烈震荡这就是过拟合的典型形态。再反过来固定阶数 15把数据量从 20 加到 200你会看到同一条高阶曲线逐渐变得平滑——数据量本身就是一种正则。阶数数据量现象对应结论320曲线平滑偏离真实 sin欠拟合920训练误差小点间震荡轻度过拟合1520穿过所有噪声点剧烈震荡严重过拟合15200曲线重新变平滑数据量缓解过拟合1520 λ0.01震荡被压制正则缓解过拟合这张表建议直接放进实验报告它把「阶数、数据量、正则」三个变量对过拟合的影响一次性说清楚了。4.2 避坑与常见问题排查现象一解析解算出来的权重数值巨大。原因通常是高阶无正则下 ΦᵀΦ 接近奇异条件数爆炸。解决办法是加正则项 λI或者降低阶数或者对输入做归一化。别指望用np.linalg.inv硬求逆能救回来换成np.linalg.solve也只是缓解。现象二梯度下降损失不降反升。九成是学习率过大。高阶多项式下各阶特征的尺度差异极大x¹⁵ 和 x¹ 差了十几个数量级梯度尺度严重不平衡。解决办法是先对 x 做归一化到 [−1,1]再把学习率降到 1e-3 甚至更低配合损失曲线观察。现象三共轭梯度迭代几步就停但结果明显不对。检查p Ap是否接近零这通常意味着搜索方向已经和矩阵的零空间沾边数值上退化了。加正则项能改善或者在 alpha 分母上加一个和矩阵尺度匹配的小量。现象四换一组随机种子结论完全变了。小数据量下这是正常的20 个点的采样本身方差就大。解决办法是固定种子做单组分析同时用多组种子跑统计报告里给出均值和方差而不是拿一组数据下结论。现象五梯度下降和解析解结果对不上。先检查两者的损失定义是否一致——梯度下降里除以 N 做平均解析解里没有这会导致正则项的等效强度不同。统一损失定义后再对比否则对比没有意义。5. 把这份代码用透从跑通到写出有说服力的报告5.1 参数扫描与结果记录跑通单个脚本只是起点这份实验真正的价值在于「比较」。建议把 degree、n_samples、lam、lr 四个参数做成可配置入口用循环批量跑把每组的结果训练误差、测试误差、权重范数记下来。权重范数 ‖w‖ 是判断过拟合的一个很好的量化指标过拟合时权重往往很大加正则后会被压下来。import numpy as np from least_square import least_square_closed_form, design_matrix def evaluate(x_train, y_train, x_test, y_test, degree, lam): w least_square_closed_form(x_train, y_train, degree, lam) Phi_tr design_matrix(x_train, degree) Phi_te design_matrix(x_test, degree) train_err np.mean((Phi_tr w - y_train) ** 2) test_err np.mean((Phi_te w - y_test) ** 2) return train_err, test_err, np.linalg.norm(w) # 扫描阶数与正则系数观察训练/测试误差的分化 for degree in [3, 5, 9, 15]: for lam in [0.0, 1e-4, 1e-2, 1e-1]: tr, te, wn evaluate(x_tr, y_tr, x_te, y_te, degree, lam) print(fdegree{degree:2d} lam{lam:.0e} train{tr:.4f} test{te:.4f} |w|{wn:.2f})逻辑说明训练误差和测试误差要分开算过拟合的标志就是训练误差持续下降而测试误差先降后升。|w|作为辅助指标能解释为什么正则有效。参数上lam 按对数刻度扫因为它的有效区间通常跨越几个数量级。5.2 一个容易被忽略的细节训练集和测试集的划分很多同学跑这个实验时直接在全部数据上拟合、在全部数据上算误差然后得出「高阶拟合很好」的结论——这是典型的自欺欺人。正确做法是留出一部分数据做测试或者用同一区间重新采样一组带不同噪声的点做验证。只有训练误差和测试误差出现分化过拟合的讨论才站得住脚。我一般会固定训练集 20 个点、测试集 100 个点测试集噪声单独设种子这样两组数据独立结论更干净。5.3 报告里值得写进去的三件事第一把解析解和迭代解的结果放在一起对比说明在什么条件下两者一致、什么条件下迭代解因为收敛问题偏离。第二把共轭梯度和梯度下降的收敛曲线画在同一张图上用迭代次数说话而不是只说「共轭梯度更快」。第三把正则系数对权重范数的影响做成曲线直观展示正则如何压制过拟合。这三件事做完这份实验报告就不只是「跑通了代码」而是真正把最小二乘、正则化、优化方法、过拟合四条线串起来了。从那以后我每次做这类拟合实验都会强制先固定随机种子、再划分训练测试集、最后才动模型参数——顺序反了后面所有对比都是白跑。希望这份 lab1.zip 能帮你把这几步走扎实需要的话直接拿去跑一遍比看十篇理论推导都管用。本文还有配套的精品资源点击获取
返回列表