机器学习小白实战【正则化——破解“过拟合”与“欠拟合”】前言学完线性回归和逻辑回归后我们在前面的实验中经常遇到一个问题只要给模型多加几个特征比如把 x 变成 x², x³模型就会疯狂扭曲非要强行穿过每一个数据点。这种“死记硬背”的现象在机器学习里叫过拟合Overfitting。为了解决这个问题我们需要引入正则化Regularization。今天我用一个对比实验来看看正则化到底是怎么把疯狂扭曲的曲线拉直的以及如果正则化力度太大又会出现什么后果。一、工具准备与环境配置还是老规矩不用复杂的框架用numpy算梯度用matplotlib画图。importnumpyasnpimportmatplotlib.pyplotasplt# 解决中文乱码plt.rcParams[font.sans-serif][SimHei]plt.rcParams[axes.unicode_minus]False二、生成模拟数据为了让过拟合现象足够明显我们生成了 30 个 样本样本越少模型越容易死记硬背。真实规律是y 0.5x² x 2外加一点随机噪音。但是为了给模型挖坑我并没有只用 x 去训练而是构造了 8 个特征X [x, x², x³, x⁴, x⁵, x⁶, x⁷, x⁸]。有了这么多高次方特征模型很容易画出像蛇一样扭曲的曲线。np.random.seed(42)m30# 样本少容易过拟合xnp.random.uniform(-3,3,m)y0.5*x**2x2np.random.normal(0,0.5,m)# 构造 8 个高次特征挖坑Xnp.column_stack([x**iforiinrange(1,9)])# 归一化防止高次数字太大了炸掉X_meannp.mean(X,axis0)X_stdnp.std(X,axis0)X_norm(X-X_mean)/X_std三、定义核心算法这里的重点在于改动成本函数和梯度计算的公式成本函数多了一项(λ / 2m) * Σ(w²)。这就是对权重 w 收取的“罚单”。J(w,b)12m∑i1m(h(i)−y(i))2λ2m∑j1nwj2 J(w, b) \frac{1}{2m} \sum_{i1}^{m} (h^{(i)} - y^{(i)})^2 \frac{\lambda}{2m} \sum_{j1}^{n} w_j^2J(w,b)2m1i1∑m(h(i)−y(i))22mλj1∑nwj2梯度下降在计算 w 的梯度时加上(λ / m) * w。这样每次更新时模型都会强行把 w 往 0 的方向拉一点点。defcompute_cost_reg(X,y,w,b,lambda_reg):mlen(y)hnp.dot(X,w)b mse(1/(2*m))*np.sum((h-y)**2)# 加上对 w 的惩罚项不惩罚 breg_term(lambda_reg/(2*m))*np.sum(w**2)returnmsereg_termdefgradient_descent_reg(X,y,w_init,b_init,alpha,iterations,lambda_reg):ww_init bb_init mlen(y)foriinrange(iterations):hnp.dot(X,w)b# 梯度计算多了一项 (lambda/m)*wgrad_w(1/m)*np.dot(X.T,(h-y))(lambda_reg/m)*w grad_b(1/m)*np.sum(h-y)ww-alpha*grad_w bb-alpha*grad_breturnw,b四、训练模型我设定了 3 个不同的正则化系数λ 进行训练模型 Aλ 0没有紧箍咒模拟完全自由发挥。模型 Bλ 1轻度惩罚模拟恰到好处的约束。模型 Cλ 10重度惩罚模拟过度压制。iterations2000alpha0.05w_A,b_Agradient_descent_reg(X_norm,y,np.zeros(8),0,alpha,iterations,lambda_reg0)w_B,b_Bgradient_descent_reg(X_norm,y,np.zeros(8),0,alpha,iterations,lambda_reg1)w_C,b_Cgradient_descent_reg(X_norm,y,np.zeros(8),0,alpha,iterations,lambda_reg10)print(f模型A的权重:{np.round(w_A,2)})print(f模型B的权重:{np.round(w_B,2)})print(f模型C的权重:{np.round(w_C,2)})五、结果可视化plt.figure(figsize(15,5))x_testnp.linspace(-3,3,100)X_testnp.column_stack([x_test**iforiinrange(1,9)])X_test_norm(X_test-X_mean)/X_std# 1. 无正则化过拟合plt.subplot(1,3,1)plt.scatter(x,y,colorblue,alpha0.6)y_Anp.dot(X_test_norm,w_A)b_A plt.plot(x_test,y_A,colorred,linewidth2)plt.title(λ0无正则化 (过拟合))plt.grid(True)# 2. 轻度正则化最佳模型plt.subplot(1,3,2)plt.scatter(x,y,colorblue,alpha0.6)y_Bnp.dot(X_test_norm,w_B)b_B plt.plot(x_test,y_B,colorgreen,linewidth2)plt.title( λ1轻度正则化 (完美))plt.grid(True)# 3. 重度正则化欠拟合plt.subplot(1,3,3)plt.scatter(x,y,colorblue,alpha0.6)y_Cnp.dot(X_test_norm,w_C)b_C plt.plot(x_test,y_C,colororange,linewidth2)plt.title(λ10重度正则化 (欠拟合))plt.grid(True)plt.tight_layout()plt.show()运行结果如下图所示通过这三张图的对比我们可以非常直观地看清正则化的威力过拟合左图无正则化时红线像蛇一样扭曲。模型把训练集的每一个微小波动甚至噪音都死死记住了导致高次项的权重非常巨大。完美拟合中图加入轻度正则化λ1后红线变得平滑、自然。模型捕捉到了数据的核心趋势同时忽略掉了噪音。这是因为正则化强制将那些不必要的权重缩小了。欠拟合右图正则化力度过大λ10时模型的权重被压缩到了近乎 0导致它连基本的趋势都抓不住了变成了一条过度平缓的曲线。总结正则化本质上就是给模型套上一道“紧箍咒”。在训练过程中普通的模型会为了讨好训练集里的每一个数据点——尤其是那些极端的“异常值”——而过度扭曲自己的形态导致原本应该平滑的规律变成疯狂波动的过山车这就是过拟合。这种模型虽然能在训练集上拿满分但在面对未知的新数据时往往表现得一塌糊涂。正则化L2所做的是在模型计算“损失错误率”的同时额外强行加上一笔“罚单”。这笔罚单会惩罚模型中那些数值巨大的权重。为了少交罚款模型就必须把权重缩得更小、更克制。一句话总结正则化是用一点点“训练集上的误差”作为代价换取了模型在真实世界中更强的“泛化能力”。加得恰到好处模型就能做到“坚守全局规律无视局部噪音”画出一条稳定笔直的线加得太多模型就会被吓破胆变成一条毫无预测能力的直线欠拟合。所以找到一个适中的“惩罚力度 λ”是正则化实战中最核心的艺术。