行莫
行莫
发布于 2025-11-24 / 5 阅读
0
0

交叉验证与网格搜索详解

交叉验证与网格搜索详解

引言

想象一下两种不同的考试方式:

  • 传统方式:只用一套题训练,用同一套题测试,容易"死记硬背"
  • 交叉验证:用多套题训练和测试,轮流使用,更真实地评估能力

想象一下两种不同的调参方式:

  • 手动调参:一个一个试,费时费力,容易遗漏最优解
  • 网格搜索:系统性地尝试所有组合,自动找到最优参数

交叉验证网格搜索是机器学习中两个非常重要的技术,它们帮助我们:

  • 更准确地评估模型性能
  • 更高效地找到最优参数
  • 避免过拟合,提高泛化能力

本文将用生动的类比、详细的数学原理和完整的代码示例,带你深入理解这两个核心概念。


第一部分:交叉验证(Cross-Validation)

什么是交叉验证?

**交叉验证(Cross-Validation)**是一种评估模型性能的方法,通过将数据集分成多个子集,轮流使用不同的子集作为训练集和测试集,来评估模型的泛化能力。

类比理解:

  • 就像用多套试卷测试学生,而不是只用一套
  • 就像轮流使用不同的数据来训练和测试模型
  • 就像多次"考试",取平均成绩更可靠

为什么需要交叉验证?

1. 传统方法的局限性

传统方法:简单划分

# 传统方法:简单划分训练集和测试集
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 划分数据集(70% 训练,30% 测试)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# 训练模型
model = LogisticRegression()
model.fit(X_train, y_train)

# 评估模型
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"准确率: {accuracy}")

问题:

  • 只评估一次,结果可能不稳定
  • 如果测试集恰好"简单"或"困难",评估不准确
  • 无法充分利用数据

类比理解:

  • 就像只考一次试,可能因为题目恰好简单或难而评估不准确
  • 就像只用一部分数据,浪费了其他数据

2. 交叉验证的优势

交叉验证方法:多次评估

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

# 使用交叉验证评估模型
model = LogisticRegression()
scores = cross_val_score(model, X, y, cv=5)  # 5折交叉验证

print(f"每次得分: {scores}")
print(f"平均得分: {scores.mean():.4f}")
print(f"标准差: {scores.std():.4f}")

优势:

  • 多次评估,结果更稳定
  • 充分利用所有数据
  • 更准确地评估模型性能

类比理解:

  • 就像多次考试取平均,评估更准确
  • 就像所有数据都参与训练和测试,不浪费

K折交叉验证(K-Fold Cross-Validation)

原理

K折交叉验证是最常用的交叉验证方法:

  1. 将数据集分成 K 个相等的子集(折)
  2. 轮流使用其中 K-1 个作为训练集,1 个作为测试集
  3. 重复 K 次,每次使用不同的折作为测试集
  4. 计算 K 次评估结果的平均值

数学表示:

给定数据集 $D = {(x_1, y_1), (x_2, y_2), ..., (x_n, y_n)}$,将其分成 K 个折:

$$
D = D_1 \cup D_2 \cup ... \cup D_K
$$

对于第 $k$ 次迭代:

  • 训练集:$D_{train}^{(k)} = D \setminus D_k$
  • 测试集:$D_{test}^{(k)} = D_k$

评估指标(如准确率):

$$
Score_k = \text{Metric}(Model(D_{train}^{(k)}), D_{test}^{(k)})
$$

最终得分:

$$
Score_{CV} = \frac{1}{K} \sum_{k=1}^{K} Score_k
$$

类比理解:

  • $K$:就像试卷的套数
  • $D_k$:就像第 $k$ 套试卷
  • $Score_k$:就像第 $k$ 次考试的成绩
  • $Score_{CV}$:就像所有考试的平均成绩

可视化理解

数据集: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

5折交叉验证:

第1折: 训练集=[2,3,4,5,6,7,8,9,10] 测试集=[1]
第2折: 训练集=[1,3,4,5,6,7,8,9,10] 测试集=[2]
第3折: 训练集=[1,2,4,5,6,7,8,9,10] 测试集=[3]
第4折: 训练集=[1,2,3,5,6,7,8,9,10] 测试集=[4]
第5折: 训练集=[1,2,3,4,6,7,8,9,10] 测试集=[5]

