为scikit-learn制作一个最小的复制器#

无论是提交bug报告,设计一套测试,还是只是在讨论中提出一个问题,能够制作最小的,可复制的示例(或最小的,可行的示例)是与社区有效沟通的关键。

互联网上有非常好的指导方针,例如 this StackOverflow documentthis blogpost by Matthew Rocklin 关于制作最小完整可验证示例(以下称为MCVE)。我们的目标不是重复这些参考,而是提供有关如何缩小错误范围的分步指南,直到您获得尽可能短的代码来复制它。

向scikit-learn提交错误报告之前的第一步是阅读 Issue template .关于您将被要求提供的信息,它已经相当详细。

良好做法#

在本节中,我们将重点关注 Steps/Code to ReproduceIssue template .我们将从一段代码开始,该代码已经提供了一个失败的示例,但仍有改进可读性的空间。然后我们从中制作MCVE。

Example

# I am currently working in a ML project and when I tried to fit a
# GradientBoostingRegressor instance to my_data.csv I get a UserWarning:
# "X has feature names, but DecisionTreeRegressor was fitted without
# feature names". You can get a copy of my dataset from
# https://example.com/my_data.csv and verify my features do have
# names. The problem seems to arise during fit when I pass an integer
# to the n_iter_no_change parameter.

df = pd.read_csv('my_data.csv')
X = df[["feature_name"]] # my features do have names
y = df["target"]

# We set random_state=42 for the train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.33, random_state=42
)

scaler = StandardScaler(with_mean=False)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# An instance with default n_iter_no_change raises no error nor warnings
gbdt = GradientBoostingRegressor(random_state=0)
gbdt.fit(X_train, y_train)
default_score = gbdt.score(X_test, y_test)

# the bug appears when I change the value for n_iter_no_change
gbdt = GradientBoostingRegressor(random_state=0, n_iter_no_change=5)
gbdt.fit(X_train, y_train)
other_score = gbdt.score(X_test, y_test)

other_score = gbdt.score(X_test, y_test)

提供具有最少注释的失败代码示例#

用英语编写重现问题的说明通常很模糊。最好确保Python代码片段中说明了重现问题的所有必要细节,以避免任何歧义。此外,至此您已经在 Describe the bugIssue template .

下面的代码,而 still not minimal ,已经 much better 因为它可以复制粘贴到Python终端中,一步就能重现问题。特别是:

  • 它包含 all necessary import statements ;

  • 它可以获取公共数据集,而不必手动下载文件并将其放入磁盘上的预期位置。

Improved example

import pandas as pd

df = pd.read_csv("https://example.com/my_data.csv")
X = df[["feature_name"]]
y = df["target"]

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.33, random_state=42
)

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler(with_mean=False)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

from sklearn.ensemble import GradientBoostingRegressor

gbdt = GradientBoostingRegressor(random_state=0)
gbdt.fit(X_train, y_train)  # no warning
default_score = gbdt.score(X_test, y_test)

gbdt = GradientBoostingRegressor(random_state=0, n_iter_no_change=5)
gbdt.fit(X_train, y_train)  # raises warning
other_score = gbdt.score(X_test, y_test)
other_score = gbdt.score(X_test, y_test)

将剧本煮成尽可能小的内容#

您必须问自己哪些代码行是相关的,哪些代码行不用于重现错误。删除不必要的代码行或通过省略不相关的非默认选项来简化函数调用将帮助您和其他贡献者缩小错误的原因。

特别是,对于这个特定的例子:

  • 该警告与 train_test_split 因为在我们使用测试集之前,它已经出现在训练步骤中。

  • 同样,计算测试集分数的线也不是必需的;

  • 该错误可以被复制为任何值 random_state 所以让它默认;

  • 无需使用 StandardScaler .

Improved example

import pandas as pd
df = pd.read_csv("https://example.com/my_data.csv")
X = df[["feature_name"]]
y = df["target"]

from sklearn.ensemble import GradientBoostingRegressor

gbdt = GradientBoostingRegressor()
gbdt.fit(X, y)  # no warning

gbdt = GradientBoostingRegressor(n_iter_no_change=5)
gbdt.fit(X, y)  # raises warning

DO NOT 除非极其必要,否则报告您的数据#

其想法是使代码尽可能独立。为此,您可以使用 合成数据集 .它可以使用numpy、pandas或 sklearn.datasets module.大多数时候,错误与数据的特定结构无关。即使是,也请尝试找到与您的特征相似并且重现问题的可用数据集。在这种特殊情况下,我们对标记了特征名称的数据感兴趣。

Improved example

import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor

df = pd.DataFrame(
    {
        "feature_name": [-12.32, 1.43, 30.01, 22.17],
        "target": [72, 55, 32, 43],
    }
)
X = df[["feature_name"]]
y = df["target"]

gbdt = GradientBoostingRegressor()
gbdt.fit(X, y) # no warning
gbdt = GradientBoostingRegressor(n_iter_no_change=5)
gbdt.fit(X, y) # raises warning

正如已经提到的,沟通的关键是代码的可读性,良好的格式确实是一个优点。请注意,在前面的片段中,我们:

  • 尝试将所有行限制为最多79个字符,以避免在GitHub问题上呈现的代码片段块中出现水平滚动条;

  • 使用白线分隔相关功能组;

  • 一开始将所有导入放入自己的组中。

