10 个 Sklearn 宝藏功能
我已经用可很长时间的sklearn了,即使我现在更多地在使用大模型相关库,但sklearn经典永不过时,我发现了这个库中很多很棒的功能。我发现很多能够处理罕见边缘情况的技巧。所有今天跟云朵君一起看看这些功能,你还要哪些没有用过的,可以试一试~~
接下来正文开始吧!
1️. FunctionTransformer
尽管 Sklearn 提供了许多可放入管道的预处理转换器,但它们不足以应对所有可能的预处理场景。即使只有一个步骤不在管道内,整个原子、单次调用管道的理念也会失效。
这就是为什么你应该将所有自定义预处理函数包装在 中FunctionTransformer,这样可以将它们转换为与 Sklearn 兼容的转换器。唯一的要求是该函数接受一个特征数组 (X) 和一个可选的目标数组,并在预处理后返回它们。
演示
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformerdefreduce_memory(X: pd.DataFrame, y=None):
"""Simple function to reduce memory usage by casting numeric columns to float32."""
num_cols = X.select_dtypes(incluce=np.number).columns
for col in num_cols:
X[col] = X.astype("float32")
return X, y
ReduceMemoryTransformer = FunctionTransformer(reduce_memory)
# Plug into a pipeline
>>> make_pipeline(SimpleImputer(), ReduceMemoryTransformer)
Pipeline(steps=[('simpleimputer', SimpleImputer()),
('functiontransformer', ReduceMemoryTransformer()])
将简单的 Python 函数转换为转换器,以便将其添加到管道中
文档
FunctionTransformer —链接[1]
2️. 用户定义的变压器
基于上一节,你无法在简单函数中执行一些自定义数据清理步骤。
例如,在清洗过程中,最常见的操作之一是缩放倾斜的特征,使其服从正态分布。通常,人们会使用对数变换器(例如PowerTransformer或 )np.log,但它们有一个问题。如果特征包含零,底层对数函数将无法处理它们,从而引发错误。
有一种解决方法是,先在特征向量上加 1,然后进行变换。如果需要原始向量,就对特征向量调用指数函数,然后减 1——问题就解决了。
当然,Sklearn 本身没有内置此操作,你也无法在简单的 Python 函数中执行。这时,你可以使用自定义转换器来优雅地解决这个问题。下面是一个完全符合我描述的转换器:
演示
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import PowerTransformerclassCustomLogTransformer(BaseEstimator, TransformerMixin):
def__init__(self):
self._estimator = PowerTransformer() # init a transformer
deffit(self, X, y=None):
X_copy = np.copy(X) + 1# add one in case of zeroes
self._estimator.fit(X_copy)
return self
deftransform(self, X):
X_copy = np.copy(X) + 1
return self._estimator.transform(X_copy) # perform scaling
definverse_transform(self, X):
X_reversed = self._estimator.inverse_transform(np.copy(X))
编写一个自定义转换器类,对特征 X 执行对数缩放。该类具有应用和逆转操作的方法。
该类应该继承自能够将其插入管道的BaseEstimator类TransformerMixin。这样可以减少代码编写量,并降低数据泄露的可能性。
如果你想了解有关此方法的更多信息,我有一篇关于如何编写此类自定义转换器的单独文章,其中包括FunctionTransformer:
文档
BaseEstimator —链接[2]。 TransformerMixin —链接[3]。
3️. TransformedTargetRegressor
有时,甚至目标数组也y需要额外的预处理步骤,而这些步骤无法包含在管道中。一个典型的场景是缩放数值目标,使其服从正态分布。你需要在管道外部执行此操作,通常不止一次。
y如果你有一个管道,可以同时处理目标数组和特征数组,那不是很棒吗X?事实证明确实有!(仅适用于回归)。
TransformedTargetRegressor 是一个类,它既接受特征上的回归器管道X,也接受目标数组的单独预处理函数或转换器y。
演示
from sklearn.compose import TransformedTargetRegressorreg_lgbm = lgbm.LGBMRegressor()
final_estimator = TransformedTargetRegressor(
regressor=reg_lgbm, transformer=CustomLogTransformer()
)
final_estimator.fit(X_train, y_train)
TransformedTargetRegressor(regressor=LGBMRegressor(),
transformer=CustomLogTransformer())
TransformedTargetRegressor 的一个实例,使用 CustomLogTransformer 对目标 y 进行对数缩放,然后拟合回归模型。
该regressor参数接受以它们结尾的回归器或管道。它还有transformer一个参数,你可以向其传递一个要应用于目标的转换器类y。如果转换器是一个函数,例如np.log,你可以将其传递给func参数。
然后,调用fit将转换特征数组和目标数组,并拟合回归器。请参阅文档了解更多信息。
文档:
TransformedTargetregressor —链接[4]。
4️. HTML 估算器表示
如果你的管道由多个步骤或子管道组成,那么它们的 IPython 显示会让你眼花缭乱。如果你不小心在 Jupyter 中显示它们,它们看起来会是这样的:
>>> giant_pipelinePipeline(steps=[('columntransformer',
ColumnTransformer(transformers=[('cat_pipe',
Pipeline(steps=[('impute',
SimpleImputer(strategy='most_frequent')),
('oh',
OneHotEncoder())]),
<sklearn.compose._column_transformer.make_column_selector object at 0x000001B6D8BD9310>),
('num_pipe',
Pipeline(steps=[('impute',
SimpleImputer(strategy='median')),
('transform',
QuantileTransformer())]),
<sklearn.compose._column_transformer.make_column_selector object at 0x000001B6D8BD9160>)])),
('lgbmregressor',
LGBMRegressor(device_type='gpu', learning_rate=0.01,
幸运的是,Sklearn 提供了其估算器的 HTML 表示形式,以使其更加用户友好并让我们的眼睛感到愉悦:
演示
from sklearn import set_configset_config(display="diagram")
>>> giant_pipeline
view raw9605
通过将显示配置设置为diagram,你可以在 IPython 中获得管道的交互式 HTML 表示。
文档
sklearn.set_config —链接[5]。 sklearn.utils.estimator_html_repr —链接[6]
5️. QuadraticDiscriminantAnalysis
在 Kaggle即时满足竞赛[7]中,二次判别分析分类器即使没有进行超参数调整也取得了令人印象深刻的 0.965 ROC AUC 分数,超过了大多数基于树的模型,包括 XGBoost 和 LightGBM。
那么,既然这个算法能够超越最先进的模型,为什么你以前从未听说过呢?因为它的用例有限。训练 QDA 的特征应该严格服从正态分布,这样 QDA 才能轻松计算并拟合出围绕该分布的椭球形状。
QDA 的另一个优点是速度极快——只需几秒钟即可在百万行数据集上进行训练:
演示
%%timefrom sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
# Generate 1M samples and 100 features
X, y = make_classification(n_samples=1000000, n_features=100)
qda = QuadraticDiscriminantAnalysis().fit(X, y)
Wall time: 13.4 s
view raw
在这篇笔记[8]中,Chris Deotte 展示了如何通过结合 512 个 QDA 模型来实现 0.965 的 ROC AUC 分数。
此外,我还使用 QDA 将 ROC AUC 得分控制在了约 0.8,而表现最佳的解决方案的 AUC 得分约为 0.81。这 2% 的差异是你需要权衡的,你可以选择最准确但速度较慢的基于树的模型,或者选择运行速度极快但准确率较低的模型。
文档:
QuadraticDiscriminantAnalysis —链接[9]。
6️. Voting Classifier/Regressor
在一个项目中,你通常会得到几个性能相近的调整模型。这时,投票集成就可以整合这些模型,进一步提升性能。投票分类器会将多个分类器的多数票作为最终预测。如果类别是概率或预测是连续的,则对预测结果取平均值。
这种方法之所以效果显著,是因为概率论的缘故。简而言之,三个准确率分别为 0.6、0.7 和 0.8 的分类器组合起来,最终会比三个准确率均为 0.8 的分类器表现更好。
Sklearn 提供了两个方便的类来实现此操作。你需要传递一个分类器或回归器的列表,它会负责将它们组合起来。
演示
from sklearn.ensemble import VotingClassifierX, y = make_classification(n_samples=1000)
ensemble = VotingClassifier(
estimators=[
("xgb", xgb.XGBClassifier(eval_metric="auc")),
("lgbm", lgbm.LGBMClassifier()),
("cb", cb.CatBoostClassifier(verbose=False)),
],
voting="soft",
# n_jobs=-1,
)
_ = ensemble.fit(X, y)
view raw
我将其设置voting为 soft,表示我希望预测结果为概率。此外,还有一个weights参数可用于分配不同的系数,从而获得更准确的模型。更多详细信息,请参阅文档。
文档
VotingClassifier—链接[10]。 VotingRegressor —链接[11]。
7️. Stacking Classifier/Regressor
另一种比投票更强大的集成方法是堆叠。
假设你有五个模型。为了堆叠它们的输出,你需要将它们逐个拟合到训练数据上并生成预测。你将得到五个预测集,并将其与训练集的目标数组合并成一个数据集。这将生成一个包含五个预测作为列、y作为目标数组的新数据集。
然后,你选择一个最终的、完全不同的模型来训练这个“预测数据集”,并对保留集进行预测。
Stacking 背后的理念是,你选择的 1 级模型应该尽可能多样化。不同的模型从不同的角度学习训练集的信息,覆盖整个信息空间。
换句话说,树、线性模型、表面拟合器、基于邻居的模型、贝叶斯和高斯等各种模型最大限度地发挥了训练潜力,它们的组合输出减少了偏差并防止了过度拟合。
Kaggle 表格竞赛中的大多数获胜解决方案都是将几个模型堆叠在一起的。
演示
from sklearn.ensemble import StackingClassifier, StackingRegressor
from sklearn.linear_model import LogisticRegressionX, y = make_classification(n_samples=1000)
ensemble = StackingClassifier(
estimators=[
("xgb", xgb.XGBClassifier(eval_metric="auc")),
("lgbm", lgbm.LGBMClassifier()),
("cb", cb.CatBoostClassifier(verbose=False)),
],
final_estimator=LogisticRegression(),
cv=5,
passthrough=False
# n_jobs=-1,
)
_ = ensemble.fit(X, y)
虽然听起来很复杂,但有了 Sklearn,你很快就能掌握。它的实现方式和投票分类器/回归器一样直观。
文档
StackingClassifier —链接[12]。 StackingRegressor —链接[13]。
8️. LocalOutlierFactor
异常值是机器学习中的一个严重问题。它们会使模型的目标函数产生偏差,并可能导致过于乐观或悲观的结果。
对于小型的玩具数据集,查找异常值并不难。真正的挑战始于包含 50 到 100 个以上特征的数据集。你需要一种能够快速准确地检测高维异常值的算法。即便如此,对于包含数百个特征和数百万行数据的数据集,原始算法可能需要数小时才能运行。
这时,你需要将降维算法与强大的异常值检测器结合起来。我最近喜欢的一个组合是UMAP和LocalOutlierFactor。
UMAP 在降低数据集维度和尽可能多地保留信息方面表现出色。LocalOutlierFactor 是一种基于邻域的算法,旨在快速处理大型数据集。
演示
%%timeimport umap # pip install umap
from sklearn.neighbors import LocalOutlierFactor
X, y = make_classification(n_samples=5000, n_classes=2, n_features=10)
X_reduced = umap.UMAP(n_components=2).fit_transform(X, y)
lof = LocalOutlierFactor()
labels = lof.fit_predict(X_reduced, y)
Wall time: 17.8 s
>>> np.where(labels == -1)
(array([ 119, 155, 303, 331, 333, 407, 418, 549, 599, 664, 795,
3092, 3262, 3271, 3280, 3289, 3311, 3477, 3899, 3929, 3975, 4301,
4358, 4442, 4522, 4561, 4621, 4631, 4989], dtype=int64),)
使用 UMAP 减少 10 个特征数据集的维度,然后使用 LocalOutlierFactor 执行异常值检测。
如果这个演示不是完全可以理解的,我还有一篇关于这个主题的单独文章对其进行了深入的介绍:
文档
LocalOutlierFactor —链接[14]。
9️. QuantileTransformer
有时,你会面临形状疯狂的疯狂分布,而对数转换器或缩放器无法强制将其纳入正态分布。
如果你有这样的双峰、三峰或n峰分布,那么让它们尽可能呈正态分布的最佳方法是QuantileTransformer。它使用四分位数和中位数等稳健的统计指标来集中和缩放分布。
演示
import pandas as pd
from sklearn.preprocessing import QuantileTransformerqt = QuantileTransformer().fit(crazy_distributions)
crazy_feature_names = ["f18", "f31", "f61"]
crazy_distributions = pd.DataFrame(qt.transform(crazy_distributions), columns=crazy_feature_names)
fig, axes = plt.subplots(1, 3, figsize=(20, 6))
for ax, f_name in zip(axes.flatten(), crazy_feature_names):
sns.kdeplot(crazy_distributions[f_name], ax=ax, color="#E50914")
文档
QuantileTransformer —链接[15]。
1️0️. PCA + tSNE/UMAP
更多数据并不一定意味着更好的模型。有些数据集实在太大,即使不充分利用它们,也能取得不错的效果。但如果你不方便舍弃部分数据,我建议使用降维技术将数据投影到更低的空间。
模型性能的提高并不能保证,但从长远来看,你可以在较小的数据集上运行更多的实验,因为你的 RAM 使用率会更低,计算时间也会更短。
但问题是,如果数据集中包含大量特征,高质量的降维操作可能需要耗时过长。你不可能一次就成功,因此更多的实验会更加耗时。
这就是为什么 Sklearn 文档建议将降维算法与 PCA(主成分分析)相结合。
PCA 适用于任意维度,速度很快,因此非常适合用于第一阶段的降维。建议先使用 PCA 将数据投影到合理的维度,例如 30-50 个维度,然后再使用其他算法(例如 tSNE 或 UMAP)进一步降维。
以下是 PCA 和 tSNE 的组合:
演示
from sklearn.decomposition import PCA
from sklearn.manifold import TSNEdf = dt.fread("data/large.csv").to_pandas()
>>> df.shape
(1000000, 287)
X, y = df.drop("target", axis=1), df[["target"]].values.flatten()
%%time
manifold_pipe = make_pipeline(QuantileTransformer(), PCA(n_components=30), TSNE())
reduced_X = manifold_pipe.fit_transform(X, y)
------------------------------------------
Wall time: 4h 27min 46s
view raw9614.py hosted with ❤ by GitHub
使用 PCA 和 tSNE 的组合,将具有 300 个特征的合成生成的数据集减少到仅 2 个。
在一个包含 100 万行数据和约 300 个特征的合成数据集上,将数据投影到前 30 个维度,然后再投影到两个维度,耗时 4.5 小时。可惜的是,结果并不理想:
>>> plt.scatter(reduced_X[:, 0], reduced_X[:, 1], c=y, s=0.05);
这就是我推荐使用 UMAP 的原因。它比 tSNE 快得多,而且能更好地保留数据的局部结构:
%%time
manifold_pipe = make_pipeline(QuantileTransformer(), PCA(n_components=30))X_pca = manifold_pipe.fit_transform(X, y)
embedding = umap.UMAP(n_components=2).fit(X_pca, y)
Wall time: 14min 27s
>>> plt.scatter(embedding.embedding_[:, 0], embedding.embedding_[:, 1], c=y, s=0.05);
UMAP 成功地找到了目标类别之间的明显区别,而且速度比 tSNE 快 20 倍。
文档
PCA —链接[16]。 tSNE —链接[17]。 UMAP —链接[18]。
写在最后
在人工智能时代,人们很容易将注意力转移到语言转换器之类的花哨模型上。然而,它们只适用于自然语言处理——经典的机器学习仍然掌握在 Scikit-learn 这样的巨头手中。
本文中库的特性可以作为常见功能的额外提升,从而提高生产力、可读性并减少代码量。
FunctionTransformer: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html#sklearn.preprocessing.FunctionTransformer
[2]BaseEstimator: https://scikit-learn.org/stable/modules/generated/sklearn.base.BaseEstimator.html#sklearn.base.BaseEstimator
[3]TransformerMixin: https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html
[4]TransformedTargetregressor: https://scikit-learn.org/stable/modules/generated/sklearn.compose.TransformedTargetRegressor.html#sklearn.compose.TransformedTargetRegressor
[5]sklearn.set_config: https://scikit-learn.org/stable/modules/generated/sklearn.set_config.html
[6]sklearn.utils.estimator_html_repr: https://scikit-learn.org/stable/modules/generated/sklearn.utils.estimator_html_repr.html
[7]Kaggle即时满足竞赛: https://www.kaggle.com/c/instant-gratification/overview
[8]Chris Deotte笔记: https://www.kaggle.com/cdeotte/pseudo-labeling-qda-0-969/notebook
[9]QuadraticDiscriminantAnalysis: https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.QuadraticDiscriminantAnalysis.html#sklearn.discriminant_analysis.QuadraticDiscriminantAnalysis
[10]VotingClassifier: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingClassifier.html#sklearn.ensemble.VotingClassifier
[11]VotingRegressor: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingRegressor.html
[12]StackingClassifier: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.StackingClassifier.html#sklearn.ensemble.StackingClassifier
[13]StackingRegressor: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.StackingRegressor.html
[14]LocalOutlierFactor: https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.LocalOutlierFactor.html#sklearn.neighbors.LocalOutlierFactor
[15]QuantileTransformer: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html#sklearn.preprocessing.QuantileTransformer
[16]PCA: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html
[17]tSNE: https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html#sklearn.manifold.TSNE
[18]UMAP: https://umap-learn.readthedocs.io/