Erstellung eines minimalen Reproduzierers für scikit-learn#

Ob Sie einen Fehlerbericht einreichen, eine Testsuite entwerfen oder einfach eine Frage in den Diskussionen stellen, die Fähigkeit, minimale, reproduzierbare Beispiele (oder minimale, funktionierende Beispiele) zu erstellen, ist der Schlüssel zur effektiven und effizienten Kommunikation mit der Community.

Es gibt sehr gute Richtlinien im Internet, wie z. B. dieses StackOverflow-Dokument oder dieser Blogbeitrag von Matthew Rocklin zur Erstellung von Minimalen vollständigen verifizierbaren Beispielen (im Folgenden als MCVE bezeichnet). Unser Ziel ist es nicht, uns mit diesen Referenzen zu wiederholen, sondern vielmehr eine Schritt-für-Schritt-Anleitung zu geben, wie ein Fehler eingegrenzt werden kann, bis Sie den kürzesten möglichen Code zum Reproduzieren gefunden haben.

Der erste Schritt vor der Einreichung eines Fehlerberichts an scikit-learn ist das Lesen der Issue-Vorlage. Sie ist bereits recht informativ über die Informationen, die Sie angeben müssen.

Gute Praktiken#

In diesem Abschnitt konzentrieren wir uns auf den Abschnitt Schritte/Code zur Reproduktion der Issue-Vorlage. Wir beginnen mit einem Code-Snippet, das bereits ein fehlerhaftes Beispiel liefert, aber noch Raum für Lesbarkeitsverbesserungen hat. Dann erstellen wir daraus ein MCVE.

Beispiel

# 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)

Stellen Sie ein fehlerhaftes Codebeispiel mit minimalen Kommentaren bereit#

Die Anweisungen zur Reproduktion des Problems in englischer Sprache zu schreiben, ist oft mehrdeutig. Stellen Sie besser sicher, dass alle notwendigen Details zur Reproduktion des Problems im Python-Code-Snippet dargestellt sind, um Mehrdeutigkeiten zu vermeiden. Außerdem haben Sie an diesem Punkt bereits eine prägnante Beschreibung im Abschnitt Beschreibung des Fehlers der Issue-Vorlage geliefert.

Der folgende Code ist, obwohl er noch nicht minimal ist, bereits viel besser, weil er in ein Python-Terminal kopiert werden kann, um das Problem in einem Schritt zu reproduzieren. Insbesondere

  • enthält er alle notwendigen Importanweisungen;

  • er kann den öffentlichen Datensatz abrufen, ohne dass eine Datei manuell heruntergeladen und an der erwarteten Stelle auf der Festplatte abgelegt werden muss.

Verbessertes Beispiel

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)

Reduzieren Sie Ihr Skript so weit wie möglich#

Sie müssen sich fragen, welche Codezeilen relevant und welche nicht relevant für die Reproduktion des Fehlers sind. Das Löschen unnötiger Codezeilen oder die Vereinfachung von Funktionsaufrufen durch Weglassen von nicht relevanten nicht standardmäßigen Optionen hilft Ihnen und anderen Mitwirkenden, die Ursache des Fehlers einzugrenzen.

Insbesondere für dieses spezifische Beispiel

  • hat die Warnung nichts mit der train_test_split zu tun, da sie bereits im Trainingsschritt auftritt, bevor wir den Testdatensatz verwenden.

  • ähnlich sind die Zeilen, die die Punktzahlen für den Testdatensatz berechnen, nicht notwendig;

  • der Fehler kann für jeden Wert von random_state reproduziert werden, also lassen Sie ihn auf seinem Standardwert;

  • der Fehler kann ohne Vorverarbeitung der Daten mit dem StandardScaler reproduziert werden.

Verbessertes Beispiel

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

Melden Sie IHRE Daten NICHT, es sei denn, es ist extrem notwendig#

Die Idee ist, den Code so in sich geschlossen wie möglich zu gestalten. Dazu können Sie einen Synthetischen Datensatz verwenden. Er kann mit numpy, pandas oder dem Modul sklearn.datasets generiert werden. Meistens hängt der Fehler nicht mit einer bestimmten Struktur Ihrer Daten zusammen. Selbst wenn, versuchen Sie, einen verfügbaren Datensatz zu finden, der ähnliche Eigenschaften wie Ihre aufweist und das Problem reproduziert. In diesem speziellen Fall interessieren uns Daten mit beschrifteten Merkmalen.

Verbessertes Beispiel

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

Wie bereits erwähnt, ist der Schlüssel zur Kommunikation die Lesbarkeit des Codes und eine gute Formatierung kann wirklich ein Pluspunkt sein. Beachten Sie, dass wir im vorherigen Snippet

  • versuchen, alle Zeilen auf maximal 79 Zeichen zu begrenzen, um horizontale Scrollbalken in den Code-Snippet-Blöcken zu vermeiden, die auf GitHub Issues gerendert werden;

  • Leerzeilen verwenden, um Gruppen zusammengehöriger Funktionen zu trennen;

  • alle Importe am Anfang in ihre eigene Gruppe stellen.

