学习笔记
2026-07-11 · 约10分钟阅读

Scikit-learn模型调参实战:GridSearch与交叉验证

Python Scikit-learn 机器学习

交叉验证基础

交叉验证是评估模型性能的核心方法,可以避免单次划分训练/测试集带来的随机性。

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只能枚举离散值

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)

调参流程总结

一个完整的调参流程应该是这样的:

  1. 选择模型:从简单模型开始(如逻辑回归),建立基准
  2. 定义参数空间:根据领域知识确定参数范围
  3. 选择搜索方法:GridSearch(小空间)或RandomizedSearch(大空间)
  4. 设置交叉验证:根据数据类型选择KFold/StratifiedKFold/TimeSeriesSplit
  5. 选择评分指标:根据业务需求选择合适的指标
  6. 评估最终模型:使用嵌套交叉验证或独立测试集验证

记住:调参不是一次性任务,而是迭代过程。通常需要先进行粗粒度搜索找到大致范围,再进行细粒度搜索精确定位最优参数。

🤖
AI助手
ONLINE