Skip to main content
Ctrl+K
scikit-learn homepage
  • 安装
  • 用户指南
  • API
  • 示例
  • 社区
    • 入门
    • 解除历史记录
    • 术语表
    • 发展
    • FAQ
    • 支持
    • 相关项目
    • 路线图
    • 治理
    • 关于我们
  • GitHub
  • 安装
  • 用户指南
  • API
  • 示例
  • 社区
    • 入门
    • 解除历史记录
    • 术语表
    • 发展
    • FAQ
    • 支持
    • 相关项目
    • 路线图
    • 治理
    • 关于我们
  • GitHub

Section Navigation

  • 1. 监督学习
    • 1.1. 线性模型
    • 1.2. 线性和二次鉴别分析
    • 1.3. 核岭回归
    • 1.4. 支持向量机
    • 1.5. 随机梯度下降
    • 1.6. 最近邻居
    • 1.7. 高斯过程
    • 1.8. 交叉分解
    • 1.9. 朴素贝叶斯
    • 1.10. 决策树
    • 1.11. 合奏:梯度提升、随机森林、装袋、投票、堆叠
    • 1.12. 多类和多输出算法
    • 1.13. 特征选择
    • 1.14. 半监督学习
    • 1.15. 保序回归
    • 1.16. 概率定标
    • 1.17. 神经网络模型(监督)
  • 2. 无监督学习
    • 2.1. 高斯混合模型
    • 2.2. 流形学习
    • 2.3. 聚类
    • 2.4. 双聚类
    • 2.5. 将信号分解为分量(矩阵分解问题)
    • 2.6. 协方差估计
    • 2.7. 新颖性和异常值检测
    • 2.8. 密度估计
    • 2.9. 神经网络模型(无监督)
  • 3. 模型选择与评估
    • 3.1. 交叉验证:评估估计器性能
    • 3.2. 调整估计器的超参数
    • 3.3. 调整类别预测的决策阈值
    • 3.4. 预设和评分:量化预测的质量
    • 3.5. 验证曲线:绘制分数以评估模型
  • 4. 元数据路由
  • 5. 检查
    • 5.1. 部分依赖和个人条件期望图
    • 5.2. 排列特征重要性
  • 6. 可视化
  • 7. 数据集转换
    • 7.1. 管道和复合估计量
    • 7.2. 特征提取
    • 7.3. 预处理数据
    • 7.4. 插补缺失值
    • 7.5. 无监督降维
    • 7.6. 随机投影
    • 7.7. 核近似
    • 7.8. 成对指标、亲和力和核心
    • 7.9. 转换预测目标 (y )
  • 8. 数据集加载实用程序
    • 8.1. 玩具数据集
    • 8.2. 真实世界的数据集
    • 8.3. 生成的数据集
    • 8.4. 加载其他数据集
  • 9. 使用scikit-learn进行计算
    • 9.1. 计算扩展策略:更大的数据
    • 9.2. 计算性能
    • 9.3. 并行主义、资源管理和配置
  • 10. 模型持久性
  • 11. 常见陷阱和建议做法
  • 12. 调度
    • 12.1. 数组API支持(实验性)
  • 13. 选择正确的估计器
  • 14. 外部资源、视频和讲座
  • 用户指南

11. 常见陷阱和建议做法#

本章的目的是说明使用scikit-learn时出现的一些常见陷阱和反模式。它提供了一些例子 not 要做的事情,以及相应的正确例子。

11.1. 预处理不一致#

scikit-learn提供了一个 数据集转换 ,可以清洁(请参阅 预处理数据 )、减少(请参阅 无监督降维 ),扩展(请参阅 核近似 )或生成(请参阅 特征提取 )特征表示。如果在训练模型时使用这些数据转换,则它们还必须用于后续数据集,无论是测试数据还是生产系统中的数据。否则,特征空间就会发生变化,模型将无法有效执行。

对于以下示例,让我们创建具有单个特征的合成数据集::

>>> from sklearn.datasets import make_regression
>>> from sklearn.model_selection import train_test_split

