摘要:经过多年的使用和查阅 Sklearn 的API reference[1],我意识到最常用的模型和函数只是该库所能实现功能的一小部分。尽管有些功能的使用范围非常窄,而且只适用于极少数的边缘情况,但我还是发现了许多估算器、转换器和实用函数,它们对人们手动进行的常见
经过多年的使用和查阅 Sklearn 的API reference[1],我意识到最常用的模型和函数只是该库所能实现功能的一小部分。尽管有些功能的使用范围非常窄,而且只适用于极少数的边缘情况,但我还是发现了许多估算器、转换器和实用函数,它们对人们手动进行的常见操作进行了更优雅的修复。
因此,我决定列出一份最优雅、最重要的功能列表,并对其进行简要说明,这样你就可以在一篇文章中大大扩展你的 Sklearn 工具集。敬请欣赏!
分布中存在离群值是很常见的。许多算法都会处理异常值,EllipticEnvelope就是 Sklearn 直接内置的一个例子。这种算法的优势在于,它在检测正态分布(高斯)特征中的异常值方面表现出色:import numpy as npfrom 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方法传递一些随机数。测试 "中的异常值为 20、10、13,该方法返回-1。选择对预测帮助最大的特征是对抗过度拟合和降低模型复杂度的必要步骤。Sklearn 提供的最强大的算法之一是递归特征消除(RFE)。它通过交叉验证自动找出最重要的特征,并舍弃其余特征。
这种估计器的优势在于它是一个封装器--可以用于任何返回特征重要性或系数分数的 Sklearn 算法。下面是一个合成数据集的示例:
from sklearn.datasets import make_regressionfrom 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 个不必要的特征。尽管随机森林算法非常强大,但过度拟合的风险也非常高。因此,Sklearn提供了一种可直接替代RF的方法,称为ExtraTrees(分类器和回归器)。Extra一词并不是指更多的树,而是指更多的随机性。该算法使用另一种类型的树,近似于决策树。唯一不同的是,它不是在构建每棵树时计算分割阈值,而是针对每个特征随机抽取这些阈值,并选择最佳阈值作为分割规则。这样做的代价是略微增加了偏差,从而降低了方差:
from sklearn.ensemble import ExtraTreesRegressor, RandomForestRegressorfrom 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.mean
0.8446103607404536
# ExtraTrees
clf = ExtraTreesRegressor(
)
>>> scores.mean
0.8737373931608834
比较RandomForest和ExtraTreesRegressor在合成数据集上的性能。ExtraTrees取得了胜利!的表现优于随机森林。阅读官方ExtraTreesRegressor用户指南[2]中有关的更多信息。
impute.IterativeImputer 和 KNNImputer
如果你正在寻找比SimpleImputer更强大、更先进的归因技术,Sklearn 会再次为你提供帮助。sklearn.imputute子包包括两种基于模型的估算算法--KNNImputer和顾名思义,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 作为基于模型的估算技术来适当填补缺失值。
一种更稳健的算法是 “迭代计算机”。它通过将每个特征的缺失值建模为其余特征的函数来查找缺失值。这一过程以逐步循环的方式进行。每一步都会选择一个有缺失值的特征作为目标(y),并选择其他特征作为特征阵列(X)。然后,使用一个回归器来预测y中的缺失值,每个特征的这一过程一直持续到max_iter次(IterativeImputer的参数)。因此,一个缺失值会产生多个预测值。这样做的好处是可以将每个缺失值视为随机变量,并将其固有的不确定性联系起来:
from sklearn.experimental import enable_iterative_imputerfrom 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 的性能更好。
离群值的存在会严重影响任何模型的预测结果。许多离群值检测算法会丢弃离群值并将其标记为缺失。虽然这有助于模型的学习功能,但却完全消除了异常值对分布的影响。
另一种算法是HuberRegressor。它不是完全去除异常值,而是在拟合过程中降低异常值的权重。它有一个epsilon超参数,用于控制应被归类为异常值的样本数量。参数越小,模型对异常值的鲁棒性越强。其 API 与其他线性回归器相同。下面是贝叶斯岭回归器与严重异常值数据集的 huber_vs_ridge比较[3]为 1.35、1.5、1.75 的HuberRegressor能够捕捉到不受异常值影响的最佳拟合线。你可以从HuberRegressor用户指南[4]中了解更多关于该算法的信息。plot_tree函数绘制单个决策树的结构图。对于刚刚开始学习基于树的模型和集合模型的初学者来说,这个功能可能会很方便:from sklearn.datasets import load_irisfrom 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);
plot_tree函数可视化决策树。还有其他绘制树木的方法,例如 Graphviz 格式。请参阅 decision-trees用户指南[5]本列表中最酷的名字是Perceptron,但它只是一个简单的线性二进制分类器。该算法的最大特点是适用于大规模学习,默认情况下,它具有以下特点:
它不需要学习率。
不执行正则化。
仅在错误发生时更新模型。
它等同于 SGDClassifier,其中包含loss='perceptron', eta=1, learning_rate='constant', penalty=None,但速度稍快:from sklearn.datasets import make_classificationfrom 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
展示 Perceptron 在二元分类样本问题上的性能。
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 个冗余特征的合成数据集上尝试使用带有的SelectFromModel估算器。
正如你所看到的,该算法成功地放弃了所有 40 个冗余特征。
混淆矩阵是分类问题的圣杯。大多数指标都是从混淆矩阵中得出的,如精确度、召回率、F1、ROC AUC 等。Sklearn 可以计算并绘制默认的混淆矩阵:
from sklearn.metrics import plot_confusion_matrixfrom 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);
说实话,我并不喜欢默认的混淆矩阵。它的格式是固定的--行是真实标签,列是预测结果。此外,第一行和第一列是负类,第二行和第二列是正类。有些人可能更喜欢不同格式的矩阵,比如转置或翻转。
例如,我喜欢将正分类作为第一行和第一列,以便与维基百科中给出的格式保持一致。这有助于我更好地分离出 4 个矩阵项--TP、FP、TN、FN。幸运的是,你可以使用另一个函数ConfusionMatrixDisplay绘制自定义矩阵:from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrixclf = 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传递给之前,你可以将其转换成任何格式。
如果有其他方法可用于其他类型的分布,那么转换目标(y)使其成为正态分布就没有意义了。
例如,Sklearn 为泊松、特威迪或伽马分布的目标变量提供了三种广义线性模型。PoissonRegressor、TweedieRegressor和GammaRegressor可以为具有相应分布的目标变量生成稳健的结果,而不是期待正态分布。除此之外,它们的 API 与其他 Sklearn 模型相同。要想知道目标的分布是否与上述三种分布相匹配,可以将它们的 PDF(概率密度函数)与完美分布绘制在同一坐标轴上。
例如,要查看目标是否遵循泊松分布,可使用 Seaborn 的kdeplot绘制其 PDF,并使用np.random.poisson由于基于树的模型和集合模型通常能产生更稳健的结果,它们在离群点检测中也被证明是有效的。Sklearn 中的IsolationForest(隔离森林)使用一个由极其随机的树(tree.ExtraTreeRegressor)组成的森林来检测异常值。每棵树都会选择一个特征,并在所选特征的最大值和最小值之间随机选择一个分割值,从而尝试隔离每个样本。这种类型的随机分割会明显缩短每棵树的根节点和终止节点之间的路径。
因此,当一个随机树森林对特定样本产生了较短的路径长度时,这些样本极有可能是异常样本--Sklearn 用户指南。
from sklearn.ensemble import IsolationForestX = 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。
有关该算法的更多信息,请参阅 isolation-forest用户指南[6]许多线性模型都需要对数值特征进行一些转换,以使其呈正态分布。标准缩放器(StandardScaler)和 最小最大缩放器(MinMaxScaler)对大多数分布都很有效。但是,当数据偏度较高时,分布的核心指标,如平均值、中位数、最小值和最大值就会受到影响。因此,简单的归一化和标准化对偏斜分布不起作用。
相反,Sklearn 实现了PowerTransformer,它使用对数变换将任何偏斜特征转化为尽可能接近的正态分布。请看钻石数据集中的这两个特征:import seaborn as snsdiamonds = sns.load_dataset("diamonds")
diamonds[["price", "carat"]].hist(figsize=(10, 5));
png
两者都严重偏斜。让我们用对数变换来解决这个问题:
from sklearn.preprocessing import PowerTransformerpt = PowerTransformer
diamonds.loc[:, ["price", "carat"]] = pt.fit_transform(diamonds[["price", "carat"]])
diamonds[["price", "carat"]].hist(figsize=(10, 5));
Sklearn 中的另一个数字变换器是RobustScaler。从它的名字中,你大概可以猜到它的作用--它可以对异常值进行稳健的特征变换。如果特征中存在异常值,就很难使其呈正态分布,因为它们会严重偏离均值和标准差。RobustScaler不使用均值/标准差,而是使用中位数和 IQR(四分位间距)来缩放数据,因为这两个指标不会因为异常值而产生偏差。你也可以在 RobustScaler用户指南[7]中阅读相关内容。在 Sklearn 中,有一种使用make_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类似的函数--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函数缩短创建对象的代码。正如你所看到的,使用的代码要短得多,而且它可以自行命名每个变换器步骤。如果你注意看的话,我们使用了select_dtypes函数和 pandas DataFrames 的columnsmake_column_selector函数创建了一个列选择器,可以直接传递到实例中。它的工作原理与select_dtypes类似,但效果更好。它具有dtype_includedtype_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)),
)
无需传递列名列表,只需传递带有相关参数的实例即可!初学者常犯的一个错误是使用LabelEncoder来编码序数分类特征。如果你注意到了,LabelEncoder只允许一次转换一列,而不是像OneHotEncoder那样同时转换。你可能会认为 Sklearn 犯了一个错误!实际上,只能用于对其LabelEncoder文档[8]中指定的响应变量(yX)进行编码,应该使用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对序数分类特征进行编码。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函数
model_selection.HalvingGrid 和 HalvingRandomSearchCV
在 0.24 版的 Sklearn 中,我们引入了两个实验性的超参数优化器:HalvingGridSearchCV和HalvingRandomSearchCV类。与它们的穷举同门GridSearch和RandomizedSearch不同,这两个新类使用了一种名为连续减半的技术。该技术不是在所有数据上训练所有候选集(参数组合集),而是只给参数一个数据子集。通过在较小的数据子集上进行训练,筛选出表现最差的候选集。每次迭代后,训练样本都会增加某个系数,可能的候选集数量也会相应减少,从而大大加快了评估时间。快多少?在我进行的实验中,HalvingGridSearch比普通GridSearch快 11 倍,而HalvingRandomSearch甚至比快 10 倍。最后但并非最不重要的一点是,Sklearn 在sklearn.utils子包下有一大堆实用和辅助函数。Sklearn 本身使用该模块中的函数来构建我们使用的所有转换器和估算器。其中有许多有用的函数,如class_weight.compute_class_weight、estimator_html_repr、shuffle、check_X_y等。你可以在自己的工作流程中使用它们,使你的代码更像 Sklearn,或者在创建适合 Sklearn API 的自定义转换器和估算器时派上用场。尽管 CatBoost、XGBoost 和 LightGBM 等库正在慢慢地从 Sklearn 作为领先 ML 库的头把交椅上蚕食一席之地,但它仍然是现代 ML 工程师技能栈中不可多得的一部分。
一致的 API、出色的代码设计以及创建强大的 ML 工作流的能力,使得 Sklearn 在功能性和灵活性方面仍然无与伦比。尽管我们可以通过基础知识完成很多工作,但本文还是向我们展示了 Sklearn 远远不止于此!
参考资料[1]
API reference: https://scikit-learn.org/stable/modules/classes.html#api-reference
[2]
ExtraTreesRegressor用户指南: https://scikit-learn.org/stable/modules/ensemble.html#extremely-randomized-trees
[3]
huber_vs_ridge比较: 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
[4]
HuberRegressor用户指南: https://scikit-learn.org/stable/modules/linear_model.html#huber-regression
[5]
decision-trees用户指南: https://scikit-learn.org/stable/modules/tree.html#decision-trees
[6]
isolation-forest用户指南: https://scikit-learn.org/stable/modules/outlier_detection.html#isolation-forest
[7]
RobustScaler用户指南: https://scikit-learn.org/stable/modules/preprocessing.html#preprocessing-data
[8]
LabelEncoder文档:
来源:一个数据人的自留地