玩转机器学习之神经网络,系统入门算法工程师|完结

9 阅读5分钟

在模型训练中,我们经常遇到这样的困境:训练集准确率高达 99%,但在测试集上却惨不忍睹。这就是典型的过拟合现象——模型像个只会死记硬背的学生,把训练数据的“噪声”也当成了“知识”背了下来。学习地址:pan.baidu.com/s/1WwerIZ_elz_FyPKqXAiZCA?pwd=waug

作为算法工程师,如何在不增加数据的前提下,通过正则化手段强行使模型“泛化”,是衡量基本功是否扎实的试金石。

一、 难点剖析:为什么会过拟合?

在数学层面,过拟合通常发生在模型参数量远大于数据量,或者模型训练时间过长的情况下。

核心冲突:

  • 优化目标:最小化训练误差。
  • 业务目标:最小化测试误差(泛化误差)。

当模型为了追求训练误差极低,强行拟合了数据中的随机噪声时,泛化能力就会断崖式下跌。

二、 突破方案:三大正则化利器

在工程实践中,我们有三种最主流的正则化手段,你需要掌握它们的适用场景。

1. L2 正则化(权重衰减)

原理:在损失函数中加入所有权重的平方和。

Losstotal=Lossoriginal+λ2n∑∣∣w∣∣2Losstotal​=Lossoriginal​+2nλ​∑∣∣w∣∣2
作用:限制权重的大小,使模型更平滑,避免任何一个特征对结果产生过大的影响(抑制共线性)。

2. Dropout(随机失活)

原理:在训练过程中,以概率 pp 随机“关掉”一部分神经元。
作用:这相当于每次都在训练一个不同的“瘦弱”网络。测试时,由于我们不能随机关掉神经元,而是让所有神经元都参与工作,但权重需乘以 pp(或训练时除以 pp)。这迫使神经元不能依赖特定的前置特征,增强了鲁棒性。

3. 早停法

原理:在训练过程中监控验证集的 Loss,当验证集 Loss 不再下降(甚至上升)时,立即停止训练。
作用:防止模型在后期开始“死记硬背”噪声。

三、 PyTorch 实战:从过拟合到泛化

下面我们通过一个具体的 PyTorch 案例,模拟过拟合现象,并综合使用上述技术进行修复。

1. 构造数据与过拟合模型

我们故意生成少量的带噪数据,并构建一个参数量较大的全连接网络,以此诱导过拟合。

python

复制

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

# 1. 生成带噪声的二次函数数据 (只有 20 个样本,极易过拟合)
# y = 2x^2 + 3x + 4 + noise
X = torch.unsqueeze(torch.linspace(-5, 5, 20), dim=1)
y = 2 * X.pow(2) + 3 * X + 4 + 2 * torch.randn(X.size())

# 定义一个参数量很大的网络 (容易记住噪声)
class OverfitNet(nn.Module):
    def __init__(self):
        super(OverfitNet, self).__init__()
        # 三个隐藏层,神经元数量较多,参数量远超样本量
        self.hidden = nn.Sequential(
            nn.Linear(1, 100),
            nn.ReLU(),
            nn.Linear(100, 100),
            nn.ReLU(),
            nn.Linear(100, 1)
        )

    def forward(self, x):
        return self.hidden(x)

net_overfit = OverfitNet()
optimizer = torch.optim.Adam(net_overfit.parameters(), lr=0.01)
loss_func = nn.MSELoss()

print("正在训练过拟合模型...")
# 训练 500 轮 (足够让它背下所有噪声)
for i in range(500):
    prediction = net_overfit(X)
    loss = loss_func(prediction, y)
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# 可视化结果
plt.figure(figsize=(10, 4))
plt.subplot(121)
plt.scatter(X.data.numpy(), y.data.numpy(), c='red', label='Real Data')
plt.plot(X.data.numpy(), net_overfit(X).data.numpy(), c='blue', lw=2, label='Overfit Prediction')
plt.title("Overfitting Model (Oscillating)")
plt.legend()

2. 引入正则化与 Dropout 修复

现在,我们构建一个加入了 Dropout 和 Weight Decay 的模型,并使用早停法。

python

复制

class RegularizedNet(nn.Module):
    def __init__(self):
        super(RegularizedNet, self).__init__()
        self.hidden = nn.Sequential(
            nn.Linear(1, 100),
            nn.ReLU(),
            # 加入 Dropout 层,训练时随机丢弃 50% 神经元
            nn.Dropout(p=0.5), 
            
            nn.Linear(100, 100),
            nn.ReLU(),
            nn.Dropout(p=0.5),
            
            nn.Linear(100, 1)
        )

    def forward(self, x):
        return self.hidden(x)

net_reg = RegularizedNet()

# ---------------------------------------------------------
# 关键点 1:在优化器中设置 weight_decay (L2 正则化系数)
# ---------------------------------------------------------
# weight_decay=1e-4 相当于在 Loss 中加了 lambda * ||w||^2
optimizer_reg = torch.optim.Adam(net_reg.parameters(), lr=0.01, weight_decay=1e-4)
loss_func = nn.MSELoss()

# 记录验证集 Loss 用于早停
best_val_loss = float('inf')
trigger_times = 0
patience = 50 # 容忍次数

print("正在训练正则化模型...")
for i in range(1000):
    prediction = net_reg(X)
    loss = loss_func(prediction, y)
    
    optimizer_reg.zero_grad()
    loss.backward()
    optimizer_reg.step()
    
    # ---------------------------------------------------------
    # 关键点 2:模拟验证集监控 (这里为了演示直接用训练集代替,实际请划分数据集)
    # ---------------------------------------------------------
    current_loss = loss.item()
    
    # 早停法逻辑
    if current_loss < best_val_loss:
        best_val_loss = current_loss
        trigger_times = 0
        # 保存最佳模型 (这里省略保存代码)
    else:
        trigger_times += 1
        if trigger_times >= patience:
            print(f"Early Stop! Epoch: {i}")
            break

# 测试时,PyTorch 会自动关闭 Dropout (model.eval() 模式)
net_reg.eval() 
with torch.no_grad():
    prediction_reg = net_reg(X)

plt.subplot(122)
plt.scatter(X.data.numpy(), y.data.numpy(), c='red', label='Real Data')
plt.plot(X.data.numpy(), prediction_reg.data.numpy(), c='green', lw=2, label='Regularized Prediction')
plt.title("Regularized Model (Smooth)")
plt.legend()
plt.show()

3. 结果对比与面试加分点

运行上述代码后,你会看到:

  • 左图:蓝色线条疯狂波动,试图穿过每一个红点(噪声),这是过拟合的典型特征。
  • 右图:绿色曲线平滑且居中,虽然可能没有穿过所有红点,但它抓住了数据的“主趋势”。

面试必杀技(为什么 Dropout 有效?):

  1. 模型平均:Dropout 可以看作是训练了 2n2n 个子网络的指数级集成。
  2. 特征适应性:防止某些特征总是协同出现,强制每个神经元具备独立的特征提取能力。
  3. 避免复杂共适应:神经元不能依赖特定的上游神经元,必须变得更有用。

四、 总结

突破算法考试的难点,不在于你会背多少公式,而在于你是否理解偏差与方差的权衡。

  • L2 正则化:是“刹车”,防止权重过大跑偏。
  • Dropout:是“减法”,强迫模型精简神经连接。
  • 早停法:是“收手”,在最佳时刻停止。

掌握了这三板斧,无论是面对 CCF-CSP 等考试,还是工业界的模型调优,你都能游刃有余。