>>> random_state = 42
>>> X, y = make_regression(random_state=random_state, n_features=1, noise=1)
>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, test_size=0.4, random_state=random_state)

Wrong

训练数据集已缩放,但测试数据集未缩放,因此测试数据集上的模型性能比预期差::

>>> from sklearn.metrics import mean_squared_error
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.preprocessing import StandardScaler

>>> scaler = StandardScaler()
>>> X_train_transformed = scaler.fit_transform(X_train)
>>> model = LinearRegression().fit(X_train_transformed, y_train)
>>> mean_squared_error(y_test, model.predict(X_test))
62.80...

Right

而不是通过未变形的 X_test 到 predict ,我们应该像转换训练数据一样转换测试数据::

>>> X_test_transformed = scaler.transform(X_test)
>>> mean_squared_error(y_test, model.predict(X_test_transformed))
0.90...

或者,我们建议使用 Pipeline ,这使得将变换与估计器链接起来变得更容易,并减少忘记变换的可能性::

>>> from sklearn.pipeline import make_pipeline

>>> model = make_pipeline(StandardScaler(), LinearRegression())
>>> model.fit(X_train, y_train)
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('linearregression', LinearRegression())])
>>> mean_squared_error(y_test, model.predict(X_test))
0.90...

管道还有助于避免另一个常见陷阱:将测试数据泄露到训练数据中。

11.2. 数据泄露#

当构建模型时使用预测时不可用的信息时,就会发生数据泄露。这会导致过度乐观的性能估计,例如来自 cross-validation ,因此当模型用于实际新颖的数据时(例如在生产期间),性能较差。

A common cause is not keeping the test and train data subsets separate. Test data should never be used to make choices about the model. The general rule is to never call fit on the test data. While this may sound obvious, this is easy to miss in some cases, for example when applying certain pre-processing steps.

尽管训练和测试数据子集都应该接受相同的预处理转换(如前一节所述),但重要的是,这些转换只能从训练数据中学习。例如,如果您有一个标准化步骤,其中您将除以平均值,则平均值应该是火车子集的平均值, not 所有数据的平均值。如果测试子集包含在平均计算中,则来自测试子集的信息将影响模型。

11.2.1. 如何避免数据泄露#

以下是一些避免数据泄露的提示:

  • 始终首先将数据拆分为训练和测试子集,尤其是在任何预处理步骤之前。

  • Never include test data when using the fit and fit_transform methods. Using all the data, e.g., fit(X), can result in overly optimistic scores.

    相反, transform 该方法应用于训练和测试子集,因为应对所有数据应用相同的预处理。这可以通过使用来实现 fit_transform 在火车子集上和 transform 在测试子集上。

  • scikit-learn pipeline 是防止数据泄露的好方法,因为它可以确保对正确的数据子集执行适当的方法。该管道非常适合用于交叉验证和超参数调优功能。

下面详细介绍了预处理期间数据泄露的示例。

11.2.2. 预处理期间数据泄露#

备注

我们在这里选择用特征选择步骤来说明数据泄漏。然而,这种泄漏风险与scikit-learn中几乎所有的转换都相关,包括(但不限于) StandardScaler , SimpleImputer ,而且 PCA .

一些 特征选择 scikit-learn中提供了功能。它们可以帮助删除不相关、多余和有噪音的功能,并改善您的模型构建时间和性能。与任何其他类型的预处理一样,特征选择应该 only 使用训练数据。将测试数据包括在特征选择中将乐观地偏向您的模型。

为了演示,我们将用10,000个随机生成的特征创建这个二进制分类问题::

>>> import numpy as np
>>> n_samples, n_features, n_classes = 200, 10000, 2
>>> rng = np.random.RandomState(42)
>>> X = rng.standard_normal((n_samples, n_features))
>>> y = rng.choice(n_classes, n_samples)

Wrong

