共计 4787 个字符,预计需要花费 12 分钟才能阅读完成。
核心概念:理解偏差与方差的博弈
在机器学习中,偏差(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. 提升模型复杂度
- 增加神经网络的层数或神经元数量
- 使用更复杂的模型(如从线性回归切换到随机森林)
- 减少正则化强度
避坑指南
数据泄露常见场景
- 在特征工程阶段使用了全部数据(包括验证集和测试集)进行标准化
- 在特征选择阶段使用了测试集信息
- 时间序列数据中使用了未来信息进行预测
交叉验证的 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 | 低 | 低 | 训练时间长的模型 |
| 特征工程 | 高 | 高 | 欠拟合情况 |
| 复杂度提升 | 高 | 高 | 欠拟合情况 |
启发式问题
- 如果增大 Batch Size 会对偏差方差产生什么影响?
- 如何确定最优的正则化强度?有哪些自动化方法可以辅助?
- 在深度学习模型中,除了 Dropout,还有哪些技术可以防止过拟合?
正文完
发表至: 未分类
近三天内
