2.5 模型选择 (Model Selection) Scikit-learn 模型选择 (Model Selection) 详解 2.5.1 模型选择的重要性 在构建机器学习模型的过程中,我们通常会面临以下几个关键问题: 模型种类的选择: 面对各种各样的算法 (如线性回归、逻辑回归、决策树、支持向量机、神经网络等),我们应该选择哪种模型来解决当前的问题? 模型复杂度的控制: 即使选择了某种模型,例如决策树,我们也需要决定树的深度、叶节点的最小样本数等参数,以控制模型的复杂度。 超参数的优化: 大多数机器学习模型都包含超参数 (Hyperparameters),这些参数不是通过模型训练学习得到的,而是需要在训练前手动设置的。如何找到一组最佳的超参数组合,以最大化模型的性能?
在构建机器学习模型的过程中,我们通常会面临以下几个关键问题:
模型种类的选择: 面对各种各样的算法 (如线性回归、逻辑回归、决策树、支持向量机、神经网络等),我们应该选择哪种模型来解决当前的问题?
模型复杂度的控制: 即使选择了某种模型,例如决策树,我们也需要决定树的深度、叶节点的最小样本数等参数,以控制模型的复杂度。
超参数的优化: 大多数机器学习模型都包含超参数 (Hyperparameters),这些参数不是通过模型训练学习得到的,而是需要在训练前手动设置的。如何找到一组最佳的超参数组合,以最大化模型的性能?
模型选择正是为了解决这些问题而存在的。它的核心目标是从候选模型集合中,选择一个在未知数据上具有最佳泛化性能的模型。如果我们选择的模型在训练数据上表现很好,但在新数据上表现很差,那么就发生了过拟合 (Overfitting)。反之,如果模型在训练数据和新数据上都表现不佳,则发生了欠拟合 (Underfitting)。模型选择的目标是找到一个平衡点,既能充分学习训练数据中的模式,又能避免过度拟合,从而获得良好的泛化能力。
Scikit-learn 提供了多种强大的工具和技术来辅助模型选择,主要包括以下几个方面:
数据集划分 (Train-Test Split): 将数据集划分为训练集和测试集,用于评估模型的泛化能力。
交叉验证 (Cross-Validation): 更可靠地评估模型性能,避免因数据集划分的随机性带来的偏差。
超参数调优 (Hyperparameter Tuning): 通过网格搜索 (Grid Search)、随机搜索 (Randomized Search) 等方法,寻找最优的超参数组合。
模型评估指标 (Model Evaluation Metrics): 选择合适的评估指标来衡量模型的性能,例如准确率 (Accuracy)、精确率 (Precision)、召回率 (Recall)、F1-score、均方误差 (Mean Squared Error) 等。
下面我们将逐一深入探讨这些关键技术,并结合代码示例进行详细讲解。
数据集划分是模型选择的第一步,也是最基本的一步。它的目的是将原始数据集划分为两个互斥的子集:训练集 (Training Set) 和 测试集 (Testing Set)。
训练集: 用于训练模型,模型通过学习训练集中的数据来调整自身参数。
测试集: 用于评估模型的泛化能力,模拟模型在未知数据上的表现。测试集在模型训练过程中是不可见的,只有在模型训练完成后才用来评估模型性能。
Scikit-learn 提供了 train_test_split 函数来实现数据集的划分。
代码示例:
from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 划分数据集,测试集占比 30%,设置随机种子保证可重复性 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 使用训练集训练模型 model.fit(X_train, y_train) # 使用测试集评估模型性能 accuracy = model.score(X_test, y_test) print(f"模型在测试集上的准确率:{accuracy:.2f}")
代码详解:
导入必要的库: train_test_split 用于数据集划分,LogisticRegression 作为示例模型,load_iris 加载 Iris 数据集。
加载数据集: 使用 load_iris() 加载 Iris 数据集,X 存储特征数据,y 存储目标变量。
划分数据集: train_test_split(X, y, test_size=0.3, random_state=42) 函数将数据集划分为训练集和测试集。
test_size=0.3:设置测试集占比为 30%。
random_state=42:设置随机种子,保证每次运行代码划分结果一致,便于结果复现。
初始化模型: LogisticRegression(max_iter=1000) 初始化逻辑回归模型,max_iter 设置最大迭代次数。
训练模型: model.fit(X_train, y_train) 使用训练集 X_train 和 y_train 训练模型。
评估模型性能: model.score(X_test, y_test) 使用测试集 X_test 和 y_test 评估模型性能,对于分类模型,score 方法默认返回准确率。
打印结果: 打印模型在测试集上的准确率。
train_test_split 函数常用参数:
test_size: 测试集大小,可以是浮点数 (表示比例) 或整数 (表示样本数量)。
train_size: 训练集大小,与 test_size 类似。
random_state: 随机种子,用于控制随机划分过程,保证可重复性。
shuffle: 是否在划分前打乱数据,默认为 True。
stratify: 分层抽样,保持训练集和测试集中各类别的比例与原始数据集一致,适用于类别不平衡问题。
数据集划分的局限性:
结果的随机性: 单次划分的结果可能受到随机性的影响,如果数据集较小,划分结果的波动可能会比较大。
数据利用率低: 测试集在模型训练过程中被完全排除在外,数据利用率较低。
为了更可靠地评估模型性能,并充分利用数据,我们通常会使用交叉验证。
交叉验证 (Cross-Validation, CV) 是一种更可靠的模型评估方法,它将数据集划分为多个互斥的子集 (称为“折叠”,Fold),然后进行多次训练和评估,每次使用不同的折叠作为测试集,其余折叠作为训练集。最终,将多次评估结果进行汇总,得到一个更稳定和可靠的性能估计。
Scikit-learn 提供了多种交叉验证策略,常用的包括:
K 折交叉验证 (K-Fold Cross-Validation): 将数据集平均划分为 K 个折叠,每次选择一个折叠作为测试集,其余 K-1 个折叠作为训练集,重复 K 次。
分层 K 折交叉验证 (Stratified K-Fold Cross-Validation): 在 K 折交叉验证的基础上,保证每个折叠中各类别的比例与原始数据集一致,适用于类别不平衡问题。
留一交叉验证 (Leave-One-Out Cross-Validation, LOOCV): 每次只选择一个样本作为测试集,其余所有样本作为训练集,重复 N 次 (N 为样本总数)。
1. K 折交叉验证 (K-Fold Cross-Validation)
K 折交叉验证是最常用的交叉验证方法。Scikit-learn 提供了 KFold 类来实现 K 折交叉验证的划分,以及 cross_val_score 和 cross_validate 函数来直接进行交叉验证评估。
代码示例:
from sklearn.model_selection import KFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 初始化 K 折交叉验证,K=5 kf = KFold(n_splits=5, shuffle=True, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 使用 cross_val_score 进行交叉验证评估,返回每次折叠的得分 cv_scores = cross_val_score(model, X, y, cv=kf) # 打印每次折叠的得分和平均得分 print("每次折叠的交叉验证得分:", cv_scores) print(f"平均交叉验证得分: {cv_scores.mean():.2f}")
代码详解:
导入必要的库: KFold 用于 K 折交叉验证划分,cross_val_score 用于交叉验证评估。
初始化 K 折交叉验证: KFold(n_splits=5, shuffle=True, random_state=42) 初始化 KFold 对象。
n_splits=5: 设置折叠数为 5,即 5 折交叉验证。
shuffle=True: 在划分前打乱数据,避免数据本身有序导致的问题。
random_state=42: 设置随机种子,保证可重复性。
使用 cross_val_score 进行交叉验证: cross_val_score(model, X, y, cv=kf) 函数执行交叉验证。
model: 要评估的模型。
X, y: 特征数据和目标变量。
cv=kf: 指定交叉验证策略为 kf (K 折交叉验证)。
cross_val_score 返回一个数组,包含每次折叠的交叉验证得分 (默认使用模型的 score 方法,对于分类模型是准确率)。
打印结果: 打印每次折叠的得分和平均得分。平均得分可以更稳定地反映模型的泛化能力。
cross_val_score 函数常用参数:
estimator: 要评估的模型。
X, y: 特征数据和目标变量。
cv: 交叉验证策略,可以是整数 (表示折叠数,例如 cv=5 表示 5 折交叉验证),也可以是交叉验证生成器对象 (例如 KFold, StratifiedKFold)。
scoring: 评估指标,默认为模型的 score 方法。可以设置为其他评估指标,例如 'accuracy', 'precision', 'recall', 'f1', 'neg_mean_squared_error' 等。
n_jobs: 并行运行的作业数,-1 表示使用所有可用的 CPU 核心。
cross_validate 函数:
cross_validate 函数与 cross_val_score 类似,但功能更强大,可以返回更丰富的信息,例如训练得分、测试得分、训练时间、测试时间等。
代码示例:
from sklearn.model_selection import KFold, cross_validate from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 初始化 K 折交叉验证,K=5 kf = KFold(n_splits=5, shuffle=True, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 使用 cross_validate 进行交叉验证评估,返回更详细的结果 cv_results = cross_validate(model, X, y, cv=kf, scoring=['accuracy', 'f1'], return_train_score=True) # 打印交叉验证结果 print("交叉验证结果:") print(cv_results) print("\n平均测试准确率:", cv_results['test_accuracy'].mean()) print("平均测试 F1-score:", cv_results['test_f1'].mean()) print("平均训练准确率:", cv_results['train_accuracy'].mean())
代码详解:
cross_validate(model, X, y, cv=kf, scoring=['accuracy', 'f1'], return_train_score=True) 函数返回一个字典,包含更详细的交叉验证结果。
scoring=['accuracy', 'f1']: 指定评估指标为准确率和 F1-score。
return_train_score=True: 返回训练集上的得分。
2. 分层 K 折交叉验证 (Stratified K-Fold Cross-Validation)
分层 K 折交叉验证适用于类别不平衡的数据集。它在 K 折交叉验证的基础上,保证每个折叠中各类别的比例与原始数据集一致。Scikit-learn 提供了 StratifiedKFold 类来实现分层 K 折交叉验证。
代码示例:
from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 初始化分层 K 折交叉验证,K=5 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 使用 cross_val_score 进行交叉验证评估 cv_scores = cross_val_score(model, X, y, cv=skf) # 打印平均交叉验证得分 print(f"平均分层 K 折交叉验证得分: {cv_scores.mean():.2f}")
代码详解:
StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 初始化 StratifiedKFold 对象,参数与 KFold 类似。
使用 StratifiedKFold 作为 cv 参数传递给 cross_val_score 或 cross_validate 函数即可进行分层 K 折交叉验证。
3. 留一交叉验证 (Leave-One-Out Cross-Validation, LOOCV)
留一交叉验证每次只使用一个样本作为测试集,其余所有样本作为训练集。Scikit-learn 提供了 LeaveOneOut 类来实现留一交叉验证。
代码示例:
from sklearn.model_selection import LeaveOneOut, cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 初始化留一交叉验证 loo = LeaveOneOut() # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 使用 cross_val_score 进行交叉验证评估 cv_scores = cross_val_score(model, X, y, cv=loo) # 打印平均交叉验证得分 print(f"平均留一交叉验证得分: {cv_scores.mean():.2f}")
代码详解:
LeaveOneOut() 初始化 LeaveOneOut 对象。
使用 LeaveOneOut 作为 cv 参数传递给 cross_val_score 或 cross_validate 函数即可进行留一交叉验证。
交叉验证的优势:
更可靠的性能评估: 交叉验证通过多次训练和评估,得到更稳定和可靠的模型性能估计,避免了单次数据集划分的随机性。
充分利用数据: 每个样本既被用于训练,也被用于测试,提高了数据利用率。
超参数调优的基础: 交叉验证是超参数调优的重要基础,可以用来评估不同超参数组合的模型性能。
交叉验证的局限性:
计算成本较高: 交叉验证需要多次训练模型,当数据集较大或模型训练时间较长时,计算成本会比较高。
不适用于时间序列数据: 传统的交叉验证方法不适用于时间序列数据,因为时间序列数据具有时间依赖性,需要使用专门的时间序列交叉验证方法。
机器学习模型通常包含超参数 (Hyperparameters),这些参数不是通过模型训练学习得到的,而是需要在训练前手动设置的。不同的超参数组合会对模型性能产生显著影响。超参数调优的目标是找到一组最优的超参数组合,使得模型在验证集或交叉验证上的性能达到最佳。
Scikit-learn 提供了两种常用的超参数调优方法:
网格搜索 (Grid Search): 穷举所有可能的超参数组合,并使用交叉验证评估每种组合的性能,选择性能最佳的组合。
随机搜索 (Randomized Search): 在指定的超参数空间中随机采样若干组超参数组合,并使用交叉验证评估每种组合的性能,选择性能最佳的组合。
1. 网格搜索 (Grid Search)
网格搜索 (Grid Search) 是一种简单而有效的超参数调优方法。它需要我们预先定义一个超参数网格 (Parameter Grid),包含所有想要尝试的超参数及其可能的取值。然后,网格搜索会遍历网格中的每种超参数组合,使用交叉验证评估模型在每种组合下的性能,并记录最佳性能和对应的超参数组合。
Scikit-learn 提供了 GridSearchCV 类来实现网格搜索。
代码示例:
from sklearn.model_selection import GridSearchCV, train_test_split from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 划分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 定义超参数网格 param_grid = { 'C': [0.001, 0.01, 0.1, 1, 10, 100], # 正则化强度 'penalty': ['l1', 'l2'], # 正则化类型 'solver': ['liblinear', 'saga'] # 优化算法 } # 初始化 GridSearchCV 对象 grid_search = GridSearchCV(model, param_grid, cv=5, scoring='accuracy', n_jobs=-1) # 运行网格搜索 grid_search.fit(X_train, y_train) # 打印最佳超参数组合和最佳得分 print("最佳超参数组合:", grid_search.best_params_) print(f"最佳交叉验证得分: {grid_search.best_score_:.2f}") # 使用最佳模型在测试集上评估性能 best_model = grid_search.best_estimator_ test_accuracy = best_model.score(X_test, y_test) print(f"最佳模型在测试集上的准确率: {test_accuracy:.2f}")
代码详解:
导入必要的库: GridSearchCV 用于网格搜索。
定义超参数网格: param_grid 是一个字典,键是超参数名称 (字符串),值是超参数可能的取值列表。
初始化 GridSearchCV 对象: GridSearchCV(model, param_grid, cv=5, scoring='accuracy', n_jobs=-1) 初始化 GridSearchCV 对象。
estimator: 要调优的模型。
param_grid: 超参数网格。
cv=5: 使用 5 折交叉验证。
scoring='accuracy': 评估指标为准确率。
n_jobs=-1: 并行运行,使用所有 CPU 核心。
运行网格搜索: grid_search.fit(X_train, y_train) 函数执行网格搜索。GridSearchCV 会遍历 param_grid 中的每种超参数组合,使用交叉验证评估模型性能,并找到最佳组合。
打印最佳结果:
grid_search.best_params_: 返回最佳超参数组合 (字典)。
grid_search.best_score_: 返回最佳交叉验证得分。
使用最佳模型评估测试集性能:
grid_search.best_estimator_: 返回使用最佳超参数组合训练得到的最佳模型。
使用 best_model.score(X_test, y_test) 在测试集上评估最佳模型的性能。
GridSearchCV 常用参数:
estimator: 要调优的模型。
param_grid: 超参数网格 (字典)。
scoring: 评估指标,默认为模型的 score 方法。
cv: 交叉验证策略。
n_jobs: 并行运行的作业数。
refit: 是否使用最佳超参数组合在整个训练集上重新训练模型,默认为 True。
2. 随机搜索 (Randomized Search)
随机搜索 (Randomized Search) 是网格搜索的替代方法。当超参数空间较大时,网格搜索会非常耗时。随机搜索不是遍历所有可能的超参数组合,而是在指定的超参数空间中随机采样固定数量的超参数组合进行评估。
Scikit-learn 提供了 RandomizedSearchCV 类来实现随机搜索。
代码示例:
from sklearn.model_selection import RandomizedSearchCV, train_test_split from sklearn.linear_model import LogisticRegression from sklearn.datasets import load_iris from scipy.stats import uniform, randint # 加载 Iris 数据集 iris = load_iris() X, y = iris.data, iris.target # 划分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 初始化逻辑回归模型 model = LogisticRegression(max_iter=1000) # 定义超参数分布 param_distributions = { 'C': uniform(0.001, 100), # C 在 0.001 到 100 之间均匀分布 'penalty': ['l1', 'l2'], 'solver': ['liblinear', 'saga'] } # 初始化 RandomizedSearchCV 对象 randomized_search = RandomizedSearchCV(model, param_distributions, n_iter=10, cv=5, scoring='accuracy', n_jobs=-1, random_state=42) # 运行随机搜索 randomized_search.fit(X_train, y_train) # 打印最佳超参数组合和最佳得分 print("最佳超参数组合:", randomized_search.best_params_) print(f"最佳交叉验证得分: {randomized_search.best_score_:.2f}") # 使用最佳模型在测试集上评估性能 best_model = randomized_search.best_estimator_ test_accuracy = best_model.score(X_test, y_test) print(f"最佳模型在测试集上的准确率: {test_accuracy:.2f}")
代码详解:
导入必要的库: RandomizedSearchCV 用于随机搜索,uniform 和 randint 用于定义超参数的分布。
定义超参数分布: param_distributions 是一个字典,键是超参数名称,值是超参数的分布。
uniform(0.001, 100): 表示 C 超参数在 0.001 到 100 之间服从均匀分布。可以使用 scipy.stats 库中提供的各种分布,例如 randint (离散均匀分布), loguniform (对数均匀分布) 等。初始化 RandomizedSearchCV 对象: RandomizedSearchCV(model, param_distributions, n_iter=10, cv=5, scoring='accuracy', n_jobs=-1, random_state=42) 初始化 RandomizedSearchCV 对象。
param_distributions: 超参数分布。
n_iter=10: 设置随机采样的超参数组合数量为 10。
其他参数与 GridSearchCV 类似。
运行随机搜索: randomized_search.fit(X_train, y_train) 函数执行随机搜索。RandomizedSearchCV 会随机采样 n_iter 组超参数组合,使用交叉验证评估模型性能,并找到最佳组合。
RandomizedSearchCV 常用参数:
estimator: 要调优的模型。
param_distributions: 超参数分布 (字典)。
n_iter: 随机采样的超参数组合数量。
scoring: 评估指标。
cv: 交叉验证策略。
n_jobs: 并行运行的作业数。
random_state: 随机种子。
refit: 是否使用最佳超参数组合在整个训练集上重新训练模型。
网格搜索 vs. 随机搜索:
网格搜索: 穷举所有可能的超参数组合,保证找到全局最优解 (在定义的网格范围内),但当超参数空间较大时,计算成本高昂。
随机搜索: 随机采样超参数组合,计算成本较低,尤其适用于超参数空间较大或某些超参数对模型性能影响较小的情况。随机搜索不保证找到全局最优解,但通常也能找到接近最优解的超参数组合。
在实际应用中,可以先使用随机搜索快速缩小超参数搜索范围,然后再使用网格搜索在更小的范围内精细搜索。
模型评估指标 (Model Evaluation Metrics) 用于衡量模型在测试集或交叉验证集上的性能。选择合适的评估指标至关重要,因为它直接影响模型选择和超参数调优的结果。不同的任务类型 (分类、回归、聚类等) 和业务场景需要选择不同的评估指标。
Scikit-learn 提供了丰富的评估指标,常用的包括:
分类指标:
准确率 (Accuracy): 分类正确的样本数占总样本数的比例,适用于类别均衡的数据集。
精确率 (Precision): 预测为正例的样本中,真正为正例的样本比例,关注模型预测正例的准确性。
召回率 (Recall): 真正为正例的样本中,被模型预测为正例的样本比例,关注模型对正例的覆盖能力。
F1-score: 精确率和召回率的调和平均值,综合考虑了精确率和召回率。
AUC (Area Under the ROC Curve): ROC 曲线下的面积,用于评估二分类模型的排序能力。
对数损失 (Log Loss): 用于评估分类模型的概率预测性能。
回归指标:
均方误差 (Mean Squared Error, MSE): 预测值与真实值之差的平方的平均值,对异常值敏感。
均方根误差 (Root Mean Squared Error, RMSE): 均方误差的平方根,与真实值具有相同的量纲。
平均绝对误差 (Mean Absolute Error, MAE): 预测值与真实值之差的绝对值的平均值,对异常值不敏感。
R 平方 (R-squared): 反映模型解释数据变异的能力,值越接近 1,模型拟合效果越好。