ARTICLE DETAIL

资讯详情

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

正则化多项式拟合实战:从最小二乘到岭回归与LASSO的Python手写实现

正则化多项式拟合实战:从最小二乘到岭回归与LASSO的Python手写实现 简介面向本科毕业设计这是一套基于Python基本算法实现正则化多项式拟合的资源包主要解答数据分析与机器学习实验中常见的过拟合问题。压缩包共31个文件包含10余个Python脚本.py及其备份4个Word实验文档docx/doc以及MATLAB脚本.m等辅助文件整体仅943KB轻量便携。目前已有37人学习适合具备一定Python基础、需要快速完成多项式拟合与正则化对比实验的学生。内容从最小二乘多项式拟合入手逐步加入L1/L2正则化惩罚项并配合梯度下降优化与可视化分析完整呈现数据生成、模型训练、评估调参的流程通过图表对比不同正则化参数的效果。同时附带主成分分析、EM算法、逻辑斯蒂回归等关联实验代码与报告可帮助毕设者理解不同算法的原理与实现细节并复用其代码撰写毕业设计中的实验章节。1. 正则化的多项式拟合这份毕设资源到底能解决什么做毕设做到回归拟合这块很多人会卡在同一个问题上多项式阶数一高模型在训练集上漂亮得不行一换数据就原形毕露。这份压缩包里的核心内容就是帮你把“正则化的多项式拟合”这件事从头到尾用 Python 基础语法实现一遍——不依赖 sklearn 的高级封装而是用 NumPy 手写最小二乘、构造设计矩阵、实现梯度下降把岭回归和 LASSO 的惩罚项是怎么加进去的、系数是怎么被压缩的一步步拆开看明白。它适合两类人一是正在做数据分析或机器学习方向毕设的本科生需要一份能跑通、能解释、能写进论文的完整代码二是想搞清楚正则化内部机理、不想只调包的从业者。压缩包里除了正则化拟合的.py文件还有 PCA、EM 算法、逻辑斯蒂回归等配套实验代码和文档等于一个小型机器学习算法工具箱。2. 从最小二乘到正则化为什么要给损失函数加惩罚项2.1 欠拟合、过拟合与模型容量多项式阶数是第一道闸门多项式拟合的本质是用一个n阶多项式去逼近一组散点数据。阶数n决定了模型的表达能力也就是“容量”。阶数太低比如用一条直线去拟合明显带有弯曲趋势的数据误差大、结构没学到这叫欠拟合阶数太高比如用 9 阶多项式去拟合 10 个带噪声的点模型会把噪声也当成规律学进去训练集误差极小但一到新数据就崩这叫过拟合。这块的数学基础是最小二乘法。给定数据点(x_i, y_i)我们要找一个多项式f(x) w_0 w_1 x w_2 x^2 ... w_n x^n使得残差平方和最小L(w) Σ(y_i - f(x_i))²写成矩阵形式就是L(w) ||y - Xw||²其中X是设计矩阵每一行是[1, x_i, x_i², ..., x_i^n]。这个方程有解析解w (X^T X)^(-1) X^T y但问题来了当n很大时X^T X可能接近奇异矩阵求逆数值不稳定而且系数w的绝对值会非常大这是过拟合的典型信号——模型为了穿过每个点把系数调得极大曲线剧烈震荡。2.2 L1 与 L2 正则化的本质区别从岭回归到 LASSO正则化的思路很直接在损失函数后面加一个惩罚项约束系数的大小。L2 正则化岭回归的损失函数是L(w) ||y - Xw||² λ ||w||₂² ||y - Xw||² λ Σ w_j²L1 正则化LASSO的损失函数是L(w) ||y - Xw||² λ Σ |w_j|两者都让系数向零收缩但方式完全不同。L2 是连续均匀地压缩系数会很小但通常不为零L1 会把一部分系数精确压缩到零天然做特征选择。在多项式拟合场景里高阶项的系数被 L1 压成零就等价于自动降低了多项式有效阶数。压缩包里regulazation.py和regulazationl2.py两个文件正好对应 L1 和 L2 两种实现。我在做毕设的时候一般两个都跑一遍对比看哪个惩罚项在这个数据集上更合适。L1 适合你怀疑很多高阶项根本没用的场景L2 适合所有特征都有微弱贡献的场景。如果既想要 L1 的稀疏又想要 L2 的稳定可以试 Elastic Net但纯手写实现时它的代码量会多一层。2.3 正则化系数如何选网格搜索与学习曲线λ是正则化强度的超参数。λ太小惩罚项形同虚设过拟合依旧λ太大所有系数都被压到接近零模型退化成一条水平线欠拟合。常见做法是把λ按数量级扫一遍比如[0.0001, 0.001, 0.01, 0.1, 1, 10, 100]对每个λ在训练集上拟合、在验证集上算误差选验证误差最小的那个。这个思路放在代码里就是一重循环成本主要在矩阵求逆或梯度迭代上数据量不大时几秒钟就跑完了。还有一种更直观的判断方式画出不同λ下的拟合曲线。λ小的曲线剧烈震荡λ大的曲线平滑但可能偏离数据点取一个折中点让曲线既贴合数据趋势又不出现明显抖动。这种“眼睛看曲线”的方法虽然不够严谨但在毕设里用作定性分析配合定量指标RMSE、R²一起写进论文说服力比只贴一个数字强很多。3. 用 Python 徒手实现正则化拟合不调 sklearn 也能跑3.1 手动构造设计矩阵与多元线性回归这份资源里最有价值的地方在于它没有直接让你sklearn.linear_model.Ridge一把梭而是把底层计算拆开。第一步是构造设计矩阵。给定原始的自变量x数组和阶数degree我们需要把每个x_i扩展成[x_i^0, x_i^1, ..., x_i^degree]的行向量。import numpy as np def build_polynomial_features(x, degree): 构造多项式特征的设计矩阵 X x: 一维数组原始自变量 degree: 多项式最高阶数 返回: shape 为 (len(x), degree1) 的矩阵 x np.asarray(x).reshape(-1, 1) # 转成列向量方便广播 powers np.arange(degree 1) # [0, 1, 2, ..., degree] X np.power(x, powers) # 每个 x_i 的 0~degree 次幂 return X这里np.power(x, powers)利用了 NumPy 的广播机制x是(m, 1)的列向量powers是(degree1,)的数组广播后得到(m, degree1)的矩阵。第 0 列全是 1对应偏置项w_0。这一步是理解多项式拟合的钥匙——拟合算法本身并不关心特征是不是多项式它只看到一堆数值列多项式特征只是把原始x做了非线性变换。有了设计矩阵最小二乘的解析解就可以直接算了。注意我在代码里加了正则项所以X^T X的对角线上要加λdef ridge_regression_fit(X, y, lambda_val): 岭回归解析解: w (X^T X λI)^(-1) X^T y 单位矩阵 I 的第一个元素不惩罚因为偏置项不需要收缩 m, n X.shape I np.eye(n) I[0, 0] 0 # 偏置项不参与正则化 A X.T X lambda_val * I w np.linalg.inv(A) X.T y return wlambda_val就是上面的λ。I[0, 0] 0这行是关键细节偏置w_0只负责平移曲线不应当被惩罚否则λ一大整条曲线就被强行拉向原点。这个细节在很多调包教程里看不到但手写实现时必须处理。如果数据做过中心化处理也可以不设这一项效果差别不大但规范做法还是单独放行偏置。3.2 批量梯度下降实现 L2 正则化解析解虽然简洁但要理解优化过程必须走一遍梯度下降。正则化的损失函数对w求梯度∂L/∂w -2X^T(y - Xw) 2λw注意这里同样不惩罚偏置项。梯度下降的迭代公式是w w - lr * (-2X^T(y - Xw) 2λw)写成代码def ridge_regression_gd(X, y, lambda_val, lr0.01, epochs1000): 批量梯度下降求解岭回归 lr: 学习率 epochs: 迭代轮数 返回: 训练好的权重向量 w m, n X.shape w np.zeros(n) # 权重初始化为 0 for epoch in range(epochs): y_pred X w # 当前预测值 grad -2 * X.T (y - y_pred) 2 * lambda_val * w grad[0] -2 * X[:, 0] (y - y_pred) # 偏置项梯度不含正则分量 w w - lr * grad if epoch % 200 0: loss np.mean((y - y_pred) ** 2) lambda_val * np.sum(w[1:] ** 2) print(fepoch {epoch}, loss {loss:.6f}) return w学习率lr是这里最敏感的参数。太大损失函数震荡不收敛太小迭代几百轮还在原地。我一般从0.01开始试如果 loss 在前 100 轮不下降就调小一个量级如果 loss 爆炸变成nan马上调小。epochs设 1000 是保守值实际运行看 loss 曲线如果已经平稳了很久可以提前break。另有emsamesigma.py和emsolute.py这类的文件是 EM 算法的变体和拟合实验的核心无关可以单独看。3.3 解析解与梯度下降的差异何时用哪种解析解w (X^T X λI)^(-1) X^T y一步到位不涉及学习率不需要迭代数据量小的场景首选。但当特征维度很高比如多项式阶数到 20 以上X^T X是 21x21 的矩阵求逆虽然不至于算不动但数值稳定性会变差尤其当λ很小时X^T X接近奇异np.linalg.inv的结果会包含巨大的数值误差。这时候梯度下降或np.linalg.pinv伪逆更稳。梯度下降的另一个好处是便于扩展——换成随机梯度下降、小批量梯度下降或者换成 L1 正则化用近端梯度或坐标下降代码改动都很小。L1 正则化没有解析解因为|w_j|不可导所以如果你想实现 LASSO 而不掉 sklearn只能走迭代类算法。压缩包里的regulazation.py我猜就是 L1 的近端梯度实现核心是软阈值操作def soft_threshold(w_j, threshold): 软阈值算子LASSO 迭代更新的核心 当 |w_j| threshold 时压为 0否则向原点收缩 if w_j threshold: return w_j - threshold elif w_j -threshold: return w_j threshold else: return 0.0这个函数的行为值得细看阈值threshold就等于λ * lr。当系数绝对值小于阈值直接归零大于阈值则整体减去阈值。这就是 L1 产生稀疏解的数学原因。跑 LASSO 时观察每一轮有多少系数被清零能直观感受到“特征选择”是怎么发生的。4. 压缩包里的完整实验流程数据构造、可视化与模型评估4.1 从多项式函数实验文档看实验设计压缩包里的多项式函数拟合实验.docx和逻辑斯蒂回归实验.docx是两篇实验指导文件。前者描述了整个拟合实验的流程生成带噪声的样本数据、设定不同多项式阶数、对比有无正则化的拟合效果。后者是逻辑斯蒂回归的配套实验和主主题互补。做毕设时这两份文档的价值在于你可以直接套用它们的实验结构——先描述问题背景再给出数据生成方式然后展示不同参数下的结果对比最后分析原因。这正好就是论文中“实验设计”和“结果分析”两章的骨架。实验设计的核心是控制变量。比如固定数据生成函数为y sin(x) 噪声在这个基础上分别跑阶数 1、3、6、9 的拟合再对同一个 9 阶模型跑不同λ的正则化记录每种配置的训练误差和验证误差。控制变量做得好论文里就可以明确说“误差变化是由阶数/正则化系数引起的”而不是数据随机波动导致的偶然现象。4.2 数据预处理归一化和特征缩放的坑多项式特征有个天然的问题x的取值范围如果较大比如 0 到 100x^9的量级是 10 的 18 次方设计矩阵的列之间尺度差异悬殊。这会带来两个后果一是解析解中X^T X的条件数极大数值不稳定二是梯度下降时高阶特征的梯度远大于低阶特征收敛极慢。常见做法是先把x归一化到[-1, 1]区间再做多项式特征构造。这一步对拟合精度的影响经常被低估。代码写起来很简单def normalize(x): 将自变量归一化到 [-1, 1] 返回归一化后的数组和映射参数预测时要用同样参数还原 x_min, x_max x.min(), x.max() x_norm 2 * (x - x_min) / (x_max - x_min) - 1 return x_norm, (x_min, x_max)注意归一化参数(x_min, x_max)必须保存下来在预测新数据时用同一组参数做变换否则模型输入分布不一致预测结果毫无意义。测试集和训练集的归一化参数应该来自训练集而不是各自独立计算这是新手最容易犯错的地方——测试集的信息提前泄露进了训练流程导致评估结果虚高。4.3 训练、验证、测试集划分报告里怎么组织数据毕设报告里如果只有一条拟合曲线和一行误差数字说服力是不够的。规范的流程是生成样本后按 6:2:2 划分训练集、验证集、测试集。训练集用来拟合权重w验证集用来选λ和阶数degree测试集最后只用一次用来报告模型的真实泛化误差。一个我常用的划分方式from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp train_test_split( X, y, test_size0.4, random_state42 ) X_val, X_test, y_val, y_test train_test_split( X_temp, y_temp, test_size0.5, random_state42 )random_state42固定随机种子保证实验可复现。test_size0.4是先分出 40% 做临时集再把这 40% 对半分成验证集和测试集最终比例 6:2:2。这里值得解释的是为什么random_state这么重要毕设实验经常需要反复跑对比如果每次随机划分的数据都不一样两次实验的差异就混杂了数据随机性的影响无法归因于模型配置的改变。固定随机种子后每次跑都是同一份数据划分对比才有意义。4.4 可视化用 matplotlib 画出拟合曲线与误差曲线压缩包里的代码大概率用了 matplotlib 展示拟合效果。标准的可视化包括三张图第一张画原始数据散点和不同阶数多项式拟合曲线第二张画训练误差和验证误差随阶数变化的曲线用来展示过拟合拐点第三张画训练误差随λ变化的曲线展示正则化强度的影响。import matplotlib.pyplot as plt def plot_fit_curves(x, y, x_grid, y_pred_list, labels): plt.figure(figsize(8, 5)) plt.scatter(x, y, s30, labeldata, zorder3) for y_pred, label in zip(y_pred_list, labels): plt.plot(x_grid, y_pred, labellabel, linewidth2) plt.xlabel(x) plt.ylabel(y) plt.legend() plt.grid(alpha0.3) plt.show()画误差曲线时注意纵轴用对数尺度因为误差从 0.1 到 1e-6 跨越好几个量级线性坐标会把小误差的差异压得看不见。用plt.yscale(log)一行就能解决。第三张图是λ从 1e-5 到 1e2 的对数扫描横轴也用plt.xscale(log)不然前面几个点挤在一起没法看。5. 避坑与排查多项式拟合里最容易翻车的六个细节5.1 过拟合的“玄学”判断训练误差下降了但验证误差在涨现象训练集误差继续下降验证集误差开始反弹典型的过拟合信号。原因模型容量过大把训练集的噪声细节都记住了。解决停止提高阶数引入正则化或增加训练样本量。判断时不要只看训练误差一定要同时监控验证集。我见过有人把训练集误差一路降到 1e-8 当成好消息写进报告验证集误差却高得离谱——这恰恰是反例要写的是验证集曲线的“U 形”拐点。5.2 归一化参数没保存预测阶段结果乱飘现象训练时效果很好一到预测新数据结果完全对不上。原因训练时对x做了归一化但预测时直接喂了原始尺度的x模型输入分布不一致。解决把归一化的(x_min, x_max)存成变量或写进模型对象预测前先做同样的变换。血泪经验有一次我用归一化后的数据训练模型换了台电脑重新跑预测脚本忘了复制归一化参数结果输出曲线飞到十万量级排查了半天才发现是输入尺度问题。5.3 梯度下降发散loss 变成 nan现象迭代没几轮loss 打印出来是nan。原因学习率太大权重更新越过最优点梯度越来越大最终数值溢出。解决立即把学习率调小 10 倍检查特征是否归一化确认梯度公式里正则项符号对不对。还有一种情况是数据里有nan或inf训练一开跑就是nan这时先检查数据清洗。5.4 正则化过猛曲线被压成直线现象λ设得很大拟合曲线接近一条水平线验证误差反而上升。原因正则化惩罚压过了数据拟合项所有系数趋近于零模型退化为均值预测。解决把λ从大到小扫描一遍观察验证误差的 U 形。通常取验证误差最小的点但再往左移一点更小的λ会稍微过拟合但拟合曲线更贴合数据趋势毕设报告里可以解释这个权衡。5.5 设计矩阵求逆报错Singular matrix现象np.linalg.inv抛出LinAlgError: Singular matrix。原因X^T X不可逆通常是特征列线性相关比如重复的数据点或多余的阶数项。解决先检查数据是否有重复行再确认degree 1是否小于等于样本数最稳妥的做法是改用np.linalg.pinv求伪逆或增大λ正则项会给对角线上加一个正数让矩阵可逆。5.6 文档和代码对应不上哪些 py 文件是核心现象压缩包里一堆.py文件分不清哪个对应正则化拟合。原因文件有~后缀的备份版本还有 EM 算法和 PCA 的代码混在一起。解决先看文件名带regulazation的两个文件regulazation.py是 L1 实现regulazationl2.py是 L2 实现这两个是主实验的核心代码。pca.py、pca_final.py是主成分分析实验em.py、emsolute.py、emsamesigma.py是 EM 算法实验lr.py、lrmap.py是逻辑斯蒂回归实验。毕设如果只需要多项式拟合这部分就跑前两个文件其他文件可以留作方法对比。6. 把这份代码改造成自己的实验参数扫描与交叉验证拿到压缩包里的代码后最快上手的路径不是逐行读而是先跑通regulazationl2.py然后按自己的数据集改三处数据生成函数、多项式阶数范围、λ扫描范围。在此基础上加一个 k 折交叉验证让实验结果更有说服力。交叉验证的实现思路是把训练集分成 k 份轮流取 k-1 份训练、1 份验证最后取 k 次的平均验证误差作为该λ的评分from sklearn.model_selection import KFold def cross_validate_lambda(X, y, lambda_candidates, k5): 对每个候选 λ 做 k 折交叉验证返回平均验证误差 X, y: 已经构造好的多项式特征和目标值 lambda_candidates: 候选正则化系数列表比如 [0.0001, 0.001, 0.01, 0.1, 1, 10] kf KFold(n_splitsk, shuffleTrue, random_state42) scores [] for lambda_val in lambda_candidates: val_errors [] for train_idx, val_idx in kf.split(X): X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx] w ridge_regression_fit(X_train, y_train, lambda_val) y_pred X_val w mse np.mean((y_val - y_pred) ** 2) val_errors.append(mse) scores.append(np.mean(val_errors)) return scoresKFold里shuffleTrue打乱样本顺序避免数据按x排序后分成几段不具代表性的折。random_state42保证每次运行划分一致。跑完后找到验证误差最小的λ再用全量训练集重新拟合一次得到最终权重。这是标准的模型选择流程交叉验证负责选超参全量训练负责产出最终模型。最后补充一个写报告时常用的小技巧画一张误差-λ曲线图横轴为log(λ)纵轴为交叉验证 RMSE用plt.axvline把最优λ的位置标出来一目了然。R² 和 RMSE 两个指标都算R² 更直观地表达拟合优度RMSE 保留原始单位的误差含义。从那以后我每次做类似实验都强制走一遍交叉验证选参的流程不再凭感觉定λ。希望这篇拆解能帮你在毕设或项目里少踩几个坑。本文还有配套的精品资源点击获取
返回列表