Sklearn 实现高效机器学习的 19 个最优雅的技巧
了解 19 个 Sklearn 功能,它们可以直接而优雅地替代你手动执行的常见操作。
通过查阅Sklearn 的API 参考[1],我发现最常用的模型和函数只是该库功能的冰山一角。尽管有些功能非常局限,只用于极少数的边缘情况,但我发现许多估算器、变换器和实用函数,可以更优雅地修复人们手动执行的常见操作。
因此,我决定列出最优雅、最重要的功能,并进行简要说明,以便你可以在一篇文章中显著扩展你的 Sklearn 工具集。
1️⃣. covariance.EllipticEnvelope[2]
分布中存在异常值是很常见的。许多算法都会处理异值,Sklearn 内置的一个例子EllipticalEnvelope就是一个例子。该算法的优势在于它在检测正态分布(高斯)特征中的异常值方面表现非常出色:
import numpy as np
from sklearn.covariance import EllipticEnvelope
# Create a sample normal distribution
X = np.random.normal(loc=5, scale=2, size=50).reshape(-1, 1)
# Fit the estimator
ee = EllipticEnvelope(random_state=0)
_ = ee.fit(X)
# Test
test = np.array([6, 8, 20, 4, 5, 6, 10, 13]).reshape(-1, 1)
# predict returns 1 for an inlier and -1 for an outlier
>>> ee.predict(test)
array([ 1, 1, -1, 1, 1, 1, -1, -1])
展示如何使用椭圆包络估计器从正态分布的特征中检测异常值。
为了测试估计器,我们创建一个均值为 5、标准差为 2 的正态分布。训练完成后,我们将一些随机数传递给它的predict方法。该方法对test中的异常值(分别为 20、10、13)返回 -1。
2️⃣. feature_selection.RFECV[3]
选择最有助于预测的特征是防止过拟合和降低模型复杂度的必要步骤。Sklearn 提供的最强大的算法之一是递归特征消除 (RFE)。它使用交叉验证自动找到最重要的特征,并丢弃其余特征。
该估计器的一个优点是它是一个包装器——它可以用于任何返回特征重要性或系数分数的 Sklearn 算法。。这是一个合成数据集上的示例:
from sklearn.datasets import make_regression
from sklearn.feature_selection import RFECV
from sklearn.linear_model import Ridge
# Build a synthetic dataset
X, y = make_regression(n_samples=10000, n_features=15, n_informative=10)
# Init/fit the selector
rfecv = RFECV(estimator=Ridge(), cv=5)
_ = rfecv.fit(X, y)
# Transform the feature array
>>> rfecv.transform(X).shape
(10000, 10)
展示基于模型的特征选择技术——RFECV 如何使用 Ridge 回归器作为模型来工作。
伪数据集包含 15 个特征,其中 10 个为信息性特征,其余均为冗余特征。我们利用Ridge回归模型拟合了 5 倍 RFECV 估计器。训练完成后,你可以使用该transform方法丢弃冗余特征。调用结果.shape显示,该估计器成功丢弃了所有 5 个不必要的特征。
我已经写了一篇关于该算法的完整文章,涵盖了它如何与真实世界的数据集一起工作的细节:
3️⃣. ensemble.ExtraTrees[4]
尽管随机森林功能强大,但过拟合的风险很高。因此,Sklearn 提供了一种名为 ExtraTrees(既是分类器又是回归器)的 RF 替代方案。
extra一词并不意味着更多的树,而是更多的随机性。该算法使用了另一种与决策树非常相似的树。
唯一的区别在于,我们不是构建每棵树时就计算分割阈值,而是为每个特征随机抽取阈值,并选择最佳阈值作为分割规则。这降低了方差,但会略微增加偏差:
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor
X, y = make_regression(n_samples=10000, n_features=20)
# Decision trees
clf = DecisionTreeRegressor(max_depth=None, min_samples_split=2, random_state=0)
scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.6376080094392635
# Random Forest
clf = RandomForestRegressor(
n_estimators=10, max_depth=None, min_samples_split=2, random_state=0
)
scores = cross_val_score(clf, X, y, cv=5)
>>> scores.mean()
0.8446103607404536
# ExtraTrees
clf = ExtraTreesRegressor(
n_estimators=10, max_depth=None, min_samples_split=2, random_state=0
)
scores = cross_val_score(clf, X, y, cv=5)
在合成数据集上比较 RandomForest 和 ExtaTreesRegressor 的性能。ExtraTrees 胜出!
如你所见,ExtraTreesRegressor 在合成数据集上的表现优于随机森林。
从官方用户指南[5]中了解有关极随机树的更多信息。
4️⃣. impute.IterativeImputer[6]和KNNImputer[7]
如果你正在寻找比更强大、更先进的插补技术SimpleImputer,Sklearn 可以再次满足你的需求。
sklearn.impute子包包括两个基于模型的插补算法——KNNImputer和IterativeImputer。
顾名思义,KNNImputer使用 k-Nearest-Neighbors 算法来找到缺失值的最佳替代:
from sklearn.impute import KNNImputer
# Code taken from Sklearn user guide
X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]]
imputer = KNNImputer(n_neighbors=2)
>>> imputer.fit_transform(X)
array([[1. , 2. , 4. ],
[3. , 4. , 3. ],
[5.5, 6. , 5. ],
[8. , 8. , 7. ]])
使用 KNNImputer 作为基于模型的插补技术来适当填充缺失值。
一种更稳健的算法是IterativeImputer。它通过将每个缺失值特征建模为其余特征的函数来查找缺失值。此过程以循序渐进的方式进行。在每个步骤中,选择一个具有缺失值的特征作为目标(y),其余特征则选择为特征数组(X)。然后,使用回归器预测 中的缺失值y,并对每个特征重复此过程,直到 max_iter 次数(IterativeImputer 的一个参数)。
因此,单个缺失值会生成多个预测。这样做的好处是将每个缺失值视为一个随机变量,并关联其固有的不确定性:
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge
imp_mean = IterativeImputer(estimator=BayesianRidge())
imp_mean.fit([[7, 2, 3], [4, np.nan, 6], [10, 5, 9]])
X = [[np.nan, 2, 3], [4, np.nan, 6], [10, np.nan, 9]]
>>> imp_mean.transform(X)
array([[ 6.95847623, 2. , 3. ],
[ 4. , 2.6000004 , 6. ],
[10. , 4.99999933, 9. ]])
展示更强大的基于模型的插补技术——IterativeImputer 的工作原理。
发现 BayesianRidge 和 ExtraTree 与 IterativeImputer 配合使用效果更佳。
5️⃣. linear_model.HuberRegressor[8]
异常值的存在会严重影响任何模型的预测。许多异常值检测算法会丢弃异常值并将其标记为缺失值。虽然这有助于模型的学习功能,但它完全消除了异常值对分布的影响。
另一种算法是HuberRegressor。它不是完全移除异常值,而是在拟合过程中赋予异常值较小的权重。它有epsilon一个超参数,用于控制应被归类为异常值的样本数量。参数越小,模型对异常值的鲁棒性就越强。它的 API 与任何其他线性回归器相同。
下面,你可以看到它与具有大量异常值的数据集上的贝叶斯岭回归器的比较[9]:
如你所见,epsilon 为 1.35、1.5、1.75 的 HuberRegressor 成功捕捉到了不受异常值影响的最佳拟合线。
你可以从用户指南[10]中了解有关该算法的更多信息。
6️⃣ tree.plot_tree[11]
Sklearn 可以使用以下函数绘制单个决策树的结构plot_tree。对于刚开始学习基于树的模型和集成模型的初学者来说,此功能可能很方便:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
iris = load_iris()
X, y = iris.data, iris.target
clf = DecisionTreeClassifier()
clf = clf.fit(X, y)
plt.figure(figsize=(15, 10), dpi=200)
plot_tree(clf, feature_names=iris.feature_names,
class_names=iris.target_names);
使用 Sklearn 的 plot_tree 函数可视化决策树。
还有其他绘制树的方法,例如使用 Graphviz 格式。请参阅用户指南[12]了解更多信息。
7️⃣. linear_model.Perceptron[13]
这份列表中最酷的名字是排名第七的——感知器。虽然名字很花哨,但它其实是一个简单的线性二分类器。该算法最显著的特点是它适用于大规模学习,并且默认情况下:
它不需要学习率。 不实施正则化。 它仅在出现错误时更新其模型。
它相当于 SGDClassifier,loss='perceptron', eta=1, learning_rate="constant", penalty=None但速度稍快一些:
from sklearn.datasets import make_classification
from sklearn.linear_model import Perceptron
# Create a large dataset
X, y = make_classification(n_samples=100000, n_features=20, n_classes=2)
# Init/Fit/Score
clf = Perceptron()
_ = clf.fit(X, y)
>>> clf.score(X, y)
0.91928
展示感知器在样本二元分类问题上的性能。
8️⃣. feature_selection.SelectFromModel[14]
Sklearn 中另一个基于模型的特征选择估计器是SelectFromModel。它不如 RFECV 那样稳健,但由于计算成本较低,对于海量数据集来说是一个不错的选择。它也是一个包装器估计器,适用于任何具有以下.feature_importances_属性的模型.coef_:
from sklearn.feature_selection import SelectFromModel
# Make a dataset with 40 uninformative features
X, y = make_regression(n_samples=int(1e4), n_features=50, n_informative=10)
# Init the selector and transform feature array
selector = SelectFromModel(estimator=ExtraTreesRegressor()).fit(X, y)
>>> selector.transform(X).shape
(10000, 8)
在具有 40 个冗余特征的合成数据集上尝试使用 ExtraTreesRegressor 的 SelectFromModel 估计器。
如你所见,该算法成功删除了所有 40 个冗余特征。
9️⃣. metrics.ConfusionMatrixDisplay[15]
混淆矩阵是分类问题的圣杯。大多数指标都源于它,例如精确度、召回率、F1、ROC AUC 等。Sklearn 可以计算并绘制默认混淆矩阵:
from sklearn.metrics import plot_confusion_matrix
from sklearn.model_selection import train_test_split
# Make a binary classification problem
X, y = make_classification(n_samples=200, n_features=5, n_classes=2)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.5, random_state=1121218
)
clf = ExtraTreeClassifier().fit(X_train, y_train)
fig, ax = plt.subplots(figsize=(5, 4), dpi=100)
plot_confusion_matrix(clf, X_test, y_test, ax=ax);
说实话,我不太喜欢默认的混淆矩阵。它的格式是固定的——行是真实标签,列是预测值。此外,第一行第一列是负类,第二行第二列是正类。有些人可能更喜欢其他格式的矩阵,比如转置矩阵或翻转矩阵。
例如,我喜欢将正类设为第一行第一列,以符合维基百科的格式。这有助于我更好地区分 TP、FP、TN、FN 这四个矩阵项。幸运的是,你可以使用另一个函数 ConfusionMatrixDisplay 绘制自定义矩阵:
from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix
clf = ExtraTreeClassifier().fit(X_train, y_train)
y_preds = clf.predict(X_test)
fig, ax = plt.subplots(figsize=(5, 4), dpi=100)
cm = confusion_matrix(y_test, y_preds)
cmp = ConfusionMatrixDisplay(cm, display_labels=["Positive", "Negative"])
cmp.plot(ax=ax);
在将混淆矩阵cm传递给之前,你可以将ConfusionMatrixDisplay设置为你想要的任何格式。
🔟. Generalized Linear Models[16]
如果存在适用于其他类型分布的替代方案,那么将目标 ( y ) 转换为正态分布是没有意义的。
例如,Sklearn 为目标变量提供了三种广义线性模型,分别为泊松分布、Tweedie 分布或 Gamma 分布。与预期的正态分布不同,PoissonRegressor、TweedieRegressor和GammaRegressor可以为具有相应分布的目标生成稳健的结果。
除此之外,它们的 API 与任何其他 Sklearn 模型相同。要确定目标的分布是否与上述三个分布相匹配,你可以将它们的 PDF(概率密度函数)绘制在具有完美分布的相同轴上。
例如,要查看目标是否遵循泊松分布,请使用 Seaborn 中的 kdeplot绘制其 PDF ,并通过在相同轴上从 Numpy 中np.random.poisson采样来绘制完美的泊松分布。
1️⃣1️⃣. ensemble.IsolationForest[17]
由于基于树的模型和集成模型通常能产生更稳健的结果,它们在异常值检测方面也被证明是有效的。例如,Sklearn 中的IsolationForest ,其使用由极其随机的树组成的森林(tree.ExtraTreeRegressor)来检测异常值。每棵树都尝试通过选择一个特征,并在所选特征的最大值和最小值之间随机选择一个分割值来隔离每个样本。
这种类型的随机分区会在每棵树的根节点和终止节点之间产生明显较短的路径。
因此,当随机树林共同为特定样本产生较短的路径长度时,它们很可能是异常——Sklearn 用户指南。
from sklearn.ensemble import IsolationForest
X = np.array([-1.1, 0.3, 0.5, 100]).reshape(-1, 1)
clf = IsolationForest(random_state=0).fit(X)
>>> clf.predict([[0.1], [0], [90]])
array([ 1, 1, -1])
该算法正确捕获了异常值(90)并将其标记为-1。
在用户指南[18]中阅读有关该算法的更多信息。
1️⃣2️⃣. preprocessing.PowerTransformer[19]
许多线性模型需要对数值特征进行一些转换,以使其服从正态分布,StandardScaler和MinMaxScaler对大多数分布都运行良好。
然而,当数据偏度较高时,分布的核心指标(例如平均值、中位数、最小值和最大值)都会受到影响。因此,简单的归一化和标准化对偏斜分布不起作用。
相反,Sklearn 中的 PowerTransformer 使用对数变换将任何倾斜的特征实现了尽可能地转换为正态分布。考虑 Diamonds 数据集中的这两个特征:
import seaborn as sns
diamonds = sns.load_dataset("diamonds")
diamonds[["price", "carat"]].hist(figsize=(10, 5));
两者都严重倾斜。让我们使用对数变换来解决这个问题:
from sklearn.preprocessing import PowerTransformer
pt = PowerTransformer()
diamonds.loc[:, ["price", "carat"]] = pt.fit_transform(diamonds[["price", "carat"]])
diamonds[["price", "carat"]].hist(figsize=(10, 5));
倾斜消失了!
1️⃣3️⃣. preprocessing.RobustScaler[20]
Sklearn 中的另一个数字转换器是RobustScaler。你大概能从它的名字猜到它的作用——它可以以一种对异常值鲁棒的方式转换特征。如果某个特征中存在异常值,则很难使它们服从正态分布,因为它们会严重扭曲平均值和标准差。
不使用平均值/标准差,而是RobustScaler使用中位数和四分位距 (IQR) 来缩放数据,因为这两个指标不会因异常值而产生偏差。你也可以在用户指南[21]中阅读相关内容。
1️⃣4️⃣. compose.make_column_transformer[22]
在 Sklearn 中,有一个使用make_pipeline函数创建 Pipeline 实例的简写方法。该函数无需为每个步骤命名,也无需让代码变得冗长,只需接受转换器和估算器即可完成其工作:
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
pipeline = make_pipeline(SimpleImputer(), StandardScaler(), ExtraTreesRegressor())
>>> pipeline
Pipeline(steps=[('simpleimputer', SimpleImputer()),
('standardscaler', StandardScaler()),
('extratreesregressor', ExtraTreesRegressor())])
使用“make_pipeline”函数缩短创建 Sklearn 管道的代码。
对于更复杂的场景,ColumnTransformer使用 ,但它也存在同样的问题——每个预处理步骤都应该命名,这会使代码冗长且难以阅读。值得庆幸的是,Sklearn 提供了类似的函数make_pipeline— make_column_transformer:
import seaborn as sns
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import OneHotEncoder
# Load diamonds dataset
diamonds = sns.load_dataset("diamonds")
X, y = diamonds.drop("price", axis=1), diamonds.price.values.reshape(-1, 1)
# Isolate numeric and categorical cols
num_cols = X.select_dtypes(include=np.number).columns
cat_cols = X.select_dtypes(exclude=np.number).columns
>>> make_column_transformer((StandardScaler(), num_cols),
(OneHotEncoder(), cat_cols))
ColumnTransformer(
transformers=[('standardscaler', StandardScaler(),
Index(['carat', 'depth', 'table', 'x', 'y', 'z'], dtype='object')),
('onehotencoder', OneHotEncoder(),
Index(['cut', 'color', 'clarity'], dtype='object'))]
使用make_column_transformer函数缩短创建ColumnTransformer对象的代码。
如你所见,使用起来make_column_transformer要短得多,并且它负责自行命名每个转换器步骤。
1️⃣5️⃣. compose.make_column_selector[23]
如果你仔细观察,就会发现我们使用了select_dtypes函数以及columnsPandas DataFrames 的属性来隔离数字列和分类列。虽然这种方法有效,但使用 Sklearn 可以实现更灵活、更优雅的解决方案。
make_column_selector 函数创建一个可直接传递给ColumnTransformer实例的列选择器。它的功能与select_dtypes类似, 甚至更好。它包含dtype_include和dtype_exclude参数,用于根据数据类型选择列。
如果需要自定义列过滤器,甚至可以将正则表达式传递给 ,pattern同时将其他参数设置为None。具体操作如下:
from sklearn.compose import make_column_selector
make_column_transformer(
(StandardScaler(), make_column_selector(dtype_include=np.number)),
(OneHotEncoder(), make_column_selector(dtype_exclude=np.number)),
)
你无需传递列名列表,只需传递make_column_selector带有相关参数的实例即可!
1️⃣6️⃣. preprocessing.OrdinalEncoder[24]
初学者常犯的一个错误是使用LabelEncoder来编码有序分类特征。如果你注意到了,LabelEncoder一次只能转换一列,而不是像OneHotEncoder那样同时转换。你可能会认为 Sklearn 犯了一个错误!
实际上,LabelEncoder应该仅用于编码响应变量(y),如其文档[25]中所述。要编码特征数组(X),你应该使用OrdinalEncoder可以正常工作的函数。它将有序分类列转换为具有 (0, n_categories - 1) 个类别的特征。并且它只需一行代码即可对所有指定的列执行此操作,从而可以将其包含在管道中。
from sklearn.preprocessing import OrdinalEncoder
oe = OrdinalEncoder()
X = [
["class_1", "rank_1"],
["class_1", "rank_3"],
["class_3", "rank_3"],
["class_2", "rank_2"],
]
>>> oe.fit_transform(X)
array([[0., 0.],
[0., 2.],
[2., 2.],
[1., 1.]])
使用 OrdinalEncoder 编码序数分类特征
1️⃣7️⃣. metrics.get_scorer[26]
Sklearn 内置了 50 多个指标,它们的文本名称可以在 中查看sklearn.metrics.SCORERS.keys()。在单个项目中,你可能需要使用多个指标,如果要单独使用它们,则需要导入它们。
直接导入大量指标sklearn.metrics可能会污染你的命名空间,并导致不必要的冗长代码。解决方案是,你可以使用metrics.get_scorer函数通过文本名称访问任何指标,而无需导入它:
from sklearn.metrics import get_scorer
>>> get_scorer("neg_mean_squared_error")
make_scorer(mean_squared_error, greater_is_better=False)
>>> get_scorer("recall_macro")
make_scorer(recall_score, pos_label=None, average=macro)
>>> get_scorer("neg_log_loss")
make_scorer(log_loss, greater_is_better=False, needs_proba=True)
使用 get_scorer 函数而不导入指标。
1️⃣8️⃣. model_selection.HalvingGrid[27]和HalvingRandomSearchCV[28]
在 Sklearn 0.24 版本中,我们引入了两个实验性的超参数优化器:HalvingGridSearchCV和HalvingRandomSearchCV类。
与穷举型网格搜索 (GridSearch) 和随机化搜索 (RandomizedSearch) 不同,新类别使用了一种名为“连续减半”的技术。它不是在所有数据上训练所有候选集(参数组合集),而是只将一部分数据提供给参数。通过在较小的数据子集上进行训练,可以过滤掉表现最差的候选集。每次迭代后,训练样本都会增加一定倍数,而可能的候选集数量则会相应减少,从而大大缩短评估时间。
快多少?在我进行的实验中,HalvingGridSearch 比常规 GridSearch 快 11 倍,HalvingRandomSearch 甚至比 HalvingGridSearch 快 10 倍。你可以在这里阅读我对连续减半的详细概述和我的实验:
1️⃣9️⃣ sklearn.utils[29]
最后但同样重要的是,Sklearn 的 subpackage 模块中提供了大量的实用函数和辅助函数sklearn.utils。Sklearn 本身使用此模块中的函数来构建我们常用的所有转换器和估算器。
有很多有用的,例如class_weight.compute_class_weight,estimator_html_repr,shuffle,check_X_y等等。你可以在自己的工作流程中使用它们,使你的代码更像 Sklearn,或者在创建适合 Sklearn API 的自定义转换器和估算器时它们可能会派上用场。
Sklearn 的API 参考: https://scikit-learn.org/stable/modules/classes.html#api-reference
[2]covariance.EllipticEnvelope: https://scikit-learn.org/stable/modules/generated/sklearn.covariance.EllipticEnvelope.html#sklearn.covariance.EllipticEnvelope
[3]feature_selection.RFECV: https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.RFECV.html
[4]ensemble.ExtraTrees: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html
[5]用户指南: https://scikit-learn.org/stable/modules/ensemble.html#extremely-randomized-trees
[6]impute.IterativeImputer: https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html
[7]KNNImputer: https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html
[8]linear_model.HuberRegressor: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.HuberRegressor.html
[9]贝叶斯岭回归器的比较: https://scikit-learn.org/stable/auto_examples/linear_model/plot_huber_vs_ridge.html#sphx-glr-auto-examples-linear-model-plot-huber-vs-ridge-py
[10]用户指南: https://scikit-learn.org/stable/modules/linear_model.html#huber-regression
[11]tree.plot_tree: https://scikit-learn.org/stable/modules/generated/sklearn.tree.plot_tree.html
[12]用户指南: https://scikit-learn.org/stable/modules/tree.html#decision-trees
[13]linear_model.Perceptron: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Perceptron.html
[14]feature_selection.SelectFromModel: https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectFromModel.html
[15]metrics.ConfusionMatrixDisplay: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.ConfusionMatrixDisplay.html
[16]Generalized Linear Models: https://scikit-learn.org/stable/modules/linear_model.html#generalized-linear-regression
[17]ensemble.IsolationForest: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html
[18]在用户指南: https://scikit-learn.org/stable/modules/outlier_detection.html#isolation-forest
[19]preprocessing.PowerTransformer: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html
[20]preprocessing.RobustScaler: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html
[21]用户指南: https://scikit-learn.org/stable/modules/preprocessing.html#preprocessing-data
[22]compose.make_column_transformer: https://scikit-learn.org/stable/modules/generated/sklearn.compose.make_column_transformer.html
[23]compose.make_column_selector: https://scikit-learn.org/stable/modules/generated/sklearn.compose.make_column_selector.html
[24]preprocessing.OrdinalEncoder: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OrdinalEncoder.html
[25]文档: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html
[26]metrics.get_scorer: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.get_scorer.html
[27]model_selection.HalvingGrid: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.HalvingGridSearchCV.html
[28]HalvingRandomSearchCV: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.HalvingGridSearchCV.html
[29]sklearn.utils: https://scikit-learn.org/stable/modules/classes.html#module-sklearn.utils