本指南中介绍的简化步骤可以按照与我们在这里显示的进展不同的顺序实施。要点是:

  • 最小复制器应该可以通过Python终端中的简单复制粘贴来运行;

  • 应该通过删除重现原始问题不严格需要的任何代码步骤来尽可能简化它;

  • 理想情况下,它应该仅依赖通过运行代码实时生成的最小数据集,而不是依赖外部数据(如果可能的话)。

使用降价格式#

要将代码或文本格式化为自己的不同块,请使用三重反勾。 Markdown 支持一个可选的语言标识符,以在您的fenced代码块中启用语法突出显示。例如::

```python
from sklearn.datasets import make_blobs

n_samples = 100
n_components = 3
X, y = make_blobs(n_samples=n_samples, centers=n_components)
```

将呈现Python格式的片段,如下所示

from sklearn.datasets import make_blobs

n_samples = 100
n_components = 3
X, y = make_blobs(n_samples=n_samples, centers=n_components)

在提交bug报告时,没有必要创建多个代码块。记住,其他审阅者会复制粘贴您的代码,使用单个单元格将使他们的任务更容易。

在命名的部分中 Actual resultsIssue template 您被要求提供错误消息,包括异常的完整追溯。在这种情况下,使用 python-traceback 资格赛。例如::

```python-traceback
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-a674e682c281> in <module>
    4 vectorizer = CountVectorizer(input=docs, analyzer='word')
    5 lda_features = vectorizer.fit_transform(docs)
----> 6 lda_model = LatentDirichletAllocation(
    7     n_topics=10,
    8     learning_method='online',

TypeError: __init__() got an unexpected keyword argument 'n_topics'
```

渲染时会产生以下内容:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-a674e682c281> in <module>
    4 vectorizer = CountVectorizer(input=docs, analyzer='word')
    5 lda_features = vectorizer.fit_transform(docs)
----> 6 lda_model = LatentDirichletAllocation(
    7     n_topics=10,
    8     learning_method='online',

TypeError: __init__() got an unexpected keyword argument 'n_topics'

合成数据集#

在选择特定的合成数据集之前,首先您必须确定您正在解决的问题类型:是分类、回归、集群等?

一旦您缩小了问题类型,您就需要相应地提供合成数据集。大多数时候,您只需要一个极简主义的数据集。以下是可能对您有帮助的非详尽工具列表。

NumPy#

NumPy工具,例如 numpy.random.randnnumpy.random.randint 可用于创建虚拟数字数据。

  • 回归

    回归以连续的数字数据作为特征和目标。

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 5, 5
    X = rng.randn(n_samples, n_features)
    y = rng.randn(n_samples)
    

在测试缩放工具(如 sklearn.preprocessing.StandardScaler .

  • 分类

    如果在对类别变量进行编码时没有出现错误,则可以将数字数据提供给分类器。只需记住确保目标确实是一个整数。

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 5, 5
    X = rng.randn(n_samples, n_features)
    y = rng.randint(0, 2, n_samples)  # binary target with values in {0, 1}
    

    如果该错误仅发生在非数字类标签上,则您可能需要生成一个随机目标 numpy.random.choice .

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 50, 5
    X = rng.randn(n_samples, n_features)
    y = np.random.choice(
        ["male", "female", "other"], size=n_samples, p=[0.49, 0.49, 0.02]
    )
    

熊猫#

一些scikit学习对象希望熊猫的预设图像作为输入。在这种情况下,您可以使用以下方法将numpy数组转换为pandas对象 pandas.DataFrame ,或者 pandas.Series .

import numpy as np
import pandas as pd

rng = np.random.RandomState(0)
n_samples, n_features = 5, 5
X = pd.DataFrame(
    {
        "continuous_feature": rng.randn(n_samples),
        "positive_feature": rng.uniform(low=0.0, high=100.0, size=n_samples),
        "categorical_feature": rng.choice(["a", "b", "c"], size=n_samples),
    }
)
y = pd.Series(rng.randn(n_samples))

此外,scikit-learn还包括各种 生成的数据集 它可用于构建大小和复杂性受控的人工数据集。

make_regression#

正如名字所暗示的那样, sklearn.datasets.make_regression 生成带有噪音的回归目标,作为随机特征的可选稀疏随机线性组合。

from sklearn.datasets import make_regression

X, y = make_regression(n_samples=1000, n_features=20)

make_classification#

sklearn.datasets.make_classification 创建每个类具有多个高斯集群的多类数据集。噪音可以通过相关、冗余或无信息特征引入。

from sklearn.datasets import make_classification

X, y = make_classification(
    n_features=2, n_redundant=0, n_informative=2, n_clusters_per_class=1
)

make_blobs#

类似于 make_classification , sklearn.datasets.make_blobs 使用正态分布的点簇创建多类数据集。它对每个聚类的中心和标准差提供了更好的控制,因此它对演示聚类很有用。

from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=10, centers=3, n_features=2)

数据集加载实用程序#

您可以使用 数据集加载实用程序 加载和获取几个流行的参考数据集。当错误与数据的特定结构相关时,该选项很有用,例如处理缺失值或图像识别。

from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True)