使用所有数据来执行特征选择会导致准确性分数远高于概率,尽管我们的目标是完全随机的。这种随机性意味着我们 X 和 y 是独立的,因此我们预计准确性在0.5左右。然而,由于特征选择步骤“看到”测试数据,因此该模型具有不公平的优势。在下面的错误示例中,我们首先使用所有数据进行特征选择,然后将数据拆分为训练和测试子集以进行模型匹配。结果是比预期高得多的准确性分数::

>>> from sklearn.model_selection import train_test_split
>>> from sklearn.feature_selection import SelectKBest
>>> from sklearn.ensemble import HistGradientBoostingClassifier
>>> from sklearn.metrics import accuracy_score

>>> # Incorrect preprocessing: the entire data is transformed
>>> X_selected = SelectKBest(k=25).fit_transform(X, y)

>>> X_train, X_test, y_train, y_test = train_test_split(
...     X_selected, y, random_state=42)
>>> gbc = HistGradientBoostingClassifier(random_state=1)
>>> gbc.fit(X_train, y_train)
HistGradientBoostingClassifier(random_state=1)

>>> y_pred = gbc.predict(X_test)
>>> accuracy_score(y_test, y_pred)
0.76

Right

为了防止数据泄露,最好将数据拆分为训练和测试子集 first .然后,只需使用火车数据集即可形成特征选择。请注意,每当我们使用 fit 或 fit_transform ,我们只使用火车数据集。现在的分数是我们对数据的预期,接近机会::

>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, random_state=42)
>>> select = SelectKBest(k=25)
>>> X_train_selected = select.fit_transform(X_train, y_train)

>>> gbc = HistGradientBoostingClassifier(random_state=1)
>>> gbc.fit(X_train_selected, y_train)
HistGradientBoostingClassifier(random_state=1)

>>> X_test_selected = select.transform(X_test)
>>> y_pred = gbc.predict(X_test_selected)
>>> accuracy_score(y_test, y_pred)
0.5

在这里,我们再次建议使用 Pipeline 将特征选择和模型估计器链接在一起。管道确保执行时仅使用训练数据 fit 且测试数据仅用于计算准确度评分::

>>> from sklearn.pipeline import make_pipeline
>>> X_train, X_test, y_train, y_test = train_test_split(
...     X, y, random_state=42)
>>> pipeline = make_pipeline(SelectKBest(k=25),
...                          HistGradientBoostingClassifier(random_state=1))
>>> pipeline.fit(X_train, y_train)
Pipeline(steps=[('selectkbest', SelectKBest(k=25)),
                ('histgradientboostingclassifier',
                 HistGradientBoostingClassifier(random_state=1))])

>>> y_pred = pipeline.predict(X_test)
>>> accuracy_score(y_test, y_pred)
0.5

管道还可以输入交叉验证功能,例如 cross_val_score .同样,管道确保在匹配和预测期间使用正确的数据子集和估计器方法:

>>> from sklearn.model_selection import cross_val_score
>>> scores = cross_val_score(pipeline, X, y)
>>> print(f"Mean accuracy: {scores.mean():.2f}+/-{scores.std():.2f}")
Mean accuracy: 0.43+/-0.05

11.3. 控制随机性#

一些scikit学习对象本质上是随机的。这些通常是估计量(例如 RandomForestClassifier )和交叉验证拆分器(例如 KFold ).这些物体的随机性是通过它们的 random_state 参数,如 Glossary .本节将详细介绍术语表条目,并描述良好实践和常见陷阱。这个微妙的参数。

备注

建议摘要

为了获得交叉验证(CV)结果的最佳稳健性,通过 RandomState 创建估计值时的实例,或离开 random_state 到 None .将整数传递给CV拆分器通常是最安全的选择,也是最好的选择;传递 RandomState 拆分器的实例有时可能有助于实现非常特定的用例。对于估计器和拆分器来说,传递一个整点与传递一个实例(或 None )导致微妙但显着的差异,尤其是对于CV程序。在报告结果时了解这些差异非常重要。

对于执行过程中的可重复结果,请删除任何使用 random_state=None .

11.3.1. 使用 None 或 RandomState 实例,并反复调用 fit 和 split#

的 random_state 参数确定是否多次调用 fit (for估计者)或到 split (for根据以下规则,CV拆分器)将产生相同的结果:

  • 如果传递了一个integer,则调用 fit 或 split 多次总是产生相同的结果。

  • 如果 None 或 RandomState 实例已传递: fit 和 split 每次被调用时都会产生不同的结果,并且连续的调用探索了所有信息的来源。 None 是所有的默认值 random_state 参数

我们在这里说明了估计器和CV拆分器的这些规则。

备注

Since passing random_state=None is equivalent to passing the global RandomState instance from numpy (random_state=np.random.mtrand._rand), we will not explicitly mention None here. Everything that applies to instances also applies to using None.

11.3.1.1. 估计#

传递实例意味着调用 fit 即使估计器在相同的数据上并使用相同的超参数进行了匹配,多次也不会产生相同的结果::

>>> from sklearn.linear_model import SGDClassifier
>>> from sklearn.datasets import make_classification
>>> import numpy as np

>>> rng = np.random.RandomState(0)
>>> X, y = make_classification(n_features=5, random_state=rng)
>>> sgd = SGDClassifier(random_state=rng)

>>> sgd.fit(X, y).coef_
array([[ 8.85418642,  4.79084103, -3.13077794,  8.11915045, -0.56479934]])

>>> sgd.fit(X, y).coef_
array([[ 6.70814003,  5.25291366, -7.55212743,  5.18197458,  1.37845099]])

从上面的片段我们可以看到,反复调用 sgd.fit 产生了不同的模型,即使数据是相同的。这是因为估计器的随机数生成器(RNG)在以下情况下被消耗(即突变): fit 被调用,并且这个突变的RNG将用于后续的调用 fit .此外该 rng 对象在所有使用它的对象之间共享,因此,这些对象变得有些相互依赖。例如,两个共享相同的估计量 RandomState 实例会相互影响,正如我们稍后在讨论克隆时所看到的那样。调试时要记住这一点很重要。

如果我们向 random_state 参数 SGDClassifier ,我们会获得相同的模型,因此每次都会获得相同的分数。当我们传递一个整除时,所有调用都会使用相同的RNG fit .内部发生的情况是,即使RNG被消耗时 fit 被调用时,它总是在开始时重置为其原始状态 fit .

11.3.1.2. CV分配器#

Randomized CV splitters have a similar behavior when a RandomState instance is passed; calling split multiple times yields different data splits:

>>> from sklearn.model_selection import KFold
>>> import numpy as np

>>> X = y = np.arange(10)
>>> rng = np.random.RandomState(0)
>>> cv = KFold(n_splits=2, shuffle=True, random_state=rng)

>>> for train, test in cv.split(X, y):
...     print(train, test)
[0 3 5 6 7] [1 2 4 8 9]
[1 2 4 8 9] [0 3 5 6 7]

>>> for train, test in cv.split(X, y):
...     print(train, test)
[0 4 6 7 8] [1 2 3 5 9]
[1 2 3 5 9] [0 4 6 7 8]

我们可以看到这次分裂与第二次不同 split 被称为。如果您通过调用比较多个估计器的性能,这可能会导致意外结果 split 很多次,正如我们将在下一节中看到的那样。

11.3.2. 常见陷阱和微妙之处#

而管理的规则 random_state 参数看起来很简单,但确实有一些微妙的含义。在某些情况下,这甚至可能导致错误的结论。

11.3.2.1. 估计#

Different `random_state` types lead to different cross-validation procedures

取决于类型 random_state 参数,估计器的行为会有所不同,尤其是在交叉验证过程中。考虑以下片段::

>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.datasets import make_classification
>>> from sklearn.model_selection import cross_val_score
>>> import numpy as np

>>> X, y = make_classification(random_state=0)

>>> rf_123 = RandomForestClassifier(random_state=123)
>>> cross_val_score(rf_123, X, y)
array([0.85, 0.95, 0.95, 0.9 , 0.9 ])

