优化机器学习模型调优:选择 RandomizedSearchCV 而非 GridSearchCV
在动态发展的机器学习领域,模型调优对于实现最佳性能至关重要。传统上,GridSearchCV 一直是超参数优化的首选方法。然而,随着数据集规模和复杂性的增长,GridSearchCV 可能成为资源密集型的瓶颈。此时,RandomizedSearchCV 作为一种更高效的替代方案应运而生,它在显著减少计算开销的同时,提供了可比的结果。本文深入探讨了这两种方法的细节,重点强调了在大规模数据项目中采用 RandomizedSearchCV 的优势。
目录
- 理解 GridSearchCV 及其局限性
- 介绍 RandomizedSearchCV
- 比较分析:GridSearchCV vs. RandomizedSearchCV
- 数据准备与预处理
- 模型构建与超参数调优
- 结果与性能评估
- 结论:何时选择 RandomizedSearchCV
- 资源与进一步阅读
理解 GridSearchCV 及其局限性
GridSearchCV 是 scikit-learn 中用于超参数调优的强大工具。它通过穷举搜索预定义的超参数集,以根据指定的度量标准识别出最佳模型性能的参数组合。
主要特征:
- 穷举搜索:评估参数网格中的所有可能组合。
- 交叉验证集成:使用交叉验证确保模型的稳健性。
- 最佳估计器选择:根据性能指标返回最佳模型。
局限性:
- 计算密集型:随着参数网格的增长,组合数量呈指数级增加,导致计算时间变长。
- 内存消耗:处理具有众多参数组合的大型数据集可能会消耗大量系统资源。
- 回报递减:并非所有参数组合对模型性能都有显著贡献,使得穷举搜索效率低下。
案例分析:在使用 GridSearchCV 处理超过 129,000 条记录的数据集时,即使在强大的硬件上也耗时约 12 小时。这展示了其在大规模应用中的不实用性。
介绍 RandomizedSearchCV
RandomizedSearchCV 通过从指定的分布中抽样固定数量的超参数组合,而不是评估所有可能的组合,提供了对 GridSearchCV 的务实替代方案。
优势:
- 效率:通过限制评估次数显著减少计算时间。
- 灵活性:允许为每个超参数指定分布,启用更为多样化的采样。
- 可扩展性:更适合大型数据集和复杂模型。
工作原理:
RandomizedSearchCV 随机选择一部分超参数组合,使用交叉验证进行评估,并基于选择的度量标准识别性能最佳的组合。
比较分析:GridSearchCV vs. RandomizedSearchCV
方面 | GridSearchCV | RandomizedSearchCV |
---|---|---|
搜索方法 | 穷举式 | 随机采样 |
计算时间 | 高 | 低至中 |
资源使用 | 高 | 中至低 |
性能 | 潜在最优 | 较少努力下的可比性能 |
灵活性 | 固定组合 | 基于概率的采样 |
可视化:在实践中,RandomizedSearchCV 可以将模型调优时间从数小时减少到几分钟,而性能下降不显著。
数据准备与预处理
有效的数据预处理为成功的模型训练奠定了基础。以下是基于提供的 Jupyter Notebook 的逐步演练。
加载数据集
使用的数据集是来自 Kaggle 的 航空公司乘客满意度。它包含 5,000 条记录和 23 个与乘客体验和满意度相关的特征。
1 2 3 4 5 6 |
import pandas as pd import seaborn as sns # 加载小型数据集 data = pd.read_csv('Airline2_tiny.csv') print(data.shape) # 输出: (4999, 23) |
处理缺失数据
数值数据
使用 均值 策略对缺失的数值值进行填补。
1 2 3 4 5 6 7 |
import numpy as np from sklearn.impute import SimpleImputer imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean') numerical_cols = list(np.where((X.dtypes == np.int64) | (X.dtypes == np.float64))[0]) imp_mean.fit(X.iloc[:, numerical_cols]) X.iloc[:, numerical_cols] = imp_mean.transform(X.iloc[:, numerical_cols]) |
分类数据
使用 最频繁 策略对缺失的分类值进行填补。
1 2 3 4 |
imp_mode = SimpleImputer(missing_values=np.nan, strategy='most_frequent') string_cols = list(np.where((X.dtypes == 'object'))[0]) imp_mode.fit(X.iloc[:, string_cols]) X.iloc[:, string_cols] = imp_mode.transform(X.iloc[:, string_cols]) |
编码分类变量
根据唯一类别的数量,使用 独热编码 和 标签编码 的组合对分类特征进行编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, LabelEncoder def OneHotEncoderMethod(indices, data): columnTransformer = ColumnTransformer([('encoder', OneHotEncoder(), indices )], remainder='passthrough') return columnTransformer.fit_transform(data) def LabelEncoderMethod(series): le = LabelEncoder() le.fit(series) return le.transform(series) def EncodingSelection(X, threshold=10): string_cols = list(np.where((X.dtypes == 'object'))[0]) one_hot_encoding_indices = [] for col in string_cols: length = len(pd.unique(X[X.columns[col]])) if length == 2 or length > threshold: X[X.columns[col]] = LabelEncoderMethod(X[X.columns[col]]) else: one_hot_encoding_indices.append(col) X = OneHotEncoderMethod(one_hot_encoding_indices, X) return X # 应用编码 X = EncodingSelection(X) print(X.shape) # 输出: (4999, 24) |
特征选择
选择最相关的特征可以提升模型性能并减少复杂性。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from sklearn.feature_selection import SelectKBest, chi2 from sklearn.preprocessing import MinMaxScaler kbest = SelectKBest(score_func=chi2, k='all') MMS = MinMaxScaler() K_features = 10 x_temp = MMS.fit_transform(X) x_temp = kbest.fit(x_temp, y) best_features = np.argsort(x_temp.scores_)[-K_features:] features_to_delete = np.argsort(x_temp.scores_)[:-K_features] X = np.delete(X, features_to_delete, axis=1) print(X.shape) # 输出: (4999, 10) |
训练测试拆分
拆分数据集确保模型在未见过的数据上进行评估,从而促进无偏的性能指标。
1 2 3 4 5 |
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=1) print(X_train.shape) # 输出: (3999, 10) print(X_test.shape) # 输出: (1000, 10) |
特征缩放
缩放特征确保所有特征对模型性能的贡献相等。
1 2 3 4 5 6 |
from sklearn.preprocessing import StandardScaler sc = StandardScaler(with_mean=False) sc.fit(X_train) X_train = sc.transform(X_train) X_test = sc.transform(X_test) |
模型构建与超参数调优
数据预处理完成后,接下来是使用 RandomizedSearchCV 构建和优化各种机器学习模型。
K-近邻 (KNN)
KNN 是一种简单的基于实例的学习算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import RandomizedSearchCV, StratifiedKFold model = KNeighborsClassifier() params = { 'n_neighbors': [4, 5, 6, 7], 'leaf_size': [1, 3, 5], 'algorithm': ['auto', 'kd_tree'], 'weights': ['uniform', 'distance'] } cv = StratifiedKFold(n_splits=2) random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: KNeighborsClassifier(leaf_size=1) print("Best score", random_search_cv.best_score_) # 输出: 0.8774673417446253 |
逻辑回归
用于二分类任务的概率模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.linear_model import LogisticRegression model = LogisticRegression() params = { 'solver': ['newton-cg', 'lbfgs', 'liblinear'], 'penalty': ['l1', 'l2'], 'C': [100, 10, 1.0, 0.1, 0.01] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: LogisticRegression(C=0.01) print("Best score", random_search_cv.best_score_) # 输出: 0.8295203666687819 |
高斯朴素贝叶斯 (GaussianNB)
基于贝叶斯定理的简单而有效的概率分类器。
1 2 3 4 5 6 7 8 |
from sklearn.naive_bayes import GaussianNB from sklearn.metrics import accuracy_score, classification_report model_GNB = GaussianNB() model_GNB.fit(X_train, y_train) y_pred = model_GNB.predict(X_test) print(accuracy_score(y_pred, y_test)) # 输出: 0.84 print(classification_report(y_pred, y_test)) |
输出:
1 2 3 4 5 6 7 8 |
precision recall f1-score support 0 0.86 0.86 0.86 564 1 0.82 0.81 0.82 436 accuracy 0.84 1000 macro avg 0.84 0.84 0.84 1000 weighted avg 0.84 0.84 0.84 1000 |
支持向量机 (SVM)
在高维空间中有效的稳健分类器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from sklearn.svm import SVC model = SVC() params = { 'kernel': ['linear', 'poly', 'rbf', 'sigmoid'], 'C': [1, 5, 10], 'degree': [3, 8], 'coef0': [0.01, 10, 0.5], 'gamma': ['auto', 'scale'] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: SVC(C=10, coef0=0.5, degree=8) print("Best score", random_search_cv.best_score_) # 输出: 0.9165979221213969 |
决策树
基于特征划分做出决策的层级模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from sklearn.tree import DecisionTreeClassifier model = DecisionTreeClassifier() params = { 'max_leaf_nodes': list(range(2, 100)), 'min_samples_split': [2, 3, 4] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: DecisionTreeClassifier(max_leaf_nodes=30, min_samples_split=4) print("Best score", random_search_cv.best_score_) # 输出: 0.9069240944070234 |
随机森林
利用多棵决策树的集成方法以增强预测性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from sklearn.ensemble import RandomForestClassifier model = RandomForestClassifier() params = { 'bootstrap': [True], 'max_depth': [80, 90, 100, 110], 'max_features': [2, 3], 'min_samples_leaf': [3, 4, 5], 'min_samples_split': [8, 10, 12], 'n_estimators': [100, 200, 300, 1000] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: RandomForestClassifier(max_leaf_nodes=96, min_samples_split=3) print("Best score", random_search_cv.best_score_) # 输出: 0.9227615146702333 |
AdaBoost
一种增强的集成方法,通过结合多个弱学习器形成一个强学习器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from sklearn.ensemble import AdaBoostClassifier model = AdaBoostClassifier() params = { 'n_estimators': np.arange(10, 300, 10), 'learning_rate': [0.01, 0.05, 0.1, 1] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: AdaBoostClassifier(learning_rate=0.1, n_estimators=200) print("Best score", random_search_cv.best_score_) # 输出: 0.8906331862757826 |
XGBoost
一种优化的梯度提升框架,以其性能和速度著称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import xgboost as xgb from sklearn.metrics import accuracy_score, classification_report model = xgb.XGBClassifier() params = { 'min_child_weight': [1, 5, 10], 'gamma': [0.5, 1, 1.5, 2, 5], 'subsample': [0.6, 0.8, 1.0], 'colsample_bytree': [0.6, 0.8, 1.0], 'max_depth': [3, 4, 5], 'n_estimators': [100, 500, 1000], 'learning_rate': [0.01, 0.3, 0.5, 0.1], 'reg_lambda': [1, 2] } random_search_cv = RandomizedSearchCV( estimator=model, param_distributions=params, verbose=1, cv=cv, scoring='f1', n_jobs=-1 ) random_search_cv.fit(X_train, y_train) print("Best Estimator", random_search_cv.best_estimator_) # 输出: XGBClassifier with best parameters print("Best score", random_search_cv.best_score_) # 输出: 0.922052180776655 # 模型评估 model_best = random_search_cv.best_estimator_ model_best.fit(X_train, y_train) y_pred = model_best.predict(X_test) print(accuracy_score(y_pred, y_test)) # 输出: 0.937 print(classification_report(y_pred, y_test)) |
输出:
1 2 3 4 5 6 7 8 |
precision recall f1-score support 0 0.96 0.93 0.95 583 1 0.91 0.94 0.93 417 accuracy 0.94 1000 macro avg 0.93 0.94 0.94 1000 weighted avg 0.94 0.94 0.94 1000 |
结果与性能评估
RandomizedSearchCV 的有效性从模型性能中可以看出:
- KNN 达到了约 0.877 的 F1 分数。
- 逻辑回归 提供了约 0.830 的 F1 分数。
- GaussianNB 保持了 84% 的准确率。
- SVM 以约 0.917 的 F1 分数脱颖而出。
- 决策树 获得了约 0.907 的 F1 分数。
- 随机森林 以约 0.923 的 F1 分数领先。
- AdaBoost 达到了约 0.891 的 F1 分数。
- XGBoost 以约 0.922 的 F1 分数和 93.7% 的准确率表现出色。
主要观察:
- RandomForestClassifier 和 XGBoost 展示了卓越的性能。
- RandomizedSearchCV 有效将计算时间从超过 12 小时(GridSearchCV)缩短到仅几分钟,而不影响模型准确性。
结论:何时选择 RandomizedSearchCV
虽然 GridSearchCV 提供了穷尽的超参数调优,但其计算需求对于大型数据集来说可能是不可承受的。RandomizedSearchCV 作为一种务实的解决方案,平衡了效率与性能。它在以下情况下尤为有利:
- 时间有限:需要快速的模型调优。
- 计算资源有限:减轻系统资源的负担。
- 高维超参数空间:简化搜索过程。
采用 RandomizedSearchCV 可以简化机器学习工作流程,使从业者能够专注于模型解释和部署,而不是漫长的调优过程。
资源与进一步阅读
通过利用 RandomizedSearchCV,机器学习从业者可以实现高效且有效的模型调优,确保数据驱动应用中的可扩展性和高性能解决方案。