一、正则化概述
在机器学习中,模型不仅需要在训练数据上表现良好,更重要的是在未知的测试数据上具备泛化能力。然而,当模型复杂度过高时,容易出现过拟合现象——模型过度拟合训练数据中的噪声和细节,导致在新数据上的预测能力下降。
正则化(Regularization) 是一类通过限制模型复杂度、缓解过拟合现象的技术手段。从广义上讲,任何能够降低模型在训练数据上的过拟合程度、提升泛化能力的方法,都可以被称作正则化。其核心逻辑是通过不同机制约束模型的“表达能力”,避免模型学习到训练数据中的噪声或特定细节,从而迫使模型捕捉更具普适性的模式。
过拟合的概念:网页连接
1.1 正则化的核心目标
- 抑制过拟合:防止模型对训练数据中的噪声、异常值或局部特征过度敏感。
- 平衡复杂度与泛化性:在模型对训练数据的拟合能力和对新数据的预测能力之间找到平衡点。
1.2 正则化的常见实现方向
- 参数约束:通过惩罚项限制模型参数的取值范围(如L1/L2正则化)。
- 数据增强:扩大训练数据的多样性,避免模型记忆特定样本(如图像旋转、文本扩充)。
- 训练过程干预:通过提前终止训练(早停法)或随机丢弃神经元(Dropout)防止模型过度拟合。
- 输入规范化:通过标准化输入数据(批量归一化)稳定训练过程,间接抑制过拟合。
这种广义的定义下,正则化不再局限于“损失函数中的惩罚项”,而是涵盖了从数据预处理到模型结构设计、训练过程控制的全流程优化策略。
1.3 常用正则化手段
- L1正则化(Lasso回归)
- L2正则化(Ridge回归)
- Dropout(随机失活)
- 批量归一化(Batch Normalization)
- 早停法(Early Stopping)
- 数据增强(Data Augmentation)
二、主流正则化技术详解
2.1 L1正则化(Lasso回归)
L1正则化通过在损失函数中添加参数的绝对值之和作为惩罚项:
数学公式:
损失函数
=
L
(
y
,
y
^
)
+
α
∑
i
=
1
n
∣
w
i
∣
\text{损失函数} = \mathcal{L}(y, \hat{y}) + \alpha \sum_{i=1}^{n} |w_i|
损失函数=L(y,y^)+αi=1∑n∣wi∣
其中,
L
(
y
,
y
^
)
\mathcal{L}(y, \hat{y})
L(y,y^) 可以是任意损失函数(如MSE、交叉熵等),
α
\alpha
α 是正则化强度,
w
i
w_i
wi 是模型参数。
核心逻辑:用“绝对值惩罚”逼退无效权重
- 为什么参数会变0?
假设某个权重 w w w 对预测影响很小,绝对值惩罚项 ∣ w ∣ |w| ∣w∣ 会“催促” w w w 向0靠近。例如,当 w = 0.5 w=0.5 w=0.5 时,惩罚项为0.5;当 w = 0 w=0 w=0 时,惩罚项为0。模型为了最小化总损失,会主动让不重要的 w w w 变为0,就像“修剪树枝”一样砍掉无效特征的影响。 - 与损失函数无关:不管损失函数是MSE还是其他类型,L1正则化始终通过绝对值惩罚强制参数稀疏化,因此常用于特征选择(让无关特征的权重直接归零)。
常见的损失函数:网页链接
2.2 L2正则化(Ridge回归)
L2正则化在损失函数中添加参数的平方和作为惩罚项:
数学公式:
损失函数
=
L
(
y
,
y
^
)
+
α
∑
i
=
1
n
w
i
2
\text{损失函数} = \mathcal{L}(y, \hat{y}) + \alpha \sum_{i=1}^{n} w_i^2
损失函数=L(y,y^)+αi=1∑nwi2
其中,
L
(
y
,
y
^
)
\mathcal{L}(y, \hat{y})
L(y,y^) 可为任意损失函数,
α
\alpha
α 控制正则化强度。
核心逻辑:用“平方惩罚”软化参数规模
- 为什么参数趋近于0但不为0?
平方惩罚项 w 2 w^2 w2 对大参数的惩罚更剧烈(如 w = 10 w=10 w=10 惩罚100, w = 1 w=1 w=1 惩罚1),但对接近0的参数惩罚很轻(如 w = 0.1 w=0.1 w=0.1 惩罚0.01)。因此,L2正则化会让所有参数“集体瘦身”,但不会直接砍到0,就像给所有树枝“均匀修剪”,保持模型的平滑性。 - 抗噪声能力:L2正则化同样有与与损失函数无关的特性,当特征间存在共线性(如房价预测中“面积”和“使用年限”高度相关),L2正则化会让相关特征的权重同时缩小,避免模型过度依赖某一特征,类似“不把鸡蛋放在一个篮子里”。
2.3 L1与L2正则化的数学示例演示
场景设定
假设我们有一个一维线性模型:
y
=
w
⋅
x
y = w \cdot x
y=w⋅x,给定一组数据点
(
x
=
1
,
y
=
2
)
(x=1, y=2)
(x=1,y=2)。我们通过均方误差(MSE)损失函数训练模型,并分别添加L1和L2正则化,观察参数
w
w
w 的变化。
-
无正则化的原始优化
- 损失函数:
L = ( w ⋅ 1 − 2 ) 2 = ( w − 2 ) 2 \mathcal{L} = (w \cdot 1 - 2)^2 = (w - 2)^2 L=(w⋅1−2)2=(w−2)2 - 求最优解:对
w
w
w 求导并令导数为0:
d L d w = 2 ( w − 2 ) = 0 ⇒ w ∗ = 2 \frac{d\mathcal{L}}{dw} = 2(w - 2) = 0 \quad \Rightarrow \quad w^* = 2 dwdL=2(w−2)=0⇒w∗=2 - 结果:无正则化时,最优解为 w = 2 w=2 w=2。
- 损失函数:
-
L1正则化的优化过程
-
损失函数:
L 1 = ( w − 2 ) 2 + α ∣ w ∣ \mathcal{L}_1 = (w - 2)^2 + \alpha |w| L1=(w−2)2+α∣w∣ -
分情况求导(假设 α = 1 \alpha=1 α=1):
- 当
w
>
0
w > 0
w>0 时,
L
1
=
(
w
−
2
)
2
+
w
\mathcal{L}_1 = (w-2)^2 + w
L1=(w−2)2+w,导数为
2
(
w
−
2
)
+
1
=
2
w
−
3
2(w-2) + 1 = 2w - 3
2(w−2)+1=2w−3
令导数为0: 2 w − 3 = 0 ⇒ w ∗ = 1.5 2w - 3 = 0 \quad \Rightarrow \quad w^* = 1.5 2w−3=0⇒w∗=1.5 - 当
w
<
0
w < 0
w<0 时,
L
1
=
(
w
−
2
)
2
−
w
\mathcal{L}_1 = (w-2)^2 - w
L1=(w−2)2−w,导数为
2
(
w
−
2
)
−
1
=
2
w
−
5
2(w-2) - 1 = 2w - 5
2(w−2)−1=2w−5
令导数为0: 2 w − 5 = 0 ⇒ w ∗ = 2.5 2w - 5 = 0 \quad \Rightarrow \quad w^* = 2.5 2w−5=0⇒w∗=2.5(与 w < 0 w<0 w<0 矛盾,舍去) - 当 w = 0 w=0 w=0 时,损失值为 ( 0 − 2 ) 2 + 0 = 4 (0-2)^2 + 0 = 4 (0−2)2+0=4,而 w = 1.5 w=1.5 w=1.5 时损失为 ( 1.5 − 2 ) 2 + 1.5 = 0.25 + 1.5 = 1.75 < 4 (1.5-2)^2 + 1.5 = 0.25 + 1.5 = 1.75 < 4 (1.5−2)2+1.5=0.25+1.5=1.75<4,因此最优解为 w = 1.5 w=1.5 w=1.5。
- 当
w
>
0
w > 0
w>0 时,
L
1
=
(
w
−
2
)
2
+
w
\mathcal{L}_1 = (w-2)^2 + w
L1=(w−2)2+w,导数为
2
(
w
−
2
)
+
1
=
2
w
−
3
2(w-2) + 1 = 2w - 3
2(w−2)+1=2w−3
-
增大α的影响(如 α = 4 \alpha=4 α=4):
- 当 w > 0 w > 0 w>0 时,导数为 2 ( w − 2 ) + 4 = 2 w 2(w-2) + 4 = 2w 2(w−2)+4=2w,令导数为0 ⇒ w ∗ = 0 \Rightarrow w^*=0 ⇒w∗=0
- 此时 w = 0 w=0 w=0 时损失为 ( 0 − 2 ) 2 + 4 × 0 = 4 (0-2)^2 + 4 \times 0 = 4 (0−2)2+4×0=4,而 w > 0 w>0 w>0 时导数在 w = 0 w=0 w=0 处由负变正,说明 w = 0 w=0 w=0 是极小值点。
-
结果:当 α \alpha α 足够大时,L1正则化会使 w w w 直接归零。
-
-
L2正则化的优化过程
-
损失函数:
L 2 = ( w − 2 ) 2 + α w 2 \mathcal{L}_2 = (w - 2)^2 + \alpha w^2 L2=(w−2)2+αw2 -
求导(假设 α = 1 \alpha=1 α=1):
d L 2 d w = 2 ( w − 2 ) + 2 α w = 2 ( w − 2 ) + 2 w = 4 w − 4 \frac{d\mathcal{L}_2}{dw} = 2(w-2) + 2\alpha w = 2(w-2) + 2w = 4w - 4 dwdL2=2(w−2)+2αw=2(w−2)+2w=4w−4
令导数为0: 4 w − 4 = 0 ⇒ w ∗ = 1 4w - 4 = 0 \quad \Rightarrow \quad w^* = 1 4w−4=0⇒w∗=1 -
增大α的影响(如 α = 3 \alpha=3 α=3):
导数为 2 ( w − 2 ) + 6 w = 8 w − 4 2(w-2) + 6w = 8w - 4 2(w−2)+6w=8w−4,令导数为0 ⇒ w ∗ = 0.5 \Rightarrow w^* = 0.5 ⇒w∗=0.5。
-
结果:L2正则化使 w w w 趋近于0,但始终不为0,且α越大, w w w 越小。
-
-
对比总结
正则化类型 | α=1时的最优w | α=4时的最优w | 参数特性 |
---|---|---|---|
无正则化 | 2 | - | 无约束 |
L1 | 1.5 | 0 | 稀疏化(参数可归零) |
L2 | 1 | 0.5 | 平滑化(参数趋近于0但非零) |
直观理解
- L1正则化:像“剪刀”一样直接砍掉不重要的参数(如α=4时w=0),适合特征选择。
- L2正则化:像“瘦身机”一样均匀缩小所有参数的规模(如α越大w越小),适合抗噪声和共线性。
通过数学推导可见:L1的绝对值惩罚在梯度中引入了“突变点”(w=0时导数不连续),促使参数归零;而L2的平方惩罚梯度连续,仅能使参数趋近于0,无法完全归零。
2.4 L1/L2正则化与梯度下降的核心区别及收敛性说明
与梯度下降学习率的本质区别(统一用“汽车行驶”比喻)
-
学习率(全局速度控制)
- 相当于汽车的“油门”,控制所有参数更新的整体步长。
- 影响:油门过大(学习率高)会导致参数更新震荡,难以收敛;油门过小(学习率低)会使收敛速度过慢。
-
L1/L2正则化(单个参数的“行为约束”)
-
L1正则化:给每个参数装“个性化刹车”——对大参数(如w=10)施加强刹车(惩罚10),对小参数(如w=1)施加弱刹车,专门抑制过度活跃的参数。
-
L2正则化:给汽车加装“整体配重”——参数越大(车身越重),转弯时越难偏移方向,迫使所有参数“集体瘦身”,保持模型稳定。
-
核心:不控制整体速度,而是约束参数的“取值偏好”,防止单一参数主导模型决策。
-
为什么正则化后仍能进行梯度下降?
-
正则化的本质是“复合目标优化”
- 原损失函数:目标是找到山谷最低点(拟合数据)。
- 正则化后:目标变为“在拟合数据的同时,参数不能太大”,相当于在山谷中增加一个“向原点吸引的引力”。
- 梯度下降会自动寻找两者的平衡点,就像汽车在行驶中同时考虑“靠近目的地”和“避免超速”,最终停在满足双重目标的位置。
-
数学层面:梯度仍然可计算
- 正则化项(如L1的|w|、L2的w²)都是可导或分段可导的,添加到损失函数后,整体梯度仍然存在。
- 梯度下降只需在原梯度中加入正则化项的梯度(如L2的梯度为2αw),即可继续迭代更新参数,最终收敛到“损失函数+正则项”的最小值。
-
直观比喻:带约束的下山过程
- 原梯度下降:从山顶往山谷最低点走(只看损失函数)。
- 正则化后:下山时被一个“不能走太远”的绳子牵引,最终停在“离谷底近且绳子拉力小”的位置,这个位置就是兼顾拟合能力和参数约束的最优解。
2.5 Dropout(随机失活)
Dropout是神经网络中常用的正则化方法,训练时以一定概率随机"丢弃"神经元及其连接:
数学公式:
y
^
=
{
0
,
概率为
p
w
⋅
x
+
b
,
概率为
1
−
p
\hat{y} = \begin{cases} 0, & \text{概率为 } p \\ w \cdot x + b, & \text{概率为 } 1-p \end{cases}
y^={0,w⋅x+b,概率为 p概率为 1−p
核心特性:
- 等效于训练多个不同网络的集成
- 减少神经元之间的协同适应(co-adaptation)
- 无需修改网络结构,实现简单
核心逻辑:用“随机逃课”打破神经元依赖
- 训练时按概率p随机让神经元“停工”(输出0),迫使存活神经元不能依赖固定伙伴,必须独立学习通用特征。例如:原本A神经元必须和B神经元搭配才能识别猫,Dropout随机让B“缺席”,A就不得不学会单独工作,避免神经网络过度依赖某一个“超强”神经元(当这个超强神经元面对未知的数据预测错误时,会影响整个网络的准确度),使得网络内部的各个神经元都得到充分的训练来提高泛化能力。
- 每次丢弃不同神经元组合,相当于训练多个“精简版网络”,测试时所有神经元开工,等效于综合多个网络的预测,减少过拟合。
- 注意:在测试过程中,随机失活不起作用。测试阶段需要稳定预测,若继续丢弃神经元,同一输入会输出不一致结果(如第一次认猫,第二次认狗)。测试时所有神经元正常工作,相当于学生考试时查看全部笔记,给出确定答案。
效果演示:
绘图代码:
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
# 设置中文字体和负号显示
plt.rcParams["font.family"] = ["SimHei"] # 设置Matplotlib使用黑体字体,确保中文正常显示
plt.rcParams["axes.unicode_minus"] = False # 允许坐标轴正确显示负号
def draw_dropout_comparison_with_full():
"""绘制完整神经网络与Dropout操作的对比图,直观展示Dropout随机丢弃神经元的过程"""
# 定义网络节点结构
input_nodes = ['x1', 'x2'] # 输入层节点(2个特征)
hidden_nodes = ['h1', 'h2', 'h3', 'h4'] # 隐藏层节点(4个神经元)
output_nodes = ['y1', 'y2'] # 输出层节点(2分类任务)
# 创建有向图对象
G = nx.DiGraph()
# 添加节点到图中,并标记所属层
G.add_nodes_from(input_nodes, layer='input') # 输入层节点
G.add_nodes_from(hidden_nodes, layer='hidden') # 隐藏层节点
G.add_nodes_from(output_nodes, layer='output') # 输出层节点
# 添加全连接边(输入层→隐藏层,隐藏层→输出层)
for i in input_nodes:
for h in hidden_nodes:
G.add_edge(i, h) # 输入层到隐藏层的全连接
for h in hidden_nodes:
for o in output_nodes:
G.add_edge(h, o) # 隐藏层到输出层的全连接
# 设置节点布局(使用自定义坐标位置)
pos = {}
pos.update((n, (0, i)) for i, n in enumerate(input_nodes)) # 输入层节点放在x=0,y轴排列
pos.update((n, (1, i + 0.5)) for i, n in enumerate(hidden_nodes)) # 隐藏层放在x=1,y轴错位排列
pos.update((n, (2, i)) for i, n in enumerate(output_nodes)) # 输出层放在x=2,y轴排列
# 创建画布(1行3列子图)
fig, axes = plt.subplots(1, 3, figsize=(18, 6), dpi=100)
# 第一张图:绘制完整网络结构(无Dropout)
ax = axes[0]
plt.sca(ax) # 设置当前绘图区域为第一个子图
# 绘制节点(不同层使用不同颜色)
nx.draw_networkx_nodes(G, pos, nodelist=input_nodes, node_color='lightblue', node_size=600, ax=ax) # 输入层浅蓝色
nx.draw_networkx_nodes(G, pos, nodelist=hidden_nodes, node_color='lightgreen', node_size=600, ax=ax) # 隐藏层浅绿色
nx.draw_networkx_nodes(G, pos, nodelist=output_nodes, node_color='salmon', node_size=600, ax=ax) # 输出层浅红色
nx.draw_networkx_edges(G, pos, ax=ax, arrows=True) # 绘制带箭头的边
nx.draw_networkx_labels(G, pos, ax=ax) # 添加节点标签
ax.set_title("完整网络结构(无Dropout)", fontsize=12) # 设置标题
ax.axis('off') # 关闭坐标轴显示
# 第二、第三张图:绘制两次不同的Dropout效果
for ax_idx, ax in enumerate(axes[1:]):
plt.sca(ax) # 设置当前绘图区域为第ax_idx+1个子图
# 随机选择50%的隐藏层节点进行Dropout(不重复选择)
dropped_nodes = np.random.choice(hidden_nodes, size=int(len(hidden_nodes) * 0.5), replace=False)
active_nodes = [n for n in hidden_nodes if n not in dropped_nodes] # 活跃的隐藏层节点
# 绘制节点(活跃节点绿色,丢弃节点灰色)
nx.draw_networkx_nodes(G, pos, nodelist=input_nodes, node_color='lightblue', node_size=600, ax=ax) # 输入层不变
nx.draw_networkx_nodes(G, pos, nodelist=active_nodes, node_color='lightgreen', node_size=600, ax=ax) # 活跃隐藏层绿色
nx.draw_networkx_nodes(G, pos, nodelist=dropped_nodes, node_color='gray', node_size=600, ax=ax) # 丢弃隐藏层灰色
nx.draw_networkx_nodes(G, pos, nodelist=output_nodes, node_color='salmon', node_size=600, ax=ax) # 输出层不变
# 仅绘制活跃神经元的连接边
active_edges_input = [(i, h) for i in input_nodes for h in active_nodes] # 输入层到活跃隐藏层的边
active_edges_output = [(h, o) for h in active_nodes for o in output_nodes] # 活跃隐藏层到输出层的边
nx.draw_networkx_edges(G, pos, edgelist=active_edges_input + active_edges_output, arrows=True, ax=ax) # 绘制活跃边
nx.draw_networkx_labels(G, pos, ax=ax) # 添加节点标签
ax.set_title(f"Dropout 网络 - 第 {ax_idx + 1} 次训练(p=0.5)", fontsize=12) # 设置标题
ax.axis('off') # 关闭坐标轴显示
plt.tight_layout() # 自动调整子图布局,避免重叠
plt.show() # 显示图表
# 调用函数绘制对比图
draw_dropout_comparison_with_full()
2.6 批量归一化(Batch Normalization)
批量归一化通过对每层输入进行标准化处理,稳定网络训练过程:
数学公式:
x
^
=
x
−
μ
B
σ
B
2
+
ϵ
⋅
γ
+
β
\hat{x} = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \cdot \gamma + \beta
x^=σB2+ϵx−μB⋅γ+β
其中, μ B \mu_B μB 和 σ B 2 \sigma_B^2 σB2 是批次数据的均值和方差, γ \gamma γ 和 β \beta β 是可学习参数。
核心特性:
- 缓解"内部协变量偏移"问题
- 允许使用更大的学习率,加速收敛
- 具有一定的正则化效果
2.7 早停法(Early Stopping)
核心思想:在模型训练过程中,持续监控验证集的性能,当验证误差不再下降时提前终止训练,避免模型对训练数据过度拟合。
防止过拟合的逻辑:
- 模型在训练初期会同时学习数据的真实模式和噪声,但随着训练深入,可能逐渐拟合噪声(表现为训练误差持续下降,验证误差开始上升)。
- 早停法通过“主动打断”训练过程,强制模型在“学习真实模式但未记住噪声”的阶段停止,类似“适可而止”的学习策略。
核心特性:
- 无需修改模型结构,仅需监控验证集指标(如损失、准确率)。
- 计算成本低,是一种“廉价”的正则化手段。
- 需合理设置验证集和停止阈值,避免提前终止导致欠拟合。
2.8 数据增强(Data Augmentation)
核心思想:通过对原始数据进行一系列可逆变换(如旋转、缩放、翻转等),生成更多“虚拟”训练样本,扩大数据集的多样性。
防止过拟合的逻辑:
- 过拟合的本质是模型“记住”了训练数据的特定细节(如图像中的某个像素模式),而数据增强通过创造不同视角的样本,迫使模型学习更通用的特征(如物体的形状而非特定光照)。
- 例如:对图像数据进行随机旋转,能让模型学会“猫”的特征与角度无关,而非记住某张图片中猫的朝向。
核心特性:
- 物理意义明确,尤其适用于图像、音频等结构化数据。
- 无需额外计算开销(变换在内存中完成),是性价比最高的正则化方法之一。
- 变换方式需根据数据类型设计(如图像旋转、文本同义词替换),避免生成“无效样本”。
三、线性回归中的过拟合演示
线性回归(Linear regression)算法详解:网页连接
3.1 代码概述
代码在基础线性回归的基础上,通过引入多项式特征拟合高次曲线,直观演示机器学习中的过拟合现象。与基础线性回归相比,本项目新增了以下核心能力:
- 过拟合对比演示:通过线性模型与9次多项式模型的拟合效果对比,展示高复杂度模型对训练数据的过度拟合;
- 量化评估指标:使用均方误差(MSE)定量分析不同模型的拟合效果;
- 特征工程实践:通过
PolynomialFeatures
生成高次项特征,模拟真实场景中特征复杂度对模型的影响。
3.2 与基础线性回归的核心差异对比
- 功能定位差异
对比维度 | 基础线性回归(原始代码) | 过拟合演示项目(当前代码) |
---|---|---|
核心目标 | 演示线性模型对数据的基本拟合能力 | 对比不同模型复杂度下的过拟合现象,强调泛化能力重要性 |
模型数量 | 仅训练线性模型 | 同时训练线性模型和9次多项式模型,形成对比 |
特征处理 | 直接使用原始特征 | 生成x, x², …, x⁹的多项式特征,增加特征复杂度 |
评估方式 | 仅可视化拟合效果 | 结合可视化与MSE指标,定量+定性评估模型性能 |
3.3代码运行效果:训练损失低≠测试损失低
-
可视化结果:
- 线性模型(左图):绿色拟合线平滑逼近蓝色真实线,虽未完全穿过所有红色数据点,但整体趋势与真实关系 y = 2.5 x + 10 y=2.5x+10 y=2.5x+10 一致。
- 9次多项式(右图):绿色拟合线“精准”穿过每个数据点,但曲线剧烈波动(如在x=0和x=50处明显偏离真实线)。
-
MSE指标对比:
- 线性模型:MSE≈614(拟合误差适中);
- 9次多项式:MSE≈379(拟合误差极低)。
-
核心矛盾:
9次多项式在训练数据上的拟合误差(MSE)远低于线性模型,但这并不代表它在新数据上的表现更好——这正是“训练损失低≠测试损失低”的典型案例。
下面是通过添加高次项特征,演示线性回归模型的过拟合现象的代码:
import numpy as np # 导入NumPy库,用于数值计算(生成数据、矩阵运算等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于数据可视化(绘制拟合曲线、散点图等)
from sklearn.linear_model import LinearRegression # 导入线性回归模型,用于拟合数据
from sklearn.preprocessing import PolynomialFeatures # 导入多项式特征生成器,用于生成高次项特征
from sklearn.metrics import mean_squared_error # 导入均方误差计算函数,用于评估模型性能
# 设置中文字体和负号显示(确保可视化时中文和负号正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 设置matplotlib使用黑体字体
plt.rcParams["axes.unicode_minus"] = False # 确保坐标轴负号正确显示
# 1. 生成训练数据(25个带噪声的样本点)
np.random.seed(44) # 固定随机种子,确保结果可复现
x_train = np.linspace(0, 50, 25) # 在0到50之间生成25个均匀分布的特征值(x轴)
k_true, b_true = 2.5, 10 # 真实线性关系的参数:斜率k=2.5,截距b=10(y=2.5x+10)
# 生成带高斯噪声的目标值(噪声均值0,标准差25),模拟真实场景中的观测误差
y_train = k_true * x_train + b_true + np.random.normal(0, 25, size=x_train.shape)
# 3. 训练线性回归模型(用于对比,演示正常拟合)
model_linear = LinearRegression() # 创建线性回归模型实例
# 用训练数据拟合模型(输入需为二维数组,reshape(-1,1)将一维x转为二维特征矩阵)
model_linear.fit(x_train.reshape(-1, 1), y_train)
# 在训练集上预测,用于计算训练损失
y_pred_linear_train = model_linear.predict(x_train.reshape(-1, 1))
# 4. 训练9次多项式模型(演示过拟合现象)
poly = PolynomialFeatures(degree=9, include_bias=False) # 创建9次多项式特征生成器(不包含常数项)
# 将训练集特征转换为9次多项式特征(例如x, x², x³,...,x⁹)
x_train_poly = poly.fit_transform(x_train.reshape(-1, 1))
model_poly = LinearRegression() # 创建线性回归模型(用于拟合多项式特征)
model_poly.fit(x_train_poly, y_train) # 用多项式特征训练模型
# 在训练集上预测多项式模型输出
y_pred_poly_train = model_poly.predict(x_train_poly)
# 5. 计算均方误差(MSE):仅评估训练集性能
mse_linear_train = mean_squared_error(y_train, y_pred_linear_train) # 线性模型的训练MSE
mse_poly_train = mean_squared_error(y_train, y_pred_poly_train) # 多项式模型的训练MSE
# 6. 可视化对比两种模型在训练集上的表现
fig, axes = plt.subplots(1, 2, figsize=(18, 6)) # 创建1行2列的子图布局(便于对比)
# 左侧子图:线性模型的拟合效果
axes[0].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
# 绘制真实线性关系(蓝色实线)
axes[0].plot(x_train, k_true * x_train + b_true, color='blue',
label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制线性模型在训练集上的拟合结果(绿色虚线)
axes[0].plot(x_train, y_pred_linear_train, color='green', linestyle='--',
label=r'线性拟合 $\hat{y} = %.2fx + %.2f$' % (model_linear.coef_[0], model_linear.intercept_))
# 设置子图标题(仅显示训练MSE)
axes[0].set_title(f'线性模型 - 正常拟合\n训练MSE = {mse_linear_train:.2f}')
axes[0].legend() # 显示图例
axes[0].grid(True, alpha=0.5) # 添加半透明网格线
# 右侧子图:9次多项式模型的拟合效果
axes[1].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
# 绘制真实线性关系(蓝色实线)
axes[1].plot(x_train, k_true * x_train + b_true, color='blue',
label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制9次多项式模型在训练集上的拟合结果(绿色虚线)
axes[1].plot(x_train, y_pred_poly_train, color='green', linestyle='--',
label=r'9次多项式拟合')
# 设置子图标题(仅显示训练MSE)
axes[1].set_title(f'9次多项式模型 - 过拟合\n训练MSE = {mse_poly_train:.2f}')
axes[1].legend() # 显示图例
axes[1].grid(True, alpha=0.5) # 添加半透明网格线
plt.tight_layout() # 自动调整子图布局,避免重叠
plt.show() # 显示图表
# 输出MSE对比表(控制台打印,量化展示过拟合现象)
print("模型类型\t\t| MSE")
print("-------------------|---------")
print(f"线性模型(正常拟合) | {mse_linear_train:.2f}")
print(f"9次多项式(过拟合) | {mse_poly_train:.2f}")
3.4 为什么训练损失低不代表测试损失低?
-
过拟合:模型记住了“噪声”而非“规律”
-
数据本质:代码生成的y包含两部分
y = 2.5 x + 10 ⏟ 真实规律 + 高斯噪声 ⏟ 随机波动 y = \underbrace{2.5x+10}_{\text{真实规律}} + \underbrace{\text{高斯噪声}}_{\text{随机波动}} y=真实规律 2.5x+10+随机波动 高斯噪声
线性模型拟合真实规律,而9次多项式不仅拟合规律,还“记住”了噪声的具体模式(如某个数据点的随机偏移)。 -
比喻说明:
- 训练集:相当于学生的“课后作业”,噪声是作业中的“特殊题型”(如某道题的陷阱)。
- 9次多项式:像学生把每道作业题的答案都背下来,但没理解解题方法,遇到新题(测试集)就会出错。
- 线性模型:像学生掌握了核心解题思路,虽作业正确率略低,但考试(测试集)发挥稳定。
-
-
模型复杂度:能力越强,越易“滥用能力”
-
线性模型:假设空间有限(仅能拟合直线),被迫忽略噪声,抓住本质规律。
-
9次多项式:假设空间极大(可拟合任意曲线),训练时会利用高次项调整曲线形状,精确匹配每个数据点(包括噪声)。
-
数学本质:
训练损失(MSE)衡量模型对训练数据的拟合程度,但未考虑模型的泛化能力。9次多项式的高次项参数(如 w 9 x 9 w_9x^9 w9x9)可能专门用于拟合训练数据中的噪声,这些参数在新数据上会导致预测偏差。
-
-
泛化能力:训练损失与测试损失的鸿沟
- 泛化能力:模型对未知数据的预测能力,由模型复杂度和数据分布共同决定。
- 代码中的体现:
9次多项式的训练MSE更低,但它学习到的“规律”包含大量噪声(如x=10处的某个数据点因噪声偏高,模型用高次项让曲线在x=10处上凸),而新数据的噪声是随机的,导致测试时误差骤增。
3.5 直观验证:假设新增测试数据
-
数据生成与模型训练逻辑
- 数据生成:
训练集(25个点)和测试集(50个点)均服从真实关系 y = 2.5 x + 10 y=2.5x+10 y=2.5x+10 并叠加高斯噪声(标准差25)。测试集样本更多且独立于训练集,模拟真实场景中的未知数据。 - 模型差异:
- 线性模型:仅拟合一次项 y = w x + b y=wx+b y=wx+b,强制忽略数据中的随机波动;
- 9次多项式:通过特征工程生成 x , x 2 , … , x 9 x, x^2, \dots, x^9 x,x2,…,x9,理论上可拟合任意曲线,包括噪声模式。
- 数据生成:
-
MSE指标的矛盾性
模型类型 | 训练MSE | 测试MSE | 测试MSE/训练MSE |
---|---|---|---|
线性模型 | 614.19 | 623.03 | 1.01 |
9次多项式模型 | 379.83 | 1313.92 | 3.46 |
-
关键发现:
- 线性模型:测试MSE仅比训练MSE高1%,说明泛化能力稳定;
- 9次多项式:测试MSE是训练MSE的3.46倍,暴露严重过拟合——模型将训练数据中的噪声误判为“规律”,在新数据上失效。
-
可视化中的过拟合证据
- 线性模型(左图):
绿色拟合线平滑贴近蓝色真实线,在红色训练点和橙色测试点间保持一致趋势,说明模型学习到了 y = 2.5 x + 10 y=2.5x+10 y=2.5x+10 的普适规律。 - 9次多项式(右图):
绿色拟合线精准穿过每个红色训练点,但在橙色测试点处剧烈波动(如x=0和x=50处明显偏离真实线),印证了“过度拟合训练噪声”的结论。
- 线性模型(左图):
3.6 核心结论:警惕“训练损失陷阱”
-
过拟合的本质是“模式误判”
模型将训练数据中的随机噪声(如某个数据点的异常偏移)当作“有效模式”学习,而噪声在测试集上是变化的。9次多项式的高次项(如 x 9 x^9 x9)实际上是对训练噪声的“记忆”,而非真实规律的提取。 -
模型复杂度≠泛化能力
- 线性模型通过限制复杂度(仅拟合线性关系),被迫忽略噪声,反而保留了对真实规律的泛化能力;
- 高复杂度模型(如9次多项式)虽能“记住”训练数据的所有细节,但丧失了对新数据的预测能力,印证了“奥卡姆剃刀原理”——越简单的模型越可能泛化。
下面是继续利用 y = 2.5 x + 10 y=2.5x+10 y=2.5x+10,噪声同样保持25的情况下,生成50个测试点,评估模型的泛化能力的代码:
import numpy as np # 导入NumPy库,用于数值计算(生成数据、矩阵运算等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于数据可视化(绘制拟合曲线、散点图等)
from sklearn.linear_model import LinearRegression # 导入线性回归模型,用于拟合数据
from sklearn.preprocessing import PolynomialFeatures # 导入多项式特征生成器,用于生成高次项特征
from sklearn.metrics import mean_squared_error # 导入均方误差计算函数,用于评估模型性能
# 设置中文字体和负号显示(确保可视化时中文和负号正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 设置matplotlib使用黑体字体
plt.rcParams["axes.unicode_minus"] = False # 确保坐标轴负号正确显示
# 1. 生成训练数据(25个带噪声的样本点)
np.random.seed(44) # 固定随机种子,确保结果可复现
x_train = np.linspace(0, 50, 25) # 在0到50之间生成25个均匀分布的特征值(x轴)
k_true, b_true = 2.5, 10 # 真实线性关系的参数:斜率k=2.5,截距b=10(y=2.5x+10)
# 生成带高斯噪声的目标值(噪声均值0,标准差25),模拟真实场景中的观测误差
y_train = k_true * x_train + b_true + np.random.normal(0, 25, size=x_train.shape)
# 2. 生成测试数据(50个带噪声的样本点,用于评估模型泛化能力)
x_test = np.linspace(0, 50, 50) # 生成50个测试样本(比训练集更多,覆盖更广泛)
y_test = k_true * x_test + b_true + np.random.normal(0, 25, size=x_test.shape) # 测试数据与训练数据同分布
# 3. 训练线性回归模型(用于对比,演示正常拟合)
model_linear = LinearRegression() # 创建线性回归模型实例
# 用训练数据拟合模型(输入需为二维数组,reshape(-1,1)将一维x转为二维特征矩阵)
model_linear.fit(x_train.reshape(-1, 1), y_train)
# 在训练集上预测,用于计算训练损失
y_pred_linear_train = model_linear.predict(x_train.reshape(-1, 1))
# 在测试集上预测,用于评估模型泛化能力
y_pred_linear_test = model_linear.predict(x_test.reshape(-1, 1))
# 4. 训练9次多项式模型(演示过拟合现象)
poly = PolynomialFeatures(degree=9, include_bias=False) # 创建9次多项式特征生成器(不包含常数项)
# 将训练集特征转换为9次多项式特征(例如x, x², x³,...,x⁹)
x_train_poly = poly.fit_transform(x_train.reshape(-1, 1))
# 将测试集特征转换为9次多项式特征(使用训练集拟合的参数,保证转换一致性)
x_test_poly = poly.transform(x_test.reshape(-1, 1))
model_poly = LinearRegression() # 创建线性回归模型(用于拟合多项式特征)
model_poly.fit(x_train_poly, y_train) # 用多项式特征训练模型
# 在训练集上预测多项式模型输出
y_pred_poly_train = model_poly.predict(x_train_poly)
# 在测试集上预测多项式模型输出
y_pred_poly_test = model_poly.predict(x_test_poly)
# 5. 计算均方误差(MSE):分别评估训练集和测试集性能
mse_linear_train = mean_squared_error(y_train, y_pred_linear_train) # 线性模型的训练MSE
mse_linear_test = mean_squared_error(y_test, y_pred_linear_test) # 线性模型的测试MSE
mse_poly_train = mean_squared_error(y_train, y_pred_poly_train) # 多项式模型的训练MSE
mse_poly_test = mean_squared_error(y_test, y_pred_poly_test) # 多项式模型的测试MSE
# 6. 可视化对比两种模型在训练集和测试集上的表现
fig, axes = plt.subplots(1, 2, figsize=(18, 6)) # 创建1行2列的子图布局(便于对比)
# 左侧子图:线性模型的拟合与泛化效果
axes[0].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
axes[0].scatter(x_test, y_test, color='orange', alpha=0.7, label='测试数据点') # 绘制测试数据(橙色)
# 绘制真实线性关系(蓝色实线)
axes[0].plot(x_test, k_true * x_test + b_true, color='blue', label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制线性模型在训练集上的拟合结果(绿色虚线)
axes[0].plot(x_train, y_pred_linear_train, color='green', linestyle='--',
label=r'线性拟合 $\hat{y} = %.2fx + %.2f$' % (model_linear.coef_[0], model_linear.intercept_))
# 设置子图标题(显示训练MSE和测试MSE)
axes[0].set_title(f'线性模型 - 拟合与泛化\n训练MSE = {mse_linear_train:.2f}\n测试MSE = {mse_linear_test:.2f}')
axes[0].legend() # 显示图例
axes[0].grid(True, alpha=0.5) # 添加半透明网格线
# 右侧子图:9次多项式模型的拟合与泛化效果
axes[1].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
axes[1].scatter(x_test, y_test, color='orange', alpha=0.7, label='测试数据点') # 绘制测试数据(橙色)
# 绘制真实线性关系(蓝色实线)
axes[1].plot(x_test, k_true * x_test + b_true, color='blue', label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制9次多项式模型在训练集上的拟合结果(绿色虚线)
axes[1].plot(x_train, y_pred_poly_train, color='green', linestyle='--',
label=r'9次多项式拟合')
# 设置子图标题(显示训练MSE和测试MSE)
axes[1].set_title(f'9次多项式模型 - 拟合与泛化\n训练MSE = {mse_poly_train:.2f}\n测试MSE = {mse_poly_test:.2f}')
axes[1].legend() # 显示图例
axes[1].grid(True, alpha=0.5) # 添加半透明网格线
plt.tight_layout() # 自动调整子图布局,避免重叠
plt.show() # 显示图表
# 输出MSE对比表(控制台打印,量化展示过拟合现象)
print("模型类型\t\t| 训练MSE | 测试MSE")
print("--------------------------------------")
print(f"线性模型(正常拟合) | {mse_linear_train:.2f} | {mse_linear_test:.2f}")
print(f"9次多项式(过拟合) | {mse_poly_train:.2f} | {mse_poly_test:.2f}")
四、L1正则化缓解过拟合的演示
4.1 L1正则化(Lasso)缓解过拟合的代码与效果分析
- 代码结构解析:L1正则化的实现逻辑
代码通过以下核心模块实现过拟合缓解与对比:- 数据生成模块:
生成25个训练样本和50个测试样本,均服从 y = 2.5 x + 10 + y=2.5x+10+ y=2.5x+10+高斯噪声(标准差25),确保测试集独立于训练集,模拟真实泛化场景。 - 特征工程模块:
使用PolynomialFeatures(degree=9)
将原始特征x转换为9次多项式特征( x , x 2 , … , x 9 x, x^2, \dots, x^9 x,x2,…,x9),人为增加特征复杂度,诱导过拟合。 - 模型对比模块:
- 普通线性回归:直接拟合9次多项式特征,无正则化约束;
- Lasso回归:通过
Lasso(alpha=0.1)
添加L1正则化,惩罚参数绝对值之和,强制参数稀疏化。
- 评估模块:
同时计算训练集与测试集的MSE,通过mean_squared_error
量化过拟合程度,并通过双图可视化对比拟合曲线。
- 数据生成模块:
4.2 运行效果:L1正则化对过拟合的改善
模型类型 | 训练MSE | 测试MSE | 测试MSE/训练MSE | 拟合曲线特征 |
---|---|---|---|---|
9次多项式(过拟合) | 379.83 | 1313.92 | 3.46 | 剧烈波动,精准穿过训练点 |
Lasso(L1正则化) | 587.78 | 573.01 | 0.97 | 平滑逼近真实线,泛化稳定 |
- 关键发现:
- 过拟合模型的缺陷:9次多项式的测试MSE是训练MSE的3.46倍,拟合曲线在测试集上严重偏离真实关系,印证了“训练损失低≠泛化能力好”。
- L1正则化的优势:
- 测试MSE(573.01)反低于训练MSE(587.78),说明模型泛化能力强;
- 拟合曲线平滑贴近真实线( y = 2.5 x + 10 y=2.5x+10 y=2.5x+10),避免了对训练噪声的过度拟合。
4.3 L1正则化的核心机制:参数稀疏化抑制噪声拟合
- 数学层面:
Lasso的损失函数为:
损失 = MSE + 0.1 ∑ ∣ w i ∣ \text{损失} = \text{MSE} + 0.1\sum|w_i| 损失=MSE+0.1∑∣wi∣
当alpha=0.1时,模型在拟合数据的同时,强制不重要的高次项参数(如 x 9 x^9 x9的权重)归零,实现特征筛选。例如,9次多项式中的 x 5 x^5 x5~ x 9 x^9 x9参数可能被置零,仅保留低次项拟合真实线性关系。 - 可视化印证:
Lasso模型的拟合曲线(右图)不再“缠绕”训练点,而是趋近于线性关系,说明L1正则化通过减少有效特征数量(稀疏化),迫使模型聚焦于真实规律( y = 2.5 x + 10 y=2.5x+10 y=2.5x+10),而非噪声模式。
4.4 正则化强度(alpha)的影响分析
- 当前alpha=0.1为经验值,若调整alpha会产生以下影响:
- alpha过小(如0.01):惩罚力度不足,参数稀疏化效果弱,可能仍存在过拟合(测试MSE仍高于训练MSE);
- alpha过大(如1.0):惩罚力度过强,可能导致所有参数归零,模型退化为常数函数(欠拟合),训练/测试MSE均升高。
- 最佳实践:
通过交叉验证搜索最优alpha,例如使用sklearn.model_selection.GridSearchCV
自动寻找使测试MSE最小的alpha值。
4.5 结论:正则化是平衡拟合与泛化的桥梁
- 过拟合的本质:模型复杂度与数据量不匹配,导致“记忆噪声”而非“学习规律”。
- L1正则化的价值:通过参数稀疏化,在不显著增加训练损失的前提下,大幅降低测试损失,实现“用简单模型拟合复杂数据”的目标。
- 工程启示:
当模型在训练集上表现优异但测试集表现差时,优先考虑添加正则化(如L1/L2),而非盲目增加模型复杂度——简单模型+正则化往往比复杂模型更具实用价值。
下面使用L1正则化(Lasso回归)演示正则化对过拟合的缓解效果的代码:
import numpy as np # 导入NumPy库,用于数值计算(生成数据、矩阵运算等)
import matplotlib.pyplot as plt # 导入Matplotlib库,用于数据可视化(绘制拟合曲线、散点图等)
from sklearn.linear_model import LinearRegression, Lasso # 导入线性回归与Lasso回归(L1正则化)
from sklearn.preprocessing import PolynomialFeatures # 导入多项式特征生成器,用于生成高次项特征
from sklearn.metrics import mean_squared_error # 导入均方误差计算函数,用于评估模型性能
# 设置中文字体和负号显示(确保可视化时中文和负号正常显示)
plt.rcParams["font.family"] = ["SimHei"] # 设置matplotlib使用黑体字体
plt.rcParams["axes.unicode_minus"] = False # 确保坐标轴负号正确显示
# 1. 生成训练数据(25个带噪声的样本点)
np.random.seed(44) # 固定随机种子,确保结果可复现
x_train = np.linspace(0, 50, 25) # 在0到50之间生成25个均匀分布的特征值(x轴)
k_true, b_true = 2.5, 10 # 真实线性关系的参数:斜率k=2.5,截距b=10(y=2.5x+10)
# 生成带高斯噪声的目标值(噪声均值0,标准差25),模拟真实场景中的观测误差
y_train = k_true * x_train + b_true + np.random.normal(0, 25, size=x_train.shape)
# 2. 生成测试数据(50个带噪声的样本点,用于评估模型泛化能力)
x_test = np.linspace(0, 50, 50) # 生成50个测试样本(比训练集更多,覆盖更广泛)
y_test = k_true * x_test + b_true + np.random.normal(0, 25, size=x_test.shape) # 测试数据与训练数据同分布
# 3. 特征工程:生成9次多项式特征(核心过拟合演示点)
poly = PolynomialFeatures(degree=9, include_bias=False) # 创建9次多项式特征生成器(不包含常数项)
# 将训练集特征转换为9次多项式特征(例如x, x², x³,...,x⁹)
x_train_poly = poly.fit_transform(x_train.reshape(-1, 1))
# 将测试集特征转换为9次多项式特征(使用训练集拟合的参数,保证转换一致性)
x_test_poly = poly.transform(x_test.reshape(-1, 1))
# 模型1:普通线性回归(无正则化,演示过拟合)
model_poly_linear = LinearRegression() # 创建普通线性回归模型
model_poly_linear.fit(x_train_poly, y_train) # 用9次多项式特征训练模型
y_pred_poly_train = model_poly_linear.predict(x_train_poly) # 训练集预测结果
y_pred_poly_test = model_poly_linear.predict(x_test_poly) # 测试集预测结果
# 模型2:Lasso回归(L1正则化,缓解过拟合)
model_lasso = Lasso(alpha=0.1) # 创建Lasso回归模型(alpha控制正则化强度,值越大惩罚越重)
model_lasso.fit(x_train_poly, y_train) # 用L1正则化训练9次多项式特征
y_pred_lasso_train = model_lasso.predict(x_train_poly) # Lasso模型训练集预测结果
y_pred_lasso_test = model_lasso.predict(x_test_poly) # Lasso模型测试集预测结果
# 4. 计算MSE(分别评估训练集和测试集性能,量化过拟合程度)
mse_poly_train = mean_squared_error(y_train, y_pred_poly_train) # 普通多项式模型的训练MSE
mse_poly_test = mean_squared_error(y_test, y_pred_poly_test) # 普通多项式模型的测试MSE
mse_lasso_train = mean_squared_error(y_train, y_pred_lasso_train) # Lasso模型的训练MSE
mse_lasso_test = mean_squared_error(y_test, y_pred_lasso_test) # Lasso模型的测试MSE
# 5. 可视化对比两种多项式模型在训练集和测试集上的表现
fig, axes = plt.subplots(1, 2, figsize=(18, 6)) # 创建1行2列的子图布局(便于对比)
# 左侧子图:9次多项式模型 - 过拟合
axes[0].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
axes[0].scatter(x_test, y_test, color='orange', alpha=0.7, label='测试数据点') # 绘制测试数据(橙色)
# 绘制真实线性关系(蓝色实线)
axes[0].plot(x_test, k_true * x_test + b_true, color='blue',
label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制9次多项式模型的拟合结果(绿色虚线)
axes[0].plot(x_train, y_pred_poly_train, color='green', linestyle='--',
label=r'9次多项式拟合')
# 设置子图标题(显示训练MSE和测试MSE,直观展示过拟合)
axes[0].set_title(f'9次多项式模型 - 过拟合\n训练MSE = {mse_poly_train:.2f}\n测试MSE = {mse_poly_test:.2f}')
axes[0].legend() # 显示图例
axes[0].grid(True, alpha=0.5) # 添加半透明网格线
# 右侧子图:Lasso回归模型 - 缓解过拟合
axes[1].scatter(x_train, y_train, color='red', alpha=0.7, label='训练数据点') # 绘制训练数据(红色)
axes[1].scatter(x_test, y_test, color='orange', alpha=0.7, label='测试数据点') # 绘制测试数据(橙色)
# 绘制真实线性关系(蓝色实线)
axes[1].plot(x_test, k_true * x_test + b_true, color='blue',
label=r'真实关系 $y = %.1fx + %.1f$' % (k_true, b_true))
# 绘制Lasso回归模型的拟合结果(绿色点划线)
axes[1].plot(x_train, y_pred_lasso_train, color='green', linestyle='--',
label=r'L1正则化拟合')
# 设置子图标题(显示Lasso模型的训练MSE和测试MSE,对比过拟合改善效果)
axes[1].set_title(f'L1正则化模型 - 缓解过拟合\n训练MSE = {mse_lasso_train:.2f}\n测试MSE = {mse_lasso_test:.2f}')
axes[1].legend() # 显示图例
axes[1].grid(True, alpha=0.5) # 添加半透明网格线
plt.tight_layout() # 自动调整子图布局,避免重叠
plt.show() # 显示图表
# 输出MSE对比表(控制台打印,量化展示正则化对过拟合的改善效果)
print("模型类型\t\t| 训练MSE | 测试MSE")
print("--------------------------------------")
print(f"9次多项式(过拟合) | {mse_poly_train:.2f} | {mse_poly_test:.2f}")
print(f"L1正则化(Lasso) | {mse_lasso_train:.2f} | {mse_lasso_test:.2f}")
五、正则化的本质与调参建议
5.1 正则化的本质
正则化通过以下机制缓解过拟合:
- 参数惩罚:L1/L2正则化通过限制参数大小,防止模型学习到训练数据中的噪声模式
- 模型平均:Dropout通过随机丢弃神经元,等效于训练多个模型的集成
- 数据规范化:Batch Normalization通过标准化输入,稳定网络训练过程
- 训练过程干预:早停法通过提前终止训练,避免模型过度拟合噪声;
- 数据多样性扩充:数据增强通过生成虚拟样本,迫使模型学习更通用特征。
5.2 常见正则化方法对比(L1/L2/Dropout/BN/早停法/数据增强)
正则化方法 | 基本原理 | 数学公式 | 核心特性 | 参数稀疏性 | 计算复杂度 | 典型适用场景 |
---|---|---|---|---|---|---|
L1正则化(Lasso) | 在损失函数中添加参数绝对值之和作为惩罚项 | L = L + α ∑ a b s ( w i ) L = \mathcal{L} + \alpha\sum{abs}(w_i) L=L+α∑abs(wi) | 1. 迫使不重要参数归零,实现特征选择 2. 对异常值鲁棒 3. 优化可能产生不连续解 | 参数稀疏 | 低(可解析求解) | 1. 特征选择(如基因表达数据) 2. 高维数据降维 3. 希望模型具备可解释性的场景 |
L2正则化(Ridge) | 在损失函数中添加参数平方和作为惩罚项 | L = L + α ∑ w i 2 L = \mathcal{L} + \alpha\sum w_i^2 L=L+α∑wi2 | 1. 使参数趋近于0但非零,平滑模型 2. 缓解特征共线性 3. 优化过程连续可导 | 参数平滑 | 低(可解析求解) | 1. 回归问题抗噪声 2. 特征共线性场景(如房价预测) 3. 神经网络权重衰减 |
Dropout(随机失活) | 训练时以概率p随机丢弃神经元及其连接 | y ^ = { 0 , p w ⋅ x + b , 1 − p \hat{y} = \begin{cases} 0, & p \\ w\cdot x+b, & 1-p \end{cases} y^={0,w⋅x+b,p1−p | 1. 等效于集成多个子网络 2. 减少神经元协同适应 3. 测试时需关闭,输出缩放 | 无参数修改 | 中(动态计算) | 1. 深度神经网络(CNN/RNN) 2. 防止神经元过度依赖 3. 计算资源有限时的集成替代方案 |
批量归一化(BatchNorm) | 对每层输入标准化,添加可学习缩放平移参数 | x ^ = x − μ B σ B 2 + ϵ ⋅ γ + β \hat{x} = \frac{x-\mu_B}{\sqrt{\sigma_B^2+\epsilon}}\cdot\gamma+\beta x^=σB2+ϵx−μB⋅γ+β | 1. 缓解内部协变量偏移 2. 允许更大学习率,加速收敛 3. 隐含正则化效果 | 无参数修改 | 中(需计算均值方差) | 1. 深度神经网络训练稳定化 2. 解决梯度消失/爆炸 3. 与Dropout结合使用效果更佳 |
早停法(Early Stopping) | 监控验证集性能,当误差不再下降时提前终止训练 | - | 1. 计算成本低,无需修改模型 2. 需合理设置验证集 3. 适用于迭代训练场景 | 无参数修改 | 低(仅监控指标) | 1. 迭代优化算法(如SGD) 2. 计算资源有限时的过拟合预防 3. 与其他正则化结合使用 |
数据增强(Data Augmentation) | 对原始数据进行变换(旋转/缩放等)生成新样本 | - | 1. 扩大数据集多样性 2. 物理意义明确 3. 无额外计算开销(内存变换) | 无参数修改 | 低(数据变换) | 1. 图像/音频等结构化数据 2. 样本量有限时 3. 需保持数据语义的场景(如医学图像旋转不改变语义) |
5.3 选择建议
-
参数稀疏性:L1 vs L2 vs 其他方法
- L1正则化:通过绝对值惩罚强制参数归零,适合需要特征筛选的场景(如识别关键基因),L1 正则化在高维数据(特征数 > 样本数)中更易筛选有效特征,如基因表达数据(10000 + 特征,样本数 < 100)。
- L2正则化:参数整体缩小但非零,适合保留所有特征但降低噪声影响(如金融数据预测)。
- 其他方法:Dropout、BN等不直接修改参数,而是通过训练过程或数据增强间接抑制过拟合。
-
计算复杂度与工程实现
- 低复杂度:L1/L2正则化可直接嵌入损失函数,无需修改网络结构,适合快速迭代。
- 中复杂度:Dropout需在训练时随机丢弃神经元,测试时调整输出尺度;BatchNorm需计算批次统计量,适合大规模神经网络。
- 零额外计算:早停法仅需监控验证集,数据增强在内存中完成变换,性价比最高。
-
场景适配策略
- 图像分类:建议组合使用数据增强+Dropout+BatchNorm,例如:
- 数据增强(旋转/翻转)扩大样本多样性;
- Dropout防止神经元过拟合局部特征;
- BatchNorm稳定深层网络训练。
- 特征选择任务:优先使用L1正则化,例如:
- 基因表达数据中筛选关键基因;
- 文本分类中识别重要词向量。
- 计算资源有限:采用早停法+L2正则化,以最小计算成本缓解过拟合。
- 自然语言处理(NLP):可补充 “L2 正则化 + Dropout” 组合,如 Transformer 中常用 Dropout 防止注意力机制过拟合。
- 时间序列预测:早停法 + L2 正则化更适合,因数据点存在时序依赖,过拟合风险高。
- 图像分类:建议组合使用数据增强+Dropout+BatchNorm,例如:
-
组合使用技巧
- L1+L2(Elastic Net):同时享受L1的稀疏性和L2的稳定性,公式为:
L = L + α ( λ ∑ ∣ w i ∣ + ( 1 − λ ) ∑ w i 2 ) L = \mathcal{L} + \alpha(\lambda\sum|w_i| + (1-\lambda)\sum w_i^2) L=L+α(λ∑∣wi∣+(1−λ)∑wi2) - Dropout+BatchNorm:先BatchNorm再Dropout,可进一步降低神经元协同适应。 在 PyTorch 中,通常顺序为BatchNorm2d → ReLU → Dropout,先归一化再丢弃神经元,可减少内部协变量偏移对 Dropout 的影响。
- L1+L2(Elastic Net):同时享受L1的稀疏性和L2的稳定性,公式为: