过拟合与正则化
过拟合是深度学习中最常见也最关键的问题——模型在训练集上表现极好但在测试集上性能急剧下降,本质上是模型“记住了噪声而非规律”。应对手段形成了一整套正则化工具箱:L2 正则化(Weight Decay)在损失函数中加入 λ‖w‖² 使权重趋近于零,等价于对参数施加高斯先验,是 Adam 优化器的标配(AdamW 专门解耦了 weight decay);L1 正则化产生稀疏权重,可用于特征选择;Dropout 随机关闭一部分神经元(p=0.1~0.5),强迫网络学习冗余表示——BERT、GPT 中广泛使用;Early Stopping 监控验证集 loss,在开始上升时停止训练,是最简单有效的防过拟合策略;数据增强(Mixup、CutMix、RandAugment)通过合成新样本扩大训练集;Label Smoothing 将 hard label 软化为 (1-ε)·one_hot + ε/K,防止模型对输出过度自信——GPT、ViT 等模型均使用 ε=0.1。本节逐一讲解每种正则化技术的数学原理,并用代码验证其效果。
一、偏差-方差权衡
预测误差的分解:
E[(y - ŷ)²] = Bias² + Variance + σ²_noise
偏差(Bias):模型预测的平均值与真实值的偏离
Bias = E[ŷ] - y_true(模型系统性地偏高或偏低)
方差(Variance):模型预测因训练数据改变而产生的波动
Var = E[(ŷ - E[ŷ])²](不同训练集训练出的模型预测差异多大)
不可约误差:数据本身的随机噪声 σ²,任何模型都无法消除
E[(y - ŷ)²] = Bias² + Variance + σ²_noise
偏差(Bias):模型预测的平均值与真实值的偏离
Bias = E[ŷ] - y_true(模型系统性地偏高或偏低)
方差(Variance):模型预测因训练数据改变而产生的波动
Var = E[(ŷ - E[ŷ])²](不同训练集训练出的模型预测差异多大)
不可约误差:数据本身的随机噪声 σ²,任何模型都无法消除
具体例子——多项式回归拟合:
真实函数:y = sin(x) + 噪声
在区间 [0, 2π] 上有 10 个训练点
关键观察:
• 1 次:训练和测试 MSE 都高 → 模型太简单,无法捕捉 sin 曲线
• 3 次:训练 MSE 适中,测试 MSE 接近 → 最佳平衡
• 9 次:训练 MSE 极低但测试暴涨 → 完美拟合了训练点(包括噪声)
• 15 次:参数比数据点还多 → 可以精确穿过每个点,但在点之间剧烈摇摆
真实函数:y = sin(x) + 噪声
在区间 [0, 2π] 上有 10 个训练点
| 模型 | 参数量 | 训练 MSE | 测试 MSE | 偏差 | 方差 | 状态 |
|---|---|---|---|---|---|---|
| 线性 (1 次) | 2 | 0.452 | 0.489 | 高 | 低 | 欠拟合 |
| 3 次多项式 | 4 | 0.089 | 0.105 | 中 | 中 | 刚好 |
| 5 次多项式 | 6 | 0.042 | 0.098 | 低 | 略高 | 轻微过拟合 |
| 9 次多项式 | 10 | 0.001 | 2.156 | 极低 | 极高 | 严重过拟合 |
| 15 次多项式 | 16 | ≈0 | 85.3 | ≈0 | 爆炸 | 灾难性过拟合 |
• 1 次:训练和测试 MSE 都高 → 模型太简单,无法捕捉 sin 曲线
• 3 次:训练 MSE 适中,测试 MSE 接近 → 最佳平衡
• 9 次:训练 MSE 极低但测试暴涨 → 完美拟合了训练点(包括噪声)
• 15 次:参数比数据点还多 → 可以精确穿过每个点,但在点之间剧烈摇摆
二、过拟合的判断方法
学习曲线分析(最直接的方法):
Epoch 1: Train Loss=2.45, Val Loss=2.52 (差距小→正常)
Epoch 10: Train Loss=0.82, Val Loss=0.91 (差距小→正常)
Epoch 30: Train Loss=0.23, Val Loss=0.35 (开始拉开差距)
Epoch 50: Train Loss=0.08, Val Loss=0.42 (差距持续增大)
Epoch 80: Train Loss=0.02, Val Loss=0.58 (验证集损失上升→过拟合!)
Epoch 100: Train Loss=0.01, Val Loss=0.71 (严重过拟合)
判断标准:
① Train Loss 持续下降但 Val Loss 开始上升 → 经典过拟合信号
② Val Loss 停滞不前但 Train Loss 继续降 → 早期过拟合
③ Train Accuracy=99% 而 Val Accuracy=72% → 过拟合(差距>15%要警惕)
④ 训练集与测试集性能差距越大 → 过拟合越严重
Epoch 1: Train Loss=2.45, Val Loss=2.52 (差距小→正常)
Epoch 10: Train Loss=0.82, Val Loss=0.91 (差距小→正常)
Epoch 30: Train Loss=0.23, Val Loss=0.35 (开始拉开差距)
Epoch 50: Train Loss=0.08, Val Loss=0.42 (差距持续增大)
Epoch 80: Train Loss=0.02, Val Loss=0.58 (验证集损失上升→过拟合!)
Epoch 100: Train Loss=0.01, Val Loss=0.71 (严重过拟合)
判断标准:
① Train Loss 持续下降但 Val Loss 开始上升 → 经典过拟合信号
② Val Loss 停滞不前但 Train Loss 继续降 → 早期过拟合
③ Train Accuracy=99% 而 Val Accuracy=72% → 过拟合(差距>15%要警惕)
④ 训练集与测试集性能差距越大 → 过拟合越严重
三、数据集划分
三集划分:
训练集(Training Set):70-80%,用于训练模型
验证集(Validation Set):10-15%,用于调超参数
测试集(Test Set):10-15%,最终评估(只用一次)
K 折交叉验证(数据量小时):
将数据分为 K 份,轮流用 1 份做验证,其余做训练
最终性能 = K 次结果的平均值
训练集(Training Set):70-80%,用于训练模型
验证集(Validation Set):10-15%,用于调超参数
测试集(Test Set):10-15%,最终评估(只用一次)
K 折交叉验证(数据量小时):
将数据分为 K 份,轮流用 1 份做验证,其余做训练
最终性能 = K 次结果的平均值
5 折交叉验证完整示例:
数据集:1000 个样本
Fold 1: 训练 [201-1000], 验证 [1-200] → Val Acc = 91.0%
Fold 2: 训练 [1-200,401-1000], 验证 [201-400] → Val Acc = 89.5%
Fold 3: 训练 [1-400,601-1000], 验证 [401-600] → Val Acc = 92.0%
Fold 4: 训练 [1-600,801-1000], 验证 [601-800] → Val Acc = 90.0%
Fold 5: 训练 [1-800], 验证 [801-1000] → Val Acc = 91.5%
平均 Accuracy = (91.0+89.5+92.0+90.0+91.5)/5 = 90.8% ± 0.89%
标准差 = 0.89%(说明不同划分影响不大→模型较稳定)
数据集:1000 个样本
Fold 1: 训练 [201-1000], 验证 [1-200] → Val Acc = 91.0%
Fold 2: 训练 [1-200,401-1000], 验证 [201-400] → Val Acc = 89.5%
Fold 3: 训练 [1-400,601-1000], 验证 [401-600] → Val Acc = 92.0%
Fold 4: 训练 [1-600,801-1000], 验证 [601-800] → Val Acc = 90.0%
Fold 5: 训练 [1-800], 验证 [801-1000] → Val Acc = 91.5%
平均 Accuracy = (91.0+89.5+92.0+90.0+91.5)/5 = 90.8% ± 0.89%
标准差 = 0.89%(说明不同划分影响不大→模型较稳定)
四、L2 正则化(权重衰减)
L2 正则化目标函数:
J(w) = L(w) + (λ/2) × Σⱼ wⱼ²
梯度更新规则:
∂J/∂w = ∂L/∂w + λw
w ← w - η(∂L/∂w + λw) = (1-ηλ)w - η∂L/∂w
每次更新都将权重缩小为 (1-ηλ) 倍,所以叫"权重衰减"
J(w) = L(w) + (λ/2) × Σⱼ wⱼ²
梯度更新规则:
∂J/∂w = ∂L/∂w + λw
w ← w - η(∂L/∂w + λw) = (1-ηλ)w - η∂L/∂w
每次更新都将权重缩小为 (1-ηλ) 倍,所以叫"权重衰减"
数值示例——L2 正则化的效果:
学习率 η = 0.01,正则化系数 λ = 0.1
当前权重 w = [2.5, -1.8, 0.3, 3.1, -0.1]
损失梯度 ∂L/∂w = [0.5, -0.3, 0.1, 0.8, -0.05]
无正则化的更新:
w₁_new = 2.5 - 0.01×0.5 = 2.5 - 0.005 = 2.495
w₄_new = 3.1 - 0.01×0.8 = 3.1 - 0.008 = 3.092
L2 正则化的更新:
w₁_new = 2.5 - 0.01×(0.5 + 0.1×2.5) = 2.5 - 0.01×0.75 = 2.5 - 0.0075 = 2.4925
w₂_new = -1.8 - 0.01×(-0.3 + 0.1×(-1.8)) = -1.8 - 0.01×(-0.48) = -1.8 + 0.0048 = -1.7952
w₃_new = 0.3 - 0.01×(0.1 + 0.1×0.3) = 0.3 - 0.01×0.13 = 0.3 - 0.0013 = 0.2987
w₄_new = 3.1 - 0.01×(0.8 + 0.1×3.1) = 3.1 - 0.01×1.11 = 3.1 - 0.0111 = 3.0889
w₅_new = -0.1 - 0.01×(-0.05 + 0.1×(-0.1)) = -0.1 - 0.01×(-0.06) = -0.1 + 0.0006 = -0.0994
观察:
• |w₄|=3.1 最大 → 额外惩罚 0.01×0.31=0.0031 最大(大权重被压得更厉害)
• |w₅|=0.1 最小 → 额外惩罚几乎为零
• L2 不会将权重压缩到 0,但会持续使其变小
学习率 η = 0.01,正则化系数 λ = 0.1
当前权重 w = [2.5, -1.8, 0.3, 3.1, -0.1]
损失梯度 ∂L/∂w = [0.5, -0.3, 0.1, 0.8, -0.05]
无正则化的更新:
w₁_new = 2.5 - 0.01×0.5 = 2.5 - 0.005 = 2.495
w₄_new = 3.1 - 0.01×0.8 = 3.1 - 0.008 = 3.092
L2 正则化的更新:
w₁_new = 2.5 - 0.01×(0.5 + 0.1×2.5) = 2.5 - 0.01×0.75 = 2.5 - 0.0075 = 2.4925
w₂_new = -1.8 - 0.01×(-0.3 + 0.1×(-1.8)) = -1.8 - 0.01×(-0.48) = -1.8 + 0.0048 = -1.7952
w₃_new = 0.3 - 0.01×(0.1 + 0.1×0.3) = 0.3 - 0.01×0.13 = 0.3 - 0.0013 = 0.2987
w₄_new = 3.1 - 0.01×(0.8 + 0.1×3.1) = 3.1 - 0.01×1.11 = 3.1 - 0.0111 = 3.0889
w₅_new = -0.1 - 0.01×(-0.05 + 0.1×(-0.1)) = -0.1 - 0.01×(-0.06) = -0.1 + 0.0006 = -0.0994
观察:
• |w₄|=3.1 最大 → 额外惩罚 0.01×0.31=0.0031 最大(大权重被压得更厉害)
• |w₅|=0.1 最小 → 额外惩罚几乎为零
• L2 不会将权重压缩到 0,但会持续使其变小
五、L1 正则化(稀疏化)
J(w) = L(w) + λ × Σⱼ |wⱼ|
∂J/∂w = ∂L/∂w + λ × sign(w)
其中 sign(w) = +1 (w>0), -1 (w<0), 0 (w=0)
∂J/∂w = ∂L/∂w + λ × sign(w)
其中 sign(w) = +1 (w>0), -1 (w<0), 0 (w=0)
L1 vs L2 对比——特征选择效果:
初始权重 w = [2.5, -1.8, 0.3, 3.1, -0.1],η=0.01, λ=0.1
L1 更新:惩罚项 = λ×sign(w)
w₃: 0.3 - 0.01×(0.1 + 0.1×1) = 0.3 - 0.002 = 0.298
w₅: -0.1 - 0.01×(-0.05 + 0.1×(-1)) = -0.1 - 0.01×(-0.15) = -0.1 + 0.0015 = -0.0985
经过 50~100 步迭代后:
L2: w ≈ [1.2, -0.9, 0.15, 1.5, -0.05](所有权重变小但不为0)
L1: w ≈ [1.4, -1.0, 0.00, 1.8, 0.00](小权重被压成 0→稀疏!)
稀疏的含义:w₃=0 和 w₅=0 意味着模型自动丢弃了第 3 和第 5 个特征→自动特征选择
初始权重 w = [2.5, -1.8, 0.3, 3.1, -0.1],η=0.01, λ=0.1
L1 更新:惩罚项 = λ×sign(w)
w₃: 0.3 - 0.01×(0.1 + 0.1×1) = 0.3 - 0.002 = 0.298
w₅: -0.1 - 0.01×(-0.05 + 0.1×(-1)) = -0.1 - 0.01×(-0.15) = -0.1 + 0.0015 = -0.0985
经过 50~100 步迭代后:
L2: w ≈ [1.2, -0.9, 0.15, 1.5, -0.05](所有权重变小但不为0)
L1: w ≈ [1.4, -1.0, 0.00, 1.8, 0.00](小权重被压成 0→稀疏!)
稀疏的含义:w₃=0 和 w₅=0 意味着模型自动丢弃了第 3 和第 5 个特征→自动特征选择
六、Dropout
训练时:以概率 p 随机"关闭"每个神经元(输出置零)
测试时:所有神经元都打开,输出乘以 (1-p) 或训练时除以 (1-p)
直觉:迫使网络不要过度依赖任何单个特征
概率解释:相当于同时训练 2ⁿ 个子网络的集成
测试时:所有神经元都打开,输出乘以 (1-p) 或训练时除以 (1-p)
直觉:迫使网络不要过度依赖任何单个特征
概率解释:相当于同时训练 2ⁿ 个子网络的集成
Dropout 前向传播完整计算(Inverted Dropout):
层输入:h = [0.8, -0.5, 1.2, 0.3, -0.9, 0.6](6 个神经元)
Dropout 率 p = 0.5(50% 的神经元被关闭)
训练阶段:
随机生成 mask(每个元素独立 Bernoulli(0.5)):
mask = [1, 0, 1, 0, 1, 0](关闭了第 2,4,6 个神经元)
应用 mask 并缩放(除以保留概率 1-p = 0.5):
h_drop = h × mask / (1-p)
= [0.8×1, -0.5×0, 1.2×1, 0.3×0, -0.9×1, 0.6×0] / 0.5
= [0.8, 0, 1.2, 0, -0.9, 0] / 0.5
= [1.6, 0, 2.4, 0, -1.8, 0]
测试阶段(不做 Dropout):
h_test = [0.8, -0.5, 1.2, 0.3, -0.9, 0.6](原样输出)
期望等价性验证:
训练时 E[h_drop₁] = 0.8 × P(mask=1) / 0.5 = 0.8 × 0.5 / 0.5 = 0.8
测试时 h_test₁ = 0.8
→ 训练和测试的期望输出一致 ✓
层输入:h = [0.8, -0.5, 1.2, 0.3, -0.9, 0.6](6 个神经元)
Dropout 率 p = 0.5(50% 的神经元被关闭)
训练阶段:
随机生成 mask(每个元素独立 Bernoulli(0.5)):
mask = [1, 0, 1, 0, 1, 0](关闭了第 2,4,6 个神经元)
应用 mask 并缩放(除以保留概率 1-p = 0.5):
h_drop = h × mask / (1-p)
= [0.8×1, -0.5×0, 1.2×1, 0.3×0, -0.9×1, 0.6×0] / 0.5
= [0.8, 0, 1.2, 0, -0.9, 0] / 0.5
= [1.6, 0, 2.4, 0, -1.8, 0]
测试阶段(不做 Dropout):
h_test = [0.8, -0.5, 1.2, 0.3, -0.9, 0.6](原样输出)
期望等价性验证:
训练时 E[h_drop₁] = 0.8 × P(mask=1) / 0.5 = 0.8 × 0.5 / 0.5 = 0.8
测试时 h_test₁ = 0.8
→ 训练和测试的期望输出一致 ✓
Dropout 在不同模型中的使用指南:
| 位置 | 典型 Dropout 率 | 说明 |
|---|---|---|
| 全连接层之间 | 0.5 | 最常用位置,正则化效果最强 |
| CNN 卷积层之后 | 0.1~0.3 | 卷积本身有权值共享,不需要太多 |
| RNN/LSTM 层间 | 0.2~0.5 | 时间步内不 Dropout,只在层间 |
| Transformer Attention | 0.1 | Attention 分数上的 Dropout |
| 输入层 | 0.1~0.2 | 模拟输入噪声/缺失特征 |
| 输出层 | 不使用 | 最终预测不应随机 |
七、Early Stopping(早停)
策略:在验证损失不再下降时停止训练
完整示例(patience=5):
Epoch 1: Val=1.82 best=1.82 wait=0 →保存
Epoch 5: Val=0.95 best=0.95 wait=0 →保存
Epoch 10: Val=0.52 best=0.52 wait=0 →保存
Epoch 15: Val=0.38 best=0.38 wait=0 →保存(最优!)
Epoch 16: Val=0.40 best=0.38 wait=1
Epoch 17: Val=0.39 best=0.38 wait=2
Epoch 18: Val=0.41 best=0.38 wait=3
Epoch 19: Val=0.43 best=0.38 wait=4
Epoch 20: Val=0.45 best=0.38 wait=5 → STOP! 恢复 Epoch 15 的权重
如果继续训练:
Epoch 50: Train=0.01, Val=0.72(已经严重过拟合了)
Early Stopping 节省了 30 个 Epoch 的无用训练,并获得了最佳泛化模型。
完整示例(patience=5):
Epoch 1: Val=1.82 best=1.82 wait=0 →保存
Epoch 5: Val=0.95 best=0.95 wait=0 →保存
Epoch 10: Val=0.52 best=0.52 wait=0 →保存
Epoch 15: Val=0.38 best=0.38 wait=0 →保存(最优!)
Epoch 16: Val=0.40 best=0.38 wait=1
Epoch 17: Val=0.39 best=0.38 wait=2
Epoch 18: Val=0.41 best=0.38 wait=3
Epoch 19: Val=0.43 best=0.38 wait=4
Epoch 20: Val=0.45 best=0.38 wait=5 → STOP! 恢复 Epoch 15 的权重
如果继续训练:
Epoch 50: Train=0.01, Val=0.72(已经严重过拟合了)
Early Stopping 节省了 30 个 Epoch 的无用训练,并获得了最佳泛化模型。
八、数据增强
图像数据增强(以 CIFAR-10 为例):
原始:32×32 猫咪图片
Mixup 数值示例:
图片 A(猫)像素 = [0.8, 0.5, 0.3], 标签 = [1, 0]
图片 B(狗)像素 = [0.2, 0.7, 0.6], 标签 = [0, 1]
λ = 0.7(从 Beta(α,α) 分布采样,α 常取 0.2)
混合图片 = 0.7×[0.8,0.5,0.3] + 0.3×[0.2,0.7,0.6] = [0.62, 0.56, 0.39]
混合标签 = 0.7×[1,0] + 0.3×[0,1] = [0.7, 0.3]
原始:32×32 猫咪图片
| 增强方法 | 操作 | 数学描述 | 效果 |
|---|---|---|---|
| 随机水平翻转 | P=0.5 左右镜像 | I'(x,y) = I(W-1-x,y) | 翻倍有效数据量 |
| 随机裁剪 | 先 Pad 4px 后裁原大小 | 36×36→随机裁出32×32 | 平移不变性 |
| 颜色抖动 | 亮度/对比度/饱和度随机±20% | I' = α×I + β | 光照不变性 |
| 随机旋转 | ±15° | 旋转矩阵变换 | 旋转不变性 |
| 随机擦除 | 随机遮挡一块区域 | 区域→灰色/噪声 | 类似 Dropout |
| Mixup | 两张图混合 | I' = λI₁+(1-λ)I₂ | 决策边界平滑 |
| CutMix | 用另一图的矩形区域替换 | 局部替换 | 信息更完整 |
图片 A(猫)像素 = [0.8, 0.5, 0.3], 标签 = [1, 0]
图片 B(狗)像素 = [0.2, 0.7, 0.6], 标签 = [0, 1]
λ = 0.7(从 Beta(α,α) 分布采样,α 常取 0.2)
混合图片 = 0.7×[0.8,0.5,0.3] + 0.3×[0.2,0.7,0.6] = [0.62, 0.56, 0.39]
混合标签 = 0.7×[1,0] + 0.3×[0,1] = [0.7, 0.3]
九、Batch Normalization 的正则化效果
BN 的意外发现:有正则化效果!
原因:每个 mini-batch 的均值和方差是随机的(因为 batch 的随机采样)
→ 标准化过程引入了噪声 → 类似 Dropout 的效果
对比实验(ResNet-18 on CIFAR-10):
原因:每个 mini-batch 的均值和方差是随机的(因为 batch 的随机采样)
→ 标准化过程引入了噪声 → 类似 Dropout 的效果
对比实验(ResNet-18 on CIFAR-10):
| 配置 | Train Acc | Val Acc | 差距 |
|---|---|---|---|
| 无 BN,无 Dropout | 99.8% | 91.2% | 8.6% |
| 有 BN,无 Dropout | 99.5% | 93.8% | 5.7% |
| 无 BN,有 Dropout(0.5) | 98.2% | 93.1% | 5.1% |
| 有 BN + Dropout(0.2) | 98.8% | 94.5% | 4.3% |
十、正则化强度的选择
λ(L2/L1 正则化系数)的调参:
以 MNIST 分类(2 层 MLP,256 隐藏单元)为例:
最佳 λ=0.001:Val Acc 最高 98.1%,权重范数适中 18.5
Train-Val 差距仅 1.1%(良好泛化)
以 MNIST 分类(2 层 MLP,256 隐藏单元)为例:
| λ | Train Acc | Val Acc | 权重范数 ||w||₂ | 状态 |
|---|---|---|---|---|
| 0(无正则化) | 99.9% | 97.2% | 45.8 | 轻微过拟合 |
| 0.0001 | 99.7% | 97.8% | 32.1 | 改善 |
| 0.001 | 99.2% | 98.1% | 18.5 | 最佳 |
| 0.01 | 97.5% | 97.3% | 8.2 | 正则化偏强 |
| 0.1 | 92.1% | 91.8% | 2.1 | 欠拟合 |
| 1.0 | 85.3% | 85.0% | 0.3 | 严重欠拟合 |
Train-Val 差距仅 1.1%(良好泛化)
十一、正则化方法总结
| 方法 | 原理 | 超参数 | 常用值 | 适用场景 |
|---|---|---|---|---|
| L2 正则化 | 约束权重大小 | λ | 1e-4 ~ 1e-2 | 几乎所有模型 |
| L1 正则化 | 稀疏化权重 | λ | 1e-5 ~ 1e-3 | 特征选择 |
| Dropout | 随机关闭神经元 | p | 0.1~0.5 | 全连接层 |
| Early Stopping | 在过拟合前停止 | patience | 5~20 | 所有训练任务 |
| 数据增强 | 增加数据多样性 | 增强强度 | 任务相关 | 图像/文本/音频 |
| BatchNorm | 标准化+噪声 | momentum | 0.1 | CNN/MLP |
| Label Smoothing | 软化标签 | ε | 0.1 | 分类任务 |
Label Smoothing 数值示例:
10 分类任务,ε = 0.1:
硬标签:y = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
软标签:y' = (1-ε)×y + ε/K = 0.9×y + 0.01
y' = [0.01, 0.01, 0.01, 0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
效果:模型不再试图输出极端概率 [0,...,1,...,0]
→ 减少过自信 → 改善泛化 → 特别适合知识蒸馏
10 分类任务,ε = 0.1:
硬标签:y = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
软标签:y' = (1-ε)×y + ε/K = 0.9×y + 0.01
y' = [0.01, 0.01, 0.01, 0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
效果:模型不再试图输出极端概率 [0,...,1,...,0]
→ 减少过自信 → 改善泛化 → 特别适合知识蒸馏
十二、代码验证(C# / Rust)
C#(.NET 10)
// dotnet run 即可执行
// ===== L2 / L1 正则化效果 =====
double[] w = { 2.5, -1.8, 0.3, 3.1, -0.1 };
double[] grad = { 0.5, -0.3, 0.1, 0.8, -0.05 };
double eta = 0.01, lam = 0.1;
Console.Write("无正则化: ");
for (int i = 0; i < 5; i++) Console.Write($"{w[i] - eta * grad[i],8:F4}");
Console.WriteLine();
Console.Write("L2 正则化:");
for (int i = 0; i < 5; i++) Console.Write($"{w[i] - eta * (grad[i] + lam * w[i]),8:F4}");
Console.WriteLine();
Console.Write("L1 正则化:");
for (int i = 0; i < 5; i++) Console.Write($"{w[i] - eta * (grad[i] + lam * Math.Sign(w[i])),8:F4}");
Console.WriteLine();
// ===== Dropout 前向传播 =====
double[] h = { 0.8, -0.5, 1.2, 0.3, -0.9, 0.6 };
double pDrop = 0.5;
int[] mask = { 0, 1, 1, 1, 0, 0 }; // 模拟随机掩码 (seed=42)
Console.WriteLine($"\nmask = [{string.Join(", ", mask)}]");
Console.Write("Dropout 输出 = [");
for (int i = 0; i < 6; i++)
{
if (i > 0) Console.Write(", ");
Console.Write($"{h[i] * mask[i] / (1 - pDrop):F2}");
}
Console.WriteLine($"]\n期望输出均值 = {h.Average():F3}");
// ===== Early Stopping 模拟 =====
int patience = 5, wait = 0, bestEpoch = 0;
double bestVal = double.MaxValue;
for (int epoch = 0; epoch < 50; epoch++)
{
double val = 2.5 * Math.Exp(-0.06 * epoch) + 0.1 + 0.005 * Math.Max(0, epoch - 15);
if (val < bestVal) { bestVal = val; bestEpoch = epoch; wait = 0; }
else if (++wait >= patience)
{
Console.WriteLine($"\nEarly Stopping at epoch {epoch}");
Console.WriteLine($"Best epoch: {bestEpoch}, Best val loss: {bestVal:F4}");
break;
}
}
// ===== Mixup 数据增强 =====
double[] imgA = { 0.8, 0.5, 0.3 }, imgB = { 0.2, 0.7, 0.6 };
double[] labelA = { 1, 0 }, labelB = { 0, 1 };
double lamMix = 0.7;
Console.Write("\nMixup 图片: [");
for (int i = 0; i < 3; i++)
{
if (i > 0) Console.Write(", ");
Console.Write($"{lamMix * imgA[i] + (1 - lamMix) * imgB[i]:F2}");
}
Console.Write("]\nMixup 标签: [");
for (int i = 0; i < 2; i++)
{
if (i > 0) Console.Write(", ");
Console.Write($"{lamMix * labelA[i] + (1 - lamMix) * labelB[i]:F1}");
}
Console.WriteLine("]");
// ===== Label Smoothing =====
int K = 10; double eps = 0.1;
Console.Write("\n硬标签: [");
for (int i = 0; i < K; i++) { if (i > 0) Console.Write(", "); Console.Write(i == 3 ? "1" : "0"); }
Console.Write("]\n软标签: [");
for (int i = 0; i < K; i++)
{
if (i > 0) Console.Write(", ");
double hard = i == 3 ? 1.0 : 0.0;
Console.Write($"{(1 - eps) * hard + eps / K:F2}");
}
Console.WriteLine("]");
Rust
fn main() {
// ===== L2 / L1 正则化效果 =====
let w = [2.5_f64, -1.8, 0.3, 3.1, -0.1];
let grad = [0.5_f64, -0.3, 0.1, 0.8, -0.05];
let (eta, lam) = (0.01_f64, 0.1);
print!("无正则化: ");
for i in 0..5 { print!("{:8.4}", w[i] - eta * grad[i]); }
println!();
print!("L2 正则化:");
for i in 0..5 { print!("{:8.4}", w[i] - eta * lam.mul_add(w[i], grad[i])); }
println!();
print!("L1 正则化:");
for i in 0..5 { print!("{:8.4}", w[i] - eta * lam.mul_add(w[i].signum(), grad[i])); }
println!();
// ===== Dropout 前向传播 =====
let h = [0.8_f64, -0.5, 1.2, 0.3, -0.9, 0.6];
let p_drop = 0.5_f64;
let mask = [0.0_f64, 1.0, 1.0, 1.0, 0.0, 0.0]; // 模拟随机掩码
println!("\nmask = {:?}", mask.map(|v| v as i32));
print!("Dropout 输出 = [");
for i in 0..6 {
if i > 0 { print!(", "); }
print!("{:.2}", h[i] * mask[i] / (1.0 - p_drop));
}
let h_mean: f64 = h.iter().sum::<f64>() / h.len() as f64;
println!("]\n期望输出均值 = {h_mean:.3}");
// ===== Early Stopping 模拟 =====
let (patience, mut wait, mut best_epoch) = (5_i32, 0_i32, 0_i32);
let mut best_val = f64::MAX;
for epoch in 0..50 {
let val = 2.5 * (-0.06 * epoch as f64).exp()
+ 0.1 + 0.005 * (epoch - 15).max(0) as f64;
if val < best_val { best_val = val; best_epoch = epoch; wait = 0; }
else { wait += 1; if wait >= patience {
println!("\nEarly Stopping at epoch {epoch} (patience={patience})");
println!("Best epoch: {best_epoch}, Best val loss: {best_val:.4}");
break;
}}
}
// ===== Mixup 数据增强 =====
let img_a = [0.8, 0.5, 0.3_f64];
let img_b = [0.2, 0.7, 0.6_f64];
let label_a = [1.0, 0.0_f64];
let label_b = [0.0, 1.0_f64];
let lam_mix = 0.7_f64;
print!("\nMixup 图片: [");
for i in 0..3 {
if i > 0 { print!(", "); }
print!("{:.2}", lam_mix.mul_add(img_a[i], (1.0 - lam_mix) * img_b[i]));
}
print!("]\nMixup 标签: [");
for i in 0..2 {
if i > 0 { print!(", "); }
print!("{:.1}", lam_mix.mul_add(label_a[i], (1.0 - lam_mix) * label_b[i]));
}
println!("]");
// ===== Label Smoothing =====
let (k, eps) = (10, 0.1_f64);
print!("\n硬标签: [");
for i in 0..k { if i > 0 { print!(", "); } print!("{}", if i == 3 { 1 } else { 0 }); }
print!("]\n软标签: [");
for i in 0..k {
if i > 0 { print!(", "); }
let hard = if i == 3 { 1.0 } else { 0.0 };
print!("{:.2}", (1.0 - eps).mul_add(hard, eps / k as f64));
}
println!("]");
}