代码示例

import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 创建模型
model = LogisticRegression(max_iter=1000)

# 方法1:使用 cross_val_score(最简单)
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
print("方法1 - 使用 cross_val_score:")
print(f"每次得分: {scores}")
print(f"平均得分: {scores.mean():.4f} ± {scores.std():.4f}")

# 方法2:手动实现 K折交叉验证(更灵活)
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = []

for train_idx, test_idx in kfold.split(X):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    
    # 训练模型
    model.fit(X_train, y_train)
    
    # 评估模型
    y_pred = model.predict(X_test)
    score = accuracy_score(y_test, y_pred)
    cv_scores.append(score)
    print(f"折 {len(cv_scores)}: 准确率 = {score:.4f}")

print(f"\n平均准确率: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}")

其他交叉验证方法

1. 留一交叉验证(Leave-One-Out Cross-Validation, LOOCV)

**原理:**每次只留一个样本作为测试集,其余作为训练集。

特点:

  • K = 样本数
  • 计算成本高
  • 适合小数据集
from sklearn.model_selection import LeaveOneOut

loo = LeaveOneOut()
scores = cross_val_score(model, X, y, cv=loo)
print(f"LOOCV 平均得分: {scores.mean():.4f}")

2. 分层K折交叉验证(Stratified K-Fold)

**原理:**保持每个折中各类别的比例与原始数据集相同。

**适用场景:**分类问题,特别是类别不平衡的情况。

from sklearn.model_selection import StratifiedKFold

skfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skfold, scoring='accuracy')
print(f"分层K折交叉验证得分: {scores.mean():.4f} ± {scores.std():.4f}")

3. 时间序列交叉验证(Time Series Split)

**原理:**按时间顺序划分,前面的数据训练,后面的数据测试。

**适用场景:**时间序列数据。

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
    print(f"训练集大小: {len(train_idx)}, 测试集大小: {len(test_idx)}")

交叉验证的注意事项

1. 数据泄露(Data Leakage)

**问题:**在划分数据前进行预处理(如标准化),会导致测试集信息泄露到训练集。

错误示例:

# ❌ 错误:先标准化,再划分
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 使用了全部数据的信息
X_train, X_test = train_test_split(X_scaled, test_size=0.3)

正确示例:

# ✅ 正确:先划分,再标准化
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 只用训练集拟合
X_test_scaled = scaler.transform(X_test)  # 用训练集的参数转换测试集

2. 交叉验证中的预处理

在交叉验证中,每次折叠都需要独立进行预处理:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score

# 使用 Pipeline 自动处理预处理
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression())
])

# 交叉验证会自动处理预处理
scores = cross_val_score(pipeline, X, y, cv=5)

什么是网格搜索?

**网格搜索(Grid Search)**是一种超参数优化方法,通过系统地遍历所有可能的参数组合,找到使模型性能最优的参数。

类比理解:

  • 就像系统地尝试所有调料组合,找到最佳配方
  • 就像系统地尝试所有参数组合,找到最佳模型
  • 就像在参数空间中"网格状"地搜索最优解

为什么需要网格搜索?

1. 手动调参的问题

手动调参示例:

# 手动尝试不同的参数
best_score = 0
best_params = None

for C in [0.1, 1, 10]:
    for penalty in ['l1', 'l2']:
        model = LogisticRegression(C=C, penalty=penalty, solver='liblinear')
        score = cross_val_score(model, X, y, cv=5).mean()
        
        if score > best_score:
            best_score = score
            best_params = {'C': C, 'penalty': penalty}
        
        print(f"C={C}, penalty={penalty}, score={score:.4f}")

print(f"\n最佳参数: {best_params}")
print(f"最佳得分: {best_score:.4f}")

问题:

  • 需要手动编写循环
  • 容易遗漏某些组合
  • 无法并行计算
  • 代码冗长

2. 网格搜索的优势

网格搜索示例:

from sklearn.model_selection import GridSearchCV

# 定义参数网格
param_grid = {
    'C': [0.1, 1, 10, 100],
    'penalty': ['l1', 'l2'],
    'solver': ['liblinear', 'lbfgs']
}

# 创建模型
model = LogisticRegression(max_iter=1000)

