构建机器学习模型时,训练数据上的优秀表现并不等同于真实场景中的可靠性。理解偏差(Bias)、方差(Variance)以及过拟合/欠拟合的成因,是将模型从"能用"提升到"好用"的关键。
欠拟合与过拟合
欠拟合(Underfitting)指模型过于简单,无法捕捉数据中的规律,在训练集和测试集上表现都差。过拟合(Overfitting)指模型过于复杂,记住了训练数据中的噪声和特例,在训练集上表现极好但测试集上表现差。
诊断 checklist
- 训练误差高、测试误差也高 → 欠拟合(模型太简单或特征不足)。
- 训练误差很低、测试误差显著更高 → 过拟合(模型太复杂或数据太少)。
- 训练误差和测试误差都低且接近 → 拟合适度(理想状态)。
偏差-方差权衡
模型的泛化误差可以分解为三个部分:
\mathbb{E}[(y - \hat{f}(x))^2] = \text{Bias}^2[\hat{f}(x)] + \text{Var}[\hat{f}(x)] + \sigma^2
- 偏差:模型期望预测与真实值的差异,反映模型对真实关系的假设能力。高偏差对应欠拟合。
- 方差:模型对训练数据波动的敏感程度。高方差对应过拟合。
- 不可约误差 \sigma^2:数据本身的噪声,无法通过模型消除。
偏差与方差通常此消彼长:增加模型复杂度会降低偏差但增加方差;降低复杂度则相反。优化的目标是在两者之间找到最佳平衡点。
学习曲线
学习曲线(Learning Curve)展示模型在训练集和验证集上的性能随训练样本量(或迭代次数)的变化。它是诊断过拟合/欠拟合的有力工具:
- 两条曲线最终都收敛在较高误差 → 欠拟合(高偏差)。
- 训练曲线收敛于低误差,验证曲线收敛于高误差,且差距大 → 过拟合(高方差)。
- 增加数据量对缩小差距有帮助 → 过拟合可通过更多数据缓解。
正则化
正则化通过在损失函数中加入惩罚项,限制模型参数的大小,从而抑制过拟合:
- L1 正则化(Lasso):惩罚项为 \lambda \sum |w_i|,倾向于产生稀疏权重,可用于特征选择。
- L2 正则化(Ridge):惩罚项为 \lambda \sum w_i^2,使权重平滑减小但通常不为零,数值稳定性更好。
L_{\text{reg}} = L_{\text{original}} + \lambda \sum_{i} w_i^2
在神经网络中,L2 正则化也常被称为"权重衰减"(Weight Decay)。正则化强度 \lambda 越大,模型越简单。
Dropout 与早停法
Dropout 在训练过程中以概率 p 随机丢弃隐藏层神经元,迫使网络不依赖任何单个神经元,显著降低共适应(co-adaptation)风险。测试时所有神经元参与,但输出按 p 缩放。
早停法(Early Stopping) 监控验证集性能,当验证误差连续多轮不再下降时停止训练,避免在训练集上过度优化。它简单有效且几乎无额外计算开销。
超参数调优
超参数是在训练前设定的参数,不能从数据中学习。主要调优策略:
- 网格搜索(Grid Search):在预定义的超参数空间中穷举所有组合,计算成本高但覆盖全面。
- 随机搜索(Random Search):在超参数空间中随机采样,研究表明在有限预算下通常优于网格搜索。
- 贝叶斯优化:基于先验评估构建代理模型,智能选择下一轮评估点,效率更高。
模型集成策略
集成学习通过组合多个模型降低方差或偏差:
- Bagging:并行训练多个模型(如随机森林),降低方差。
- Boosting:串行训练,修正前序模型的错误,降低偏差。
- Stacking:用元学习器组合多个基模型的预测,发挥各自优势。
偏差与方差分解示例
假设真实关系为 y = f(x) + \epsilon,其中 \epsilon \sim N(0, 4)。用 100 组不同的训练集训练同一个复杂模型,在某测试点 x_0 上得到预测值分布:均值 \bar{\hat{y}} = 8.2,方差 Var(\hat{y}) = 3.5,真实值 f(x_0) = 10。
计算
偏差 = \bar{\hat{y}} - f(x_0) = 8.2 - 10 = -1.8,偏差² = 3.24
方差 = 3.5
不可约误差 = \sigma^2 = 4
总期望误差 ≈ 3.24 + 3.5 + 4 = 10.74
此例中误差主要由不可约误差和方差贡献。若换用更简单的模型后方差降至 0.8 但偏差²升至 6.0,则总误差 ≈ 10.8,差异不大;但如果能将偏差²控制在 1.0 同时方差为 1.5,总误差可降至 6.5。
学习曲线分析
对于高偏差(欠拟合)模型,增加训练数据对改善验证性能帮助有限,因为模型根本没有足够容量学习规律。此时应尝试更复杂的模型或更好的特征。
对于高方差(过拟合)模型,增加训练数据通常能有效缩小训练与验证误差之间的差距。同时正则化也是直接的应对手段。
工程应用场景
- 模型选择:通过学习曲线和交叉验证指标,在候选模型中选择偏差-方差权衡最优者。
- 超参优化自动化:使用 Optuna、Ray Tune 等框架自动搜索超参数空间,减少人工试错。
- 模型压缩:对过参数化的大模型进行剪枝、量化、知识蒸馏,在保持性能的同时降低部署成本。
- 迁移学习:利用预训练模型的低偏差初始化,配合少量数据微调,快速适配新任务。
绘制学习曲线
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
X, y = load_iris(return_X_y=True)
model_high = DecisionTreeClassifier(max_depth=None, random_state=42)
model_low = DecisionTreeClassifier(max_depth=2, random_state=42)
for model, title in [(model_high, 'Overfitting (deep tree)'),
(model_low, 'Underfitting (shallow tree)')]:
train_sizes, train_scores, val_scores = learning_curve(
model, X, y, cv=5,
train_sizes=np.linspace(0.1, 1.0, 10),
scoring='accuracy', random_state=42)
plt.plot(train_sizes, train_scores.mean(axis=1), 'o-', label='Train')
plt.plot(train_sizes, val_scores.mean(axis=1), 's-', label='Validation')
plt.title(title)
plt.xlabel('Training Set Size')
plt.ylabel('Accuracy')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
GridSearchCV 网格搜索
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
param_grid = {
'C': [0.1, 1, 10, 100],
'kernel': ['rbf', 'linear'],
'gamma': ['scale', 'auto', 0.001, 0.01]
}
grid = GridSearchCV(
SVC(random_state=42),
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1
)
grid.fit(X, y)
print(f"Best params: {grid.best_params_}")
print(f"Best CV score: {grid.best_score_:.3f}")
RandomizedSearchCV 随机搜索
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import loguniform
param_distributions = {
'C': loguniform(1e-3, 1e3),
'gamma': loguniform(1e-4, 1e0),
'kernel': ['rbf', 'poly', 'linear']
}
random_search = RandomizedSearchCV(
SVC(random_state=42),
param_distributions,
n_iter=20,
cv=5,
scoring='accuracy',
random_state=42,
n_jobs=-1
)
random_search.fit(X, y)
print(f"Best params: {random_search.best_params_}")
print(f"Best CV score: {random_search.best_score_:.3f}")
验证曲线分析正则化效果
from sklearn.model_selection import validation_curve
from sklearn.linear_model import Ridge
param_range = np.logspace(-3, 3, 10)
train_scores, val_scores = validation_curve(
Ridge(), X, y,
param_name='alpha',
param_range=param_range,
cv=5, scoring='neg_mean_squared_error'
)
plt.semilogx(param_range, -train_scores.mean(axis=1), 'o-', label='Train')
plt.semilogx(param_range, -val_scores.mean(axis=1), 's-', label='Validation')
plt.xlabel('Alpha (regularization strength)')
plt.ylabel('MSE')
plt.title('Validation Curve for Ridge Regression')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()