机器学习模型调优实战:从图解过拟合与欠拟合到高方差低偏差调节

1次阅读
没有评论

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

image.webp

核心概念:理解偏差与方差的博弈

在机器学习中,偏差(Bias) 方差(Variance)是衡量模型表现的两个关键指标。简单来说:

  • 偏差 反映了模型预测值与真实值之间的差距。高偏差意味着模型对数据的拟合不足(欠拟合)。
  • 方差 反映了模型对训练数据微小变化的敏感程度。高方差意味着模型过度拟合了训练数据(过拟合)。

过拟合 vs 欠拟合

  • 过拟合(高方差低偏差):模型在训练集上表现很好,但在验证集上表现差。这通常是因为模型过于复杂,记住了训练数据的噪声而非真实模式。

  • 欠拟合(低方差高偏差):模型在训练集和验证集上都表现不佳。这通常是因为模型过于简单,无法捕捉数据中的复杂关系。

可视化理解

我们可以通过训练误差和验证误差曲线来直观理解这两种情况:

import matplotlib.pyplot as plt
import numpy as np

# 模拟训练误差和验证误差
model_complexity = np.linspace(1, 10, 10)
train_error = np.array([0.8, 0.6, 0.4, 0.3, 0.25, 0.2, 0.18, 0.16, 0.15, 0.14])
val_error = np.array([0.85, 0.7, 0.5, 0.4, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6])

plt.figure(figsize=(10, 6))
plt.plot(model_complexity, train_error, label='Training Error')
plt.plot(model_complexity, val_error, label='Validation Error')
plt.xlabel('Model Complexity')
plt.ylabel('Error')
plt.title('Bias-Variance Tradeoff')
plt.legend()
plt.show()

机器学习模型调优实战:从图解过拟合与欠拟合到高方差低偏差调节

诊断方法:学习曲线与验证曲线

学习曲线(Learning Curve)

学习曲线展示了随着训练样本数量的增加,模型在训练集和验证集上的表现变化。

from sklearn.model_selection import learning_curve
from sklearn.linear_model import LogisticRegression

# 生成学习曲线
train_sizes, train_scores, val_scores = learning_curve(LogisticRegression(), X, y, cv=5, train_sizes=np.linspace(0.1, 1.0, 10)
)

# 计算平均值和标准差
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
val_scores_mean = np.mean(val_scores, axis=1)
val_scores_std = np.std(val_scores, axis=1)

# 绘制学习曲线
plt.figure(figsize=(10, 6))
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.1, color='r')
plt.fill_between(train_sizes, val_scores_mean - val_scores_std,
                 val_scores_mean + val_scores_std, alpha=0.1, color='g')
plt.plot(train_sizes, train_scores_mean, 'o-', color='r', label='Training score')
plt.plot(train_sizes, val_scores_mean, 'o-', color='g', label='Validation score')
plt.xlabel('Training examples')
plt.ylabel('Score')
plt.legend(loc='best')
plt.show()

验证曲线(Validation Curve)

验证曲线展示了随着某个超参数(如正则化强度)的变化,模型在训练集和验证集上的表现变化。

from sklearn.model_selection import validation_curve

param_range = np.logspace(-6, 6, 13)

train_scores, val_scores = validation_curve(LogisticRegression(), X, y, param_name='C', param_range=param_range, cv=5
)

# 计算平均值和标准差
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
val_scores_mean = np.mean(val_scores, axis=1)
val_scores_std = np.std(val_scores, axis=1)

# 绘制验证曲线
plt.figure(figsize=(10, 6))
plt.semilogx(param_range, train_scores_mean, label='Training score', color='r')
plt.semilogx(param_range, val_scores_mean, label='Validation score', color='g')
plt.fill_between(param_range, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.2, color='r')
plt.fill_between(param_range, val_scores_mean - val_scores_std,
                 val_scores_mean + val_scores_std, alpha=0.2, color='g')
plt.xlabel('Regularization strength (C)')
plt.ylabel('Score')
plt.legend(loc='best')
plt.show()

解决方案:针对过拟合和欠拟合的调节方法

针对过拟合的解决方案

1. L1/L2 正则化

from sklearn.linear_model import LogisticRegression

# L1 正则化
model_l1 = LogisticRegression(penalty='l1', solver='liblinear', C=0.1)
model_l1.fit(X_train, y_train)

# L2 正则化
model_l2 = LogisticRegression(penalty='l2', C=0.1)
model_l2.fit(X_train, y_train)

2. Dropout 实现(PyTorch 版)

import torch
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 512)
        self.dropout = nn.Dropout(0.5)  # 50% 的 dropout 率
        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

3. Early Stopping

from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import accuracy_score

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

best_score = 0
best_model = None
patience = 5
no_improvement = 0

for epoch in range(100):
    model = SGDClassifier(loss='log', max_iter=1, warm_start=True)
    if epoch == 0:
        model.fit(X_train, y_train)
    else:
        model.fit(X_train, y_train, coef_init=model.coef_, intercept_init=model.intercept_)

    val_score = accuracy_score(y_val, model.predict(X_val))

    if val_score > best_score:
        best_score = val_score
        best_model = model
        no_improvement = 0
    else:
        no_improvement += 1

    if no_improvement >= patience:
        print(f'Early stopping at epoch {epoch}')
        break

针对欠拟合的解决方案

1. 特征工程策略

  • 添加多项式特征
  • 特征交叉
  • 分箱(Binning)
  • 使用领域知识创建新特征
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)
X_poly = poly.fit_transform(X)

2. 提升模型复杂度

  • 增加神经网络的层数或神经元数量
  • 使用更复杂的模型(如从线性回归切换到随机森林)
  • 减少正则化强度

避坑指南

数据泄露常见场景

  1. 在特征工程阶段使用了全部数据(包括验证集和测试集)进行标准化
  2. 在特征选择阶段使用了测试集信息
  3. 时间序列数据中使用了未来信息进行预测

交叉验证的 k 值选择原则

  • 小数据集(<1k 样本):使用较大的 k 值(如 10 折)
  • 大数据集(>100k 样本):使用较小的 k 值(如 3 - 5 折)
  • 时间序列数据:使用时间序列交叉验证(TimeSeriesSplit)

正则化系数 λ 的网格搜索技巧

from sklearn.model_selection import GridSearchCV

param_grid = {'C': np.logspace(-4, 4, 20)}
grid = GridSearchCV(LogisticRegression(), param_grid, cv=5)
grid.fit(X_train, y_train)

print(f'Best C: {grid.best_params_["C"]}')

性能考量

不同解决方案的计算开销和内存占用比较:

方法 计算开销 内存占用 适用场景
L1 正则化 特征选择重要时
L2 正则化 一般情况
Dropout 神经网络
Early Stopping 训练时间长的模型
特征工程 欠拟合情况
复杂度提升 欠拟合情况

启发式问题

  1. 如果增大 Batch Size 会对偏差方差产生什么影响?
  2. 如何确定最优的正则化强度?有哪些自动化方法可以辅助?
  3. 在深度学习模型中,除了 Dropout,还有哪些技术可以防止过拟合?
正文完
 0
评论(没有评论)