# 网格搜索
grid_search = GridSearchCV(
    model, 
    param_grid, 
    cv=5,  # 5折交叉验证
    scoring='accuracy',
    n_jobs=-1  # 并行计算
)

# 执行搜索
grid_search.fit(X, y)

# 查看结果
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳得分: {grid_search.best_score_:.4f}")

优势:

  • 自动遍历所有组合
  • 结合交叉验证评估
  • 支持并行计算
  • 代码简洁

网格搜索的原理

参数空间

参数网格定义:

param_grid = {
    'C': [0.1, 1, 10],
    'penalty': ['l1', 'l2']
}

参数组合:

  • (C=0.1, penalty='l1')
  • (C=0.1, penalty='l2')
  • (C=1, penalty='l1')
  • (C=1, penalty='l2')
  • (C=10, penalty='l1')
  • (C=10, penalty='l2')

总共 3 × 2 = 6 种组合。

数学表示:

给定参数网格:
$$
\Theta = {\theta_1, \theta_2, ..., \theta_n}
$$

其中每个 $\theta_i$ 是一个参数组合:
$$
\theta_i = (p_1^{(i)}, p_2^{(i)}, ..., p_m^{(i)})
$$

网格搜索的目标是找到:
$$
\theta^* = \arg\max_{\theta \in \Theta} Score_{CV}(\theta)
$$

其中 $Score_{CV}(\theta)$ 是使用参数 $\theta$ 的交叉验证得分。

搜索过程

对于每个参数组合 θ:
    1. 使用交叉验证评估模型性能
    2. 记录得分
    
找到得分最高的参数组合
返回最佳参数和最佳得分

GridSearchCV 详解

基本用法

from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
from sklearn.datasets import load_iris

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 定义参数网格
param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': [0.001, 0.01, 0.1, 1],
    'kernel': ['rbf', 'linear']
}

# 创建模型
svm = SVC()

# 网格搜索
grid_search = GridSearchCV(
    estimator=svm,
    param_grid=param_grid,
    cv=5,  # 5折交叉验证
    scoring='accuracy',  # 评估指标
    n_jobs=-1,  # 使用所有CPU核心
    verbose=1  # 显示进度
)

# 执行搜索
grid_search.fit(X, y)

# 查看结果
print("最佳参数:", grid_search.best_params_)
print("最佳交叉验证得分:", grid_search.best_score_)
print("最佳模型:", grid_search.best_estimator_)

重要参数说明

GridSearchCV 的主要参数:

  1. estimator: 要优化的模型
  2. param_grid: 参数网格(字典或字典列表)
  3. cv: 交叉验证折数或交叉验证对象
  4. scoring: 评估指标('accuracy', 'f1', 'roc_auc' 等)
  5. n_jobs: 并行任务数(-1 表示使用所有核心)
  6. verbose: 详细程度(0-3)
  7. refit: 是否用最佳参数重新训练整个数据集(默认 True)

查看详细结果

import pandas as pd

# 将结果转换为 DataFrame
results_df = pd.DataFrame(grid_search.cv_results_)

# 查看前几列
print(results_df[['params', 'mean_test_score', 'std_test_score', 'rank_test_score']].head(10))

# 查看最佳参数的所有信息
best_idx = grid_search.best_index_
print("\n最佳参数组合的详细信息:")
print(results_df.iloc[best_idx])

随机搜索是网格搜索的替代方法,随机采样参数组合而不是遍历所有组合。

适用场景:

  • 参数空间很大
  • 计算资源有限
  • 某些参数对性能影响较小
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform, randint

# 定义参数分布
param_distributions = {
    'C': uniform(0.1, 100),  # 均匀分布
    'gamma': uniform(0.001, 1),
    'kernel': ['rbf', 'linear']
}

# 随机搜索
random_search = RandomizedSearchCV(
    svm,
    param_distributions,
    n_iter=50,  # 随机尝试50次
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    random_state=42
)

random_search.fit(X, y)

print("最佳参数:", random_search.best_params_)
print("最佳得分:", random_search.best_score_)

网格搜索与交叉验证的结合

GridSearchCV 内部流程:

对于每个参数组合:
    使用交叉验证评估:
        第1折: 训练 → 测试 → 得分1
        第2折: 训练 → 测试 → 得分2
        ...
        第K折: 训练 → 测试 → 得分K
    
    计算平均得分 = (得分1 + 得分2 + ... + 得分K) / K

