机器学习模型调优指南:图解过拟合与欠拟合的本质及解决方案

1次阅读
没有评论

共计 2707 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

从可视化理解开始

先看两组模型的预测表现(使用 matplotlib 生成):

机器学习模型调优指南:图解过拟合与欠拟合的本质及解决方案

import numpy as np
import matplotlib.pyplot as plt

# 生成带噪声的二次曲线数据
np.random.seed(42)
X = np.linspace(-3, 3, 100)
y = X**2 + np.random.normal(0, 1, 100)

plt.figure(figsize=(12,4))

# 欠拟合示例(线性回归)plt.subplot(121)
plt.scatter(X, y, s=10, label='真实数据')
plt.plot(X, 2*X + 3, 'r-', label='欠拟合模型')
plt.title('欠拟合:高偏差低方差')
plt.legend()

# 过拟合示例(高阶多项式)plt.subplot(122)
plt.scatter(X, y, s=10, label='真实数据')
plt.plot(X, 0.1*X**5 - 0.8*X**3 + 2*X, 'g-', label='过拟合模型')
plt.title('过拟合:低偏差高方差')
plt.legend()

左图直线无法捕捉数据规律(欠拟合),右图曲线完美拟合训练点但波动剧烈(过拟合)。这就是偏差 - 方差权衡的直观体现。

偏差 - 方差分解的数学本质

模型的泛化误差可以分解为:

$$E[(y-\hat{f})^2] = \underbrace{(E[\hat{f}] – f)^2}{\text{偏差}^2} + \underbrace{E[(\hat{f} – E[\hat{f}])^2]} + \sigma^2$$}

  • 偏差:模型预测值与真实值的系统性差异
  • 方差:模型对训练数据扰动的敏感程度
  • 不可约误差:数据本身的噪声 $\sigma^2$

理想模型应该同时保持低偏差和低方差,但实践中往往需要权衡。

四大调参实战方法

1. L1/L2 正则化(以 PyTorch 为例)

import torch
import torch.nn as nn

# 定义带 L2 正则化的线性模型
class LinearModel(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.linear = nn.Linear(input_dim, 1)

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

model = LinearModel(10)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=0.1)  # L2 正则化系数 0.1

# L1 正则化需要手动实现
l1_lambda = 0.01
for param in model.parameters():
    optimizer.zero_grad()
    loss = criterion(output, target) + l1_lambda * torch.norm(param, 1)
    loss.backward()
    optimizer.step()

2. Early Stopping 监控

from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)

best_loss = float('inf')
patience = 5
counter = 0

for epoch in range(100):
    model.train()
    train_loss = train_one_epoch()

    model.eval()
    val_loss = evaluate(X_val, y_val)

    if val_loss < best_loss:
        best_loss = val_loss
        counter = 0
        torch.save(model.state_dict(), 'best_model.pt')
    else:
        counter += 1
        if counter >= patience:
            print(f'Early stopping at epoch {epoch}')
            break

3. Dropout 层应用

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(20, 64)
        self.dropout = nn.Dropout(0.5)  # 随机丢弃 50% 神经元
        self.fc2 = nn.Linear(64, 1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.dropout(x)  # 只在训练时生效
        return self.fc2(x)

4. 学习曲线诊断

from sklearn.model_selection import learning_curve

train_sizes, train_scores, val_scores = learning_curve(
    estimator=model,
    X=X_train,
    y=y_train,
    cv=5,
    scoring='neg_mean_squared_error'
)

plt.plot(train_sizes, -train_scores.mean(1), 'o-', label='训练误差')
plt.plot(train_sizes, -val_scores.mean(1), 'o-', label='验证误差')
plt.xlabel('训练样本量')
plt.ylabel('MSE')
plt.legend()

五大避坑指南

  1. 数据集划分
  2. 绝对避免用测试集参与任何调参过程
  3. 推荐比例:训练 60%/ 验证 20%/ 测试 20%
  4. 分类问题确保分层抽样(stratify=y)

  5. 正则化系数选择

  6. L2 正则化通常从 0.001 到 1 之间尝试
  7. 用验证集表现选择最佳系数
  8. 配合学习率调整(大正则化需要小学习率)

  9. 计算资源分配

  10. 简单模型 + 大数据 > 复杂模型 + 小数据
  11. 使用早停法节省训练时间
  12. 分布式训练时注意正则化的全局应用

  13. 特征工程检查

  14. 过拟合可能是特征泄漏导致
  15. 欠拟合需检查特征表达能力
  16. 必要时使用 PCA 降维

  17. 模型复杂度控制

  18. 神经网络优先增加宽度而非深度
  19. 决策树需要限制 max_depth
  20. 交叉验证选择最佳复杂度

思考与实践

  1. 当验证集和测试集表现差异超过 15% 时:
  2. 检查数据分布是否一致
  3. 确认没有在测试集上多次评估
  4. 考虑重新划分数据集

  5. 验证过拟合的实验设计:

  6. 在训练集子集上观察性能变化
  7. 添加噪声数据测试鲁棒性
  8. 比较训练 / 验证损失曲线分叉点

调参是门艺术,建议从小规模实验开始,每次只改变一个变量,做好实验记录。记住:没有万能参数,只有最适合当前数据和任务的参数组合。

正文完
 0
评论(没有评论)