Die in dieser Anleitung vorgestellten Vereinfachungsschritte können in einer anderen Reihenfolge als die hier gezeigte Reihenfolge implementiert werden. Wichtig sind:

  • ein minimaler Reproduzierer sollte durch einfaches Kopieren und Einfügen in ein Python-Terminal ausführbar sein;

  • er sollte so weit wie möglich vereinfacht werden, indem alle Code-Schritte entfernt werden, die nicht unbedingt zur Reproduktion des ursprünglichen Problems erforderlich sind;

  • er sollte idealerweise nur auf einem minimalen Datensatz basieren, der on-the-fly durch Ausführen des Codes generiert wird, anstatt auf externen Daten zu beruhen, wenn möglich.

Markdown-Formatierung verwenden#

Um Code oder Text in einem eigenen Block zu formatieren, verwenden Sie dreifache Backticks. Markdown unterstützt eine optionale Sprachkennung, um die Syntaxhervorhebung in Ihrem abgeschlossenen Codeblock zu aktivieren. Zum Beispiel

```python
from sklearn.datasets import make_blobs

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

wird ein Python-formatiertes Snippet wie folgt gerendert

from sklearn.datasets import make_blobs

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

Es ist nicht notwendig, mehrere Codeblöcke beim Einreichen eines Fehlerberichts zu erstellen. Denken Sie daran, dass andere Gutachter Ihren Code kopieren und einfügen werden, und ein einzelner Block erleichtert ihre Aufgabe.

Im Abschnitt Tatsächliche Ergebnisse der Issue-Vorlage werden Sie gebeten, die Fehlermeldung einschließlich des vollständigen Tracebacks der Ausnahme anzugeben. Verwenden Sie in diesem Fall den Qualifier python-traceback. Zum Beispiel

```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'
```

ergibt beim Rendern Folgendes

---------------------------------------------------------------------------
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'

Synthetischer Datensatz#

Bevor Sie einen bestimmten synthetischen Datensatz auswählen, müssen Sie zunächst die Art des Problems identifizieren, das Sie lösen: Handelt es sich um eine Klassifizierung, eine Regression, ein Clustering usw.?

Sobald Sie die Art des Problems eingegrenzt haben, müssen Sie einen entsprechenden synthetischen Datensatz bereitstellen. Meistens benötigen Sie nur einen minimalistischen Datensatz. Hier ist eine nicht erschöpfende Liste von Werkzeugen, die Ihnen helfen können.

NumPy#

NumPy-Tools wie numpy.random.randn und numpy.random.randint können zur Erstellung von Dummy-Numerikdaten verwendet werden.

  • Regression

    Regressionen nehmen kontinuierliche numerische Daten als Merkmale und Zielwerte entgegen.

    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)
    

Ein ähnliches Snippet kann als synthetische Daten verwendet werden, wenn Skalierungswerkzeuge wie sklearn.preprocessing.StandardScaler getestet werden.

  • Klassifizierung

    Wenn der Fehler nicht beim Kodieren einer kategorialen Variablen auftritt, können Sie numerische Daten an einen Klassifikator übergeben. Denken Sie einfach daran sicherzustellen, dass das Ziel tatsächlich eine Ganzzahl ist.

    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}
    

    Wenn der Fehler nur bei nicht-numerischen Klassenbezeichnungen auftritt, möchten Sie vielleicht ein zufälliges Ziel mit numpy.random.choice generieren.

    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]
    )
    

Pandas#

Einige scikit-learn-Objekte erwarten Pandas-DataFrames als Eingabe. In diesem Fall können Sie NumPy-Arrays mithilfe von pandas.DataFrame oder pandas.Series in Pandas-Objekte umwandeln.

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))

Darüber hinaus enthält scikit-learn verschiedene Generierte Datensätze, die zum Erstellen künstlicher Datensätze mit kontrollierter Größe und Komplexität verwendet werden können.

make_regression#

Wie der Name schon sagt, erzeugt sklearn.datasets.make_regression Regressionsziele mit Rauschen als optional spärliche zufällige Linearkombination von zufälligen Merkmalen.

from sklearn.datasets import make_regression

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

make_classification#

sklearn.datasets.make_classification erstellt Multiklassen-Datensätze mit mehreren Gaußschen Clustern pro Klasse. Rauschen kann durch korrelierte, redundante oder uninformativen Merkmale eingeführt werden.

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#

Ähnlich wie make_classification erzeugt sklearn.datasets.make_blobs Multiklassen-Datensätze unter Verwendung von normalverteilten Punktclustern. Es bietet eine größere Kontrolle über die Zentren und Standardabweichungen jedes Clusters und ist daher nützlich, um Clustering zu demonstrieren.

from sklearn.datasets import make_blobs

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

Dienstprogramme zum Laden von Datensätzen#

Sie können die Dienstprogramme zum Laden von Datensätzen verwenden, um mehrere beliebte Referenzdatensätze zu laden und abzurufen. Diese Option ist nützlich, wenn der Fehler mit der spezifischen Struktur der Daten zusammenhängt, z. B. bei der Behandlung fehlender Werte oder bei der Bilderkennung.

from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True)