找到平均得分最高的参数组合

完整示例:

from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# 创建管道(包含预处理和模型)
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', RandomForestClassifier())
])

# 定义参数网格(注意:参数名前缀)
param_grid = {
    'scaler__with_mean': [True, False],
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [3, 5, 7, None],
    'classifier__min_samples_split': [2, 5, 10]
}

# 使用分层K折交叉验证
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# 网格搜索
grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=cv,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)

grid_search.fit(X, y)

print("最佳参数:", grid_search.best_params_)
print("最佳得分:", grid_search.best_score_)

第三部分:实际应用示例

示例1:分类问题 - 鸢尾花数据集

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

print("数据集形状:", X.shape)
print("类别数:", len(np.unique(y)))

# 示例1:SVM 网格搜索
print("\n=== SVM 网格搜索 ===")
svm_param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': [0.001, 0.01, 0.1, 1],
    'kernel': ['rbf', 'linear']
}

svm = SVC()
svm_grid = GridSearchCV(
    svm, svm_param_grid, cv=5, 
    scoring='accuracy', n_jobs=-1
)
svm_grid.fit(X, y)

print(f"最佳参数: {svm_grid.best_params_}")
print(f"最佳得分: {svm_grid.best_score_:.4f}")

# 示例2:随机森林网格搜索
print("\n=== 随机森林网格搜索 ===")
rf_param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [3, 5, 7, None],
    'min_samples_split': [2, 5, 10]
}

rf = RandomForestClassifier(random_state=42)
rf_grid = GridSearchCV(
    rf, rf_param_grid, cv=5,
    scoring='accuracy', n_jobs=-1
)
rf_grid.fit(X, y)

print(f"最佳参数: {rf_grid.best_params_}")
print(f"最佳得分: {rf_grid.best_score_:.4f}")

# 示例3:使用 Pipeline
print("\n=== 使用 Pipeline 的网格搜索 ===")
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('svm', SVC())
])

pipeline_param_grid = {
    'scaler__with_mean': [True, False],
    'svm__C': [0.1, 1, 10],
    'svm__gamma': [0.01, 0.1, 1],
    'svm__kernel': ['rbf', 'linear']
}

pipeline_grid = GridSearchCV(
    pipeline, pipeline_param_grid, cv=5,
    scoring='accuracy', n_jobs=-1
)
pipeline_grid.fit(X, y)

print(f"最佳参数: {pipeline_grid.best_params_}")
print(f"最佳得分: {pipeline_grid.best_score_:.4f}")

示例2:回归问题 - 波士顿房价

from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_squared_error, r2_score

# 加载数据
housing = fetch_california_housing()
X, y = housing.data, housing.target

print("数据集形状:", X.shape)

# 定义参数网格
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [3, 5, 7],
    'learning_rate': [0.01, 0.1, 0.2],
    'subsample': [0.8, 1.0]
}

# 创建模型
gbr = GradientBoostingRegressor(random_state=42)

# 网格搜索(回归问题使用负均方误差作为评分)
grid_search = GridSearchCV(
    gbr, param_grid, cv=5,
    scoring='neg_mean_squared_error',  # 回归问题使用负MSE
    n_jobs=-1, verbose=1
)

grid_search.fit(X, y)

print(f"\n最佳参数: {grid_search.best_params_}")
print(f"最佳得分 (负MSE): {grid_search.best_score_:.4f}")
print(f"最佳RMSE: {np.sqrt(-grid_search.best_score_):.4f}")

# 使用最佳模型预测
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X)
print(f"\nR²得分: {r2_score(y, y_pred):.4f}")

示例3:多指标评估

from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer, accuracy_score, f1_score, roc_auc_score
from sklearn.linear_model import LogisticRegression

# 定义多个评估指标
scoring = {
    'accuracy': 'accuracy',
    'f1': 'f1_macro',
    'roc_auc': 'roc_auc_ovr'
}

# 参数网格
param_grid = {
    'C': [0.1, 1, 10, 100],
    'penalty': ['l1', 'l2'],
    'solver': ['liblinear', 'lbfgs']
}

model = LogisticRegression(max_iter=1000)

# 多指标网格搜索
grid_search = GridSearchCV(
    model, param_grid, cv=5,
    scoring=scoring,
    refit='accuracy',  # 用accuracy重新训练最佳模型
    n_jobs=-1
)

