交叉验证基础
交叉验证是评估模型性能的核心方法,可以避免单次划分训练/测试集带来的随机性。
1. K-fold交叉验证
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X, y = data.data, data.target
model = LogisticRegression(max_iter=1000)
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')
print(f'5折交叉验证得分: {scores}')
print(f'平均得分: {scores.mean():.4f} ± {scores.std():.4f}')
2. Stratified K-fold(针对不平衡数据)
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='f1')
print(f'Stratified K-fold F1得分: {scores}')
print(f'平均F1: {scores.mean():.4f}')
✅ 何时使用Stratified K-fold?当目标变量类别不平衡时(如欺诈检测中欺诈样本占比<1%),必须使用Stratified K-fold确保每折都包含足够的少数类样本。
3. TimeSeriesSplit(时序数据)
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
print(f'Train: {len(X_train)}, Test: {len(X_test)}')
⚠️ 时序数据不能随机划分!使用KFold随机划分会导致数据泄露(未来数据出现在训练集中)。必须使用TimeSeriesSplit按时间顺序划分。
GridSearchCV:穷尽搜索
GridSearchCV是最常用的调参方法,遍历所有参数组合找到最优解。
基本用法
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
param_grid = {
'C': [0.1, 1, 10, 100],
'gamma': [1, 0.1, 0.01, 0.001],
'kernel': ['rbf', 'linear']
}
grid = GridSearchCV(SVC(), param_grid, refit=True, verbose=2, cv=5)
grid.fit(X_train, y_train)
print(f'最佳参数: {grid.best_params_}')
print(f'最佳得分: {grid.best_score_:.4f}')
best_model = grid.best_estimator_
y_pred = best_model.predict(X_test)
多分类问题的评分指标
grid = GridSearchCV(
SVC(),
param_grid,
refit=True,
verbose=2,
cv=5,
scoring=['accuracy', 'f1_macro', 'precision_macro', 'recall_macro'],
refit='f1_macro'
)
grid.fit(X_train, y_train)
print(f'所有评分指标结果:\n{grid.cv_results_["mean_test_f1_macro"]}')
并行化加速
grid = GridSearchCV(
SVC(),
param_grid,
refit=True,
cv=5,
n_jobs=-1,
verbose=2
)
✅ n_jobs=-1:使用所有可用的CPU核心进行并行搜索,可以大幅缩短调参时间。
RandomizedSearchCV:随机搜索
当参数空间很大时,随机搜索比穷尽搜索更高效。
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
param_dist = {
'n_estimators': randint(100, 500),
'max_depth': randint(3, 15),
'min_samples_split': randint(2, 20),
'min_samples_leaf': randint(1, 10),
'learning_rate': uniform(0.01, 0.3)
}
random_search = RandomizedSearchCV(
XGBClassifier(),
param_distributions=param_dist,
n_iter=100,
cv=5,
scoring='accuracy',
n_jobs=-1,
random_state=42,
verbose=2
)
random_search.fit(X_train, y_train)
print(f'最佳参数: {random_search.best_params_}')
print(f'最佳得分: {random_search.best_score_:.4f}')
✅ GridSearch vs RandomizedSearch:
- 参数空间小(<20种组合):用GridSearch,保证找到最优解
- 参数空间大(>20种组合):用RandomizedSearch,随机采样更高效
- 连续参数:RandomizedSearch可以在区间内采样,GridSearch只能枚举离散值
- 参数空间小(<20种组合):用GridSearch,保证找到最优解
- 参数空间大(>20种组合):用RandomizedSearch,随机采样更高效
- 连续参数:RandomizedSearch可以在区间内采样,GridSearch只能枚举离散值
BayesianOptimization:贝叶斯优化
贝叶斯优化是更先进的调参方法,基于之前的搜索结果智能选择下一个参数组合。
from bayes_opt import BayesianOptimization
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
def rf_cv(n_estimators, max_depth, min_samples_split):
model = RandomForestClassifier(
n_estimators=int(n_estimators),
max_depth=int(max_depth),
min_samples_split=int(min_samples_split),
random_state=42
)
score = cross_val_score(model, X_train, y_train, cv=5, scoring='f1').mean()
return score
pbounds = {
'n_estimators': (100, 500),
'max_depth': (3, 20),
'min_samples_split': (2, 20)
}
optimizer = BayesianOptimization(
f=rf_cv,
pbounds=pbounds,
random_state=42,
verbose=2
)
optimizer.maximize(init_points=5, n_iter=25)
print(f'最佳参数: {optimizer.max["params"]}')
print(f'最佳得分: {optimizer.max["target"]:.4f}')
✅ 贝叶斯优化的优势:在参数空间较大时,贝叶斯优化通常比随机搜索更快找到最优解,因为它会"学习"参数与性能的关系。
Pipeline与调参
Pipeline可以将预处理和模型训练串联起来,避免数据泄露。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.linear_model import Ridge
pipeline = Pipeline([
('scaler', StandardScaler()),
('poly', PolynomialFeatures(degree=2)),
('model', Ridge())
])
param_grid = {
'poly__degree': [1, 2, 3],
'model__alpha': [0.1, 1, 10, 100]
}
grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='neg_mean_squared_error')
grid.fit(X_train, y_train)
print(f'最佳参数: {grid.best_params_}')
⚠️ 为什么需要Pipeline?如果不使用Pipeline,在GridSearchCV中会对整个数据集进行标准化,然后再划分训练/测试集,这会导致测试集的数据泄露到训练过程中。
常见调参陷阱
陷阱一:数据泄露
# ❌ 错误:先标准化再划分
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y)
# ✅ 正确:在Pipeline中处理,或在每个fold中分别标准化
pipeline = Pipeline([
('scaler', StandardScaler()),
('model', LogisticRegression())
])
陷阱二:过度调参(Overfitting to the validation set)
调参过程本身也可能导致过拟合,特别是当参数空间很大时。
# ✅ 使用嵌套交叉验证评估模型
from sklearn.model_selection import cross_val_score
inner_cv = KFold(n_splits=5, shuffle=True, random_state=42)
outer_cv = KFold(n_splits=5, shuffle=True, random_state=42)
grid = GridSearchCV(model, param_grid, cv=inner_cv)
scores = cross_val_score(grid, X, y, cv=outer_cv)
print(f'嵌套交叉验证得分: {scores}')
print(f'平均得分: {scores.mean():.4f}')
陷阱三:忽略类别不平衡
# ✅ 在GridSearch中使用class_weight
param_grid = {
'C': [0.1, 1, 10],
'class_weight': ['balanced', None]
}
grid = GridSearchCV(LogisticRegression(), param_grid, cv=5, scoring='f1')
实用技巧
1. 可视化调参结果
import matplotlib.pyplot as plt
results = pd.DataFrame(grid.cv_results_)
plt.figure(figsize=(12, 6))
for kernel in ['rbf', 'linear']:
subset = results[results['param_kernel'] == kernel]
plt.plot(subset['param_C'], subset['mean_test_score'],
marker='o', label=f'kernel={kernel}')
plt.xlabel('C')
plt.ylabel('Mean Test Score')
plt.legend()
plt.grid(True)
plt.show()
2. 提前终止(Early Stopping)
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier(
n_estimators=1000,
learning_rate=0.01,
validation_fraction=0.1,
n_iter_no_change=5,
tol=0.0001
)
model.fit(X_train, y_train)
3. 模型集成
from sklearn.ensemble import VotingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
estimators = [
('rf', RandomForestClassifier()),
('dt', DecisionTreeClassifier()),
('svc', SVC(probability=True))
]
voting_clf = VotingClassifier(estimators=estimators, voting='soft')
voting_clf.fit(X_train, y_train)
调参流程总结
一个完整的调参流程应该是这样的:
- 选择模型:从简单模型开始(如逻辑回归),建立基准
- 定义参数空间:根据领域知识确定参数范围
- 选择搜索方法:GridSearch(小空间)或RandomizedSearch(大空间)
- 设置交叉验证:根据数据类型选择KFold/StratifiedKFold/TimeSeriesSplit
- 选择评分指标:根据业务需求选择合适的指标
- 评估最终模型:使用嵌套交叉验证或独立测试集验证
记住:调参不是一次性任务,而是迭代过程。通常需要先进行粗粒度搜索找到大致范围,再进行细粒度搜索精确定位最优参数。