>>> rf_inst = RandomForestClassifier(random_state=np.random.RandomState(0))
>>> cross_val_score(rf_inst, X, y)
array([0.9 , 0.95, 0.95, 0.9 , 0.9 ])

We see that the cross-validated scores of rf_123 and rf_inst are different, as should be expected since we didn't pass the same random_state parameter. However, the difference between these scores is more subtle than it looks, and the cross-validation procedures that were performed by cross_val_score significantly differ in each case:

  • 以来 rf_123 每次调用都传递了一个整数 fit 使用相同的RNG:这意味着随机森林估计器的所有随机特征对于CV程序的5个折叠中的每一个都是相同的。特别是,估计器的(随机选择的)特征子集在所有折叠中都是相同的。

  • 以来 rf_inst 被通过了 RandomState 例如,每次调用 fit 从不同的RNG开始。因此,特征的随机子集对于每个折叠来说都是不同的。

虽然在折叠中使用恒定估计器RNG本质上并不是错误的,但我们通常希望获得可靠的CV结果。估计者的随机性。因此,传递实例而不是整个可能是更好的,因为这将允许估计器RNG针对每个折叠而变化。

备注

在这里, cross_val_score 将使用非随机CV拆分器(默认情况),因此将在相同的拆分上评估两个估计量。本节不是关于分裂的变异性。此外,无论我们传递一个integer还是一个实例 make_classification 与我们的说明目的无关:重要的是我们传递给 RandomForestClassifier 估计者。

克隆#

传球的另一个微妙副作用 RandomState 实例是如何 clone 将工作::

>>> from sklearn import clone
>>> from sklearn.ensemble import RandomForestClassifier
>>> import numpy as np

>>> rng = np.random.RandomState(0)
>>> a = RandomForestClassifier(random_state=rng)
>>> b = clone(a)

由于 RandomState 实例已传递给 a , a 和 b 不是严格意义上的克隆,而是统计意义上的克隆: a 和 b 仍然是不同的模型,即使在调用 fit(X, y) 基于相同的数据。此外, a 和 b 将相互影响,因为它们共享相同的内部RNG:呼叫 a.fit 将消耗 b 的RNG,并呼叫 b.fit 将消耗 a 是RNG,因为它们是一样的。对于任何共享一个 random_state 参数;它不特定于克隆。

如果传递了一个整词, a 和 b 将是精确的克隆,它们不会相互影响。

警告

即使 clone 很少在用户代码中使用,它在整个scikit-learn代码库中普遍被调用:特别是,大多数接受非匹配估计量的元估计量调用 clone 境内 (GridSearchCV , StackingClassifier , CalibratedClassifierCV 等)。

11.3.2.2. CV分配器#

当经过一个 RandomState 例如,CV拆分器每次都会产生不同的拆分 split 被称为。当比较不同的估计器时,这可能会导致高估估计器之间性能差异的方差::

>>> from sklearn.naive_bayes import GaussianNB
>>> from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
>>> from sklearn.datasets import make_classification
>>> from sklearn.model_selection import KFold
>>> from sklearn.model_selection import cross_val_score
>>> import numpy as np

>>> rng = np.random.RandomState(0)
>>> X, y = make_classification(random_state=rng)
>>> cv = KFold(shuffle=True, random_state=rng)
>>> lda = LinearDiscriminantAnalysis()
>>> nb = GaussianNB()

>>> for est in (lda, nb):
...     print(cross_val_score(est, X, y, cv=cv))
[0.8  0.75 0.75 0.7  0.85]
[0.85 0.95 0.95 0.85 0.95]

直接比较 LinearDiscriminantAnalysis 估计者与 GaussianNB 估计器 on each fold 那将是一个错误: the splits on which the estimators are evaluated are different .事实上, cross_val_score 将在内部调用 cv.split 在同一 KFold 例如,但每次拆分都会不同。对于任何通过交叉验证执行模型选择的工具来说,这也是如此,例如 GridSearchCV 和 RandomizedSearchCV :不同电话中的分数不具有可比性 search.fit ,自从 cv.split 会被多次呼叫。只需一次通话即可 search.fit 然而,由于搜索估计器仅调用 cv.split 一次