grid_search.fit(X, y)

print("最佳参数:", grid_search.best_params_)
print("最佳准确率:", grid_search.best_score_)

# 查看所有指标的得分
print("\n所有指标的交叉验证得分:")
for metric in scoring.keys():
    scores = grid_search.cv_results_[f'mean_test_{metric}']
    best_idx = grid_search.best_index_
    print(f"{metric}: {scores[best_idx]:.4f}")

第四部分:最佳实践与注意事项

1. 数据预处理顺序

正确顺序:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV

# ✅ 正确:使用 Pipeline
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression())
])

param_grid = {
    'model__C': [0.1, 1, 10]
}

grid_search = GridSearchCV(pipeline, param_grid, cv=5)
grid_search.fit(X, y)

错误顺序:

# ❌ 错误:先预处理,再网格搜索
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 数据泄露!

grid_search = GridSearchCV(LogisticRegression(), param_grid, cv=5)
grid_search.fit(X_scaled, y)

2. 参数网格设计

原则:

  • 从粗到细:先大范围搜索,再精细调整
  • 考虑计算成本:参数组合数 = 各参数取值数的乘积
  • 使用对数尺度:对于某些参数(如 C),使用对数尺度更合理
import numpy as np

# 粗搜索
param_grid_coarse = {
    'C': [0.1, 1, 10, 100],
    'gamma': [0.001, 0.01, 0.1, 1]
}

# 细搜索(在最佳参数附近)
param_grid_fine = {
    'C': np.logspace(-1, 2, 10),  # 对数尺度
    'gamma': np.logspace(-3, 0, 10)
}

3. 交叉验证折数选择

建议:

  • 小数据集(<1000样本):使用 LOOCV 或 10折
  • 中等数据集(1000-10000样本):使用 5折或 10折
  • 大数据集(>10000样本):使用 3折或 5折
# 根据数据集大小选择折数
n_samples = len(X)
if n_samples < 100:
    cv = LeaveOneOut()
elif n_samples < 1000:
    cv = 10
else:
    cv = 5

4. 计算资源管理

并行计算:

# 使用所有CPU核心
grid_search = GridSearchCV(model, param_grid, n_jobs=-1)

# 限制CPU核心数
grid_search = GridSearchCV(model, param_grid, n_jobs=4)

# 不使用并行(调试时)
grid_search = GridSearchCV(model, param_grid, n_jobs=1)

内存管理:

# 对于大数据集,使用预计算
from sklearn.model_selection import PredefinedSplit

# 或者使用较小的参数网格
# 或者使用随机搜索代替网格搜索

5. 避免过拟合

**问题:**在网格搜索中使用交叉验证,但最终在测试集上表现不佳。

**原因:**可能是在整个数据集上进行了多次交叉验证,导致"信息泄露"。

**解决方案:**使用嵌套交叉验证或保留独立的测试集。

from sklearn.model_selection import cross_val_score

# 嵌套交叉验证
def nested_cv(X, y, model, param_grid, outer_cv=5, inner_cv=5):
    outer_scores = []
    
    outer_kfold = KFold(n_splits=outer_cv, shuffle=True, random_state=42)
    
    for train_idx, test_idx in outer_kfold.split(X):
        X_train, X_test = X[train_idx], X[test_idx]
        y_train, y_test = y[train_idx], y[test_idx]
        
        # 内层:网格搜索
        inner_grid = GridSearchCV(
            model, param_grid, cv=inner_cv, scoring='accuracy'
        )
        inner_grid.fit(X_train, y_train)
        
        # 外层:评估
        best_model = inner_grid.best_estimator_
        score = best_model.score(X_test, y_test)
        outer_scores.append(score)
    
    return np.mean(outer_scores), np.std(outer_scores)

# 使用嵌套交叉验证
mean_score, std_score = nested_cv(X, y, SVC(), svm_param_grid)
print(f"嵌套交叉验证得分: {mean_score:.4f} ± {std_score:.4f}")

第五部分:总结与扩展

核心概念总结

交叉验证

  • 目的:更准确地评估模型性能
  • 方法:将数据分成多折,轮流训练和测试
  • 优势:充分利用数据,评估更稳定
  • 常用方法:K折交叉验证、分层K折、留一交叉验证

网格搜索

  • 目的:找到最优超参数
  • 方法:系统地遍历所有参数组合
  • 优势:自动搜索,结合交叉验证
  • 替代方法:随机搜索、贝叶斯优化

完整工作流程

# 1. 导入库
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report

# 2. 加载和划分数据
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 3. 创建管道
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', RandomForestClassifier(random_state=42))
])

# 4. 定义参数网格
param_grid = {
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [3, 5, 7, None],
    'classifier__min_samples_split': [2, 5, 10]
}

# 5. 网格搜索
grid_search = GridSearchCV(
    pipeline, param_grid, cv=5,
    scoring='accuracy', n_jobs=-1, verbose=1
)

# 6. 训练和搜索
grid_search.fit(X_train, y_train)

# 7. 评估最佳模型
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)

print("最佳参数:", grid_search.best_params_)
print("最佳交叉验证得分:", grid_search.best_score_)
print("\n测试集性能:")
print(classification_report(y_test, y_pred))

扩展学习

1. 贝叶斯优化(Bayesian Optimization)

比网格搜索和随机搜索更高效的方法:

from skopt import BayesSearchCV
from skopt.space import Real, Categorical, Integer

# 定义参数空间
param_space = {
    'C': Real(0.1, 100, prior='log-uniform'),
    'gamma': Real(0.001, 1, prior='log-uniform'),
    'kernel': Categorical(['rbf', 'linear'])
}

# 贝叶斯优化
bayes_search = BayesSearchCV(
    SVC(), param_space, n_iter=50, cv=5,
    scoring='accuracy', n_jobs=-1
)

bayes_search.fit(X, y)
print("最佳参数:", bayes_search.best_params_)

2. 早停(Early Stopping)

对于某些算法(如梯度提升),可以使用早停来节省时间:

from sklearn.ensemble import GradientBoostingClassifier

param_grid = {
    'n_estimators': [100, 200, 300],
    'learning_rate': [0.01, 0.1, 0.2]
}

# 梯度提升支持早停
gbr = GradientBoostingClassifier(
    n_iter_no_change=10,  # 10轮无改善则停止
    validation_fraction=0.2  # 20%数据用于验证
)

3. 自定义评估指标

from sklearn.metrics import make_scorer

# 定义自定义评估函数
def custom_score(y_true, y_pred):
    # 你的自定义逻辑
    return accuracy_score(y_true, y_pred)

# 创建评估器
custom_scorer = make_scorer(custom_score)

# 在网格搜索中使用
grid_search = GridSearchCV(
    model, param_grid, cv=5,
    scoring=custom_scorer
)

常见问题解答

Q1: 交叉验证和网格搜索有什么区别?

A: 交叉验证是评估模型性能的方法,网格搜索是优化超参数的方法。网格搜索通常内部使用交叉验证来评估每个参数组合。

Q2: 应该使用多少折交叉验证?

A: 通常使用 5 折或 10 折。数据集很小时可以使用留一交叉验证,数据集很大时可以使用 3 折。

Q3: 网格搜索需要多长时间?

A: 时间 = 参数组合数 × 交叉验证折数 × 单次训练时间。可以通过并行计算(n_jobs=-1)加速。

Q4: 如何选择参数网格的范围?

A: 从粗到细:先大范围搜索找到大致范围,再在该范围内精细搜索。对于某些参数(如正则化系数),使用对数尺度更合理。

Q5: 网格搜索会过拟合吗?

A: 可能会。如果使用整个数据集进行网格搜索,然后在同一数据集上测试,可能导致过拟合。建议使用嵌套交叉验证或保留独立的测试集。


结语

交叉验证和网格搜索是机器学习中两个非常重要的技术:

  • 交叉验证帮助我们更准确地评估模型性能,避免因数据划分导致的评估偏差
  • 网格搜索帮助我们系统地找到最优超参数,提高模型性能

掌握这两个技术,能够让你:

  • 更自信地评估模型
  • 更高效地调参
  • 构建更好的机器学习模型

希望本文能够帮助你深入理解这两个概念,并在实际项目中灵活运用!


参考文献

  1. Scikit-learn 官方文档: https://scikit-learn.org/stable/
  2. Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning
  3. James, G., et al. (2013). An Introduction to Statistical Learning

评论