对于所有场景下的可比折叠结果,应向CV拆分器传递一个整数: cv = KFold(shuffle=True, random_state=0) .

备注

虽然不建议进行折叠比较 RandomState 然而,在实例中,只要使用足够的折叠和数据,平均分数就可以得出一个估计器是否比另一个估计器更好的结论。

备注

在这个例子中重要的是传递给了什么 KFold .无论我们通过一个 RandomState 实例或一个整个 make_classification 与我们的说明目的无关。此外,两者都没有 LinearDiscriminantAnalysis 也不 GaussianNB 是随机估计者。

11.3.3. 一般性建议#

11.3.3.1. 在多次执行中获得可重复的结果#

为了获得多个可重复(即恒定)的结果 program executions ,我们需要删除所有使用 random_state=None ,这是默认值。建议的方式是声明 rng 程序顶部的变量,并将其传递给任何接受 random_state 参数::

>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.datasets import make_classification
>>> from sklearn.model_selection import train_test_split
>>> import numpy as np

>>> rng = np.random.RandomState(0)
>>> X, y = make_classification(random_state=rng)
>>> rf = RandomForestClassifier(random_state=rng)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y,
...                                                     random_state=rng)
>>> rf.fit(X_train, y_train).score(X_test, y_test)
0.84

现在,无论我们运行它多少次,我们都可以保证该脚本的结果始终为0.84。更改全局 rng 正如预期的那样,变量为不同值应该会影响结果。

也可以宣布 rng 变量为一个整数。然而,这可能会导致交叉验证结果不那么稳健,正如我们将在下一节中看到的那样。

备注

我们不建议将全局 numpy seed by calling np.random.seed(0). See here 进行讨论。

11.3.3.2. 交叉验证结果的稳健性#

当我们通过交叉验证评估随机估计器的性能时,我们希望确保估计器能够对新数据产生准确的预测,但我们也希望确保估计器在w.r.t.时是稳健的。其随机初始化。例如,我们希望初始化 SGDClassifier 在所有方面始终保持良好状态:否则,当我们根据新数据训练估计器时,我们可能会变得不幸,并且随机初始化可能会导致性能不佳。类似地,我们希望随机森林在w.r.t.时具有稳健性。每棵树将使用的一组随机选择的特征。

出于这些原因,最好通过让估计器在每个折叠上使用不同的RNG来评估交叉验证性能。这是通过传递 RandomState 实例(或 None )到估计器初始化。

当我们传递一个整元时,估计器将在每个倍数上使用相同的RNG:如果估计器表现良好(或差)(如CV所评估的那样),那可能只是因为我们对该特定种子很幸运(或不幸)。传递实例会产生更稳健的CV结果,并使各种算法之间的比较更加公平。它还有助于限制将估计器的RNG视为可以调整的超参数的诱惑。

无论我们通过 RandomState CV拆分器的实例或整数对稳健性没有影响,只要 split 只被呼叫一次。当 split 被多次调用,则不再可能进行折叠间比较。因此,向CV拆分器传递integer通常更安全,并且涵盖大多数用例。

上一页

10. 模型持久性

下一页

12. 调度

On this page
  • 11.1. 预处理不一致
  • 11.2. 数据泄露
    • 11.2.1. 如何避免数据泄露
    • 11.2.2. 预处理期间数据泄露
  • 11.3. 控制随机性
    • 11.3.1. 使用 None 或 RandomState 实例,并反复调用 fit 和 split
      • 11.3.1.1. 估计
      • 11.3.1.2. CV分配器
    • 11.3.2. 常见陷阱和微妙之处
      • 11.3.2.1. 估计
      • 11.3.2.2. CV分配器
    • 11.3.3. 一般性建议
      • 11.3.3.1. 在多次执行中获得可重复的结果
      • 11.3.3.2. 交叉验证结果的稳健性

© Copyright 2007 - 2025, scikit-learn developers (BSD License).