Erweiterte Datenvorbereitung mit benutzerdefinierten Transformatoren in Scikit-Learn | von Matt Chapman | Juni 2023

0
28


Gehen Sie über den „Anfängermodus“ hinaus und nutzen Sie die leistungsstärkeren Funktionen von scikit-learn voll aus

Bild von Daniel K Cheung An Unsplash

Scikit-Be taught bietet viele nützliche Instruments zur Datenvorbereitung, aber manchmal reichen die vorgefertigten Optionen nicht aus.

In diesem Artikel zeige ich Ihnen, wie Sie mithilfe benutzerdefinierter Transformer erweiterte Datenvorbereitungsworkflows erstellen. Wenn Sie scikit-learn schon seit einiger Zeit verwenden und Ihre Fähigkeiten verbessern möchten, ist das Erlernen von Transformers eine hervorragende Möglichkeit, über den „Anfängermodus“ hinauszukommen und einige der fortgeschritteneren Funktionen kennenzulernen, die in modernen Information-Science-Groups erforderlich sind .

Wenn das Thema etwas fortgeschritten klingt, machen Sie sich keine Sorgen – dieser Artikel ist vollgepackt mit Beispielen, die Ihnen helfen werden, sowohl mit dem Code als auch mit den Konzepten vertraut zu sein.

Ich beginne mit einem kurzen Überblick über scikit-learn Transformer Klasse und gehen Sie dann zwei Möglichkeiten durch, um benutzerdefinierte Transformer zu erstellen:

  1. Verwendung einer FunctionTransformer
  2. Einen Brauch schreiben Transformer von Grund auf neu

Der Transformer ist einer der zentralen Bausteine ​​von scikit-learn. Es ist in der Tat so grundlegend, dass die Wahrscheinlichkeit groß ist, dass Sie es bereits verwendet haben, ohne es überhaupt zu merken.

In scikit-learn, a Transformer ist irgendein Objekt mit dem match() Und rework() Methoden. Im Klartext bedeutet das, dass ein Transformer eine Klasse (dh ein wiederverwendbarer Codeblock) ist, der Ihren Rohdatensatz als Eingabe verwendet und eine transformierte Model dieses Datensatzes zurückgibt.

Bild vom Autor

Wichtig: Scikit-Be taught Transformers sind NICHT dasselbe wie die „Transformatoren“, die in Giant Language Fashions (LLMs) wie BERT und GPT-4 verwendet werden, oder den über HuggingFace verfügbaren Modellen transformers Bibliothek. Im Kontext von LLMs ist ein „Transformer“ (Kleinbuchstabe „t“) ein Deep-Studying-Modell; ein scikit-lernen Transformer (Großbuchstabe „T“) ist eine völlig andere (und viel einfachere) Entität. Sie können es sich einfach als Werkzeug zur Vorverarbeitung von Daten in einem typischen ML-Workflow vorstellen.

Wenn Sie scikit-learn importieren, erhalten Sie automatischen Zugriff auf eine Reihe vorgefertigter Transformer, die für häufige ML-Datenvorverarbeitungsaufgaben wie die Imputation fehlender Werte, Neuskalierungsfunktionen und One-Scorching-Codierung entwickelt wurden. Zu den beliebtesten Transformers gehören:

  1. sklearn.impute.SimpleImputer – ein Transformer, der fehlende Werte in Ihrem Datensatz ersetzt
  2. sklearn.preprocessing.MinMaxScaler – ein Transformer, der die numerischen Merkmale in Ihrem Datensatz neu skalieren kann
  3. sklearn.preprocessing.OneHotEncoder – ein Transformer für One-Scorching-Codierung kategorialer Funktionen

Mit einem Scikit-Be taught sklearn.pipeline.PipelineSie können sogar mehrere Transformer miteinander verketten, um mehrstufige Datenvorbereitungs-Workflows zur Vorbereitung auf die anschließende ML-Modellierung zu erstellen:

Bild vom Autor

Wenn Sie mit Pipelines oder ColumnTransformern nicht vertraut sind, sind sie eine großartige Möglichkeit, Ihren ML-Code zu vereinfachen. Weitere Informationen dazu finden Sie in meinem vorherigen Artikel:

Gar nichts!

Wenn Sie mit einfachen Datensätzen arbeiten und Standardschritte zur Datenvorbereitung durchführen, sind die vorgefertigten Transformatoren von scikit-learn wahrscheinlich völlig ausreichend. Es ist nicht nötig, das Rad neu zu erfinden, indem man von Grund auf benutzerdefinierte Designs schreibt.

Aber – und seien wir ehrlich – wann sind Datensätze im wirklichen Leben jemals wirklich einfach?

(Spoiler: niemals.)

Wenn Sie mit realen Daten arbeiten oder eine interessante Vorverarbeitungsmethode implementieren müssen, ist die Wahrscheinlichkeit groß, dass die integrierten Transformer von scikit-learn nicht immer ausreichend sind. Früher oder später müssen Sie benutzerdefinierte Datentransformationen implementieren.

Glücklicherweise bietet scikit-learn einige Möglichkeiten, seine grundlegenden Transformer-Funktionen zu erweitern und individuellere Transformer zu erstellen. Um zu zeigen, wie diese funktionieren, verwende ich den kanonischen Titanic Survival Prediction-Datensatz. Selbst bei diesem vermeintlich „einfachen“ Datensatz werden Sie feststellen, dass es reichlich Möglichkeiten gibt, bei der Datenaufbereitung kreativ zu werden. Und wie ich zeigen werde, sind benutzerdefinierte Transformer das ideale Werkzeug für diese Aufgabe.

Laden wir zunächst den Datensatz und teilen ihn in Trainings- und Testteilmengen auf:

import pandas as pd
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split

# Load knowledge and cut up into coaching and testing units
X, y = fetch_openml("titanic", model=1, as_frame=True, return_X_y=True)
X.drop(['boat', 'body', 'home.dest'], axis=1, inplace=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=0.2)
X_train.head()

Bild vom Autor. Titanic-Datensatz eine CC0-Public-Area-Lizenz verfügbar.

Weil sich dieser Artikel auf das Erstellen konzentriert individuell angepasst Transformers, ich werde nicht im Element auf die Standardvorverarbeitungsschritte eingehen, die mithilfe der integrierten Transformers von scikit-learn problemlos auf diesen Datensatz angewendet werden können (z. B. One-Scorching-Codierung kategorialer Variablen wie intercourse Verwendung einer OneHotEncoderoder fehlende Werte durch a ersetzen SimpleImputer).

Stattdessen werde ich mich darauf konzentrieren, wie komplexere Vorverarbeitungsschritte integriert werden können, die mit „handelsüblichen“ Transformern nicht implementiert werden können.

Ein solcher Schritt besteht darin, den Titel jedes Passagiers (z. B. Herr, Frau, Kapitän) aus dem zu extrahieren identify Feld. Warum sollten wir das tun? Nun, wenn wir wissen, dass der Titel jedes Passagiers einen Hinweis auf seine Klasse/Alter/Geschlecht enthält, und wir annehmen, dass diese Faktoren die Fähigkeit der Passagiere, auf Rettungsboote zu gelangen, beeinflusst haben, ist es vernünftig anzunehmen, dass Titel Aufschluss über die Überlebenschancen geben könnten. Beispielsweise könnte ein Passagier mit dem Titel „Grasp“ (der anzeigt, dass er ein Variety ist) eine höhere Überlebenschance haben als ein Passagier mit dem Titel „Mr“ (der anzeigt, dass er ein Erwachsener ist).

Das Drawback besteht natürlich darin, dass es keine eingebaute Scikit-Be taught-Klasse gibt, die etwas so Spezifisches wie das Extrahieren des Titels aus dem tun kann identify Feld. Um die Titel zu extrahieren, müssen wir einen benutzerdefinierten Transformer erstellen.

Der schnellste Weg, einen benutzerdefinierten Transformer zu erstellen, ist die Verwendung von FunctionTransformer Klasse, mit der Sie Transformer direkt aus normalen Python-Funktionen erstellen können.

Um a zu verwenden FunctionTransformerbeginnen Sie mit der Definition einer Funktion, die einen Eingabedatensatz entgegennimmt Xführt die gewünschte Transformation durch und gibt eine transformierte Model von zurück X. Dann packen Sie Ihre Funktion in a FunctionTransformerund scikit-learn erstellt einen angepassten Transformer, der Ihre Funktion implementiert.

Hier ist zum Beispiel eine Funktion, die den Titel eines Passagiers aus dem extrahieren kann identify Feld unseres Titanic-Datensatzes:

from sklearn.preprocessing import FunctionTransformer

def extract_title(X):
"""Extract the title from every passenger's `identify`."""
X['title'] = X['name'].str.cut up(', ', increase=True)[1].str.cut up('.', increase=True)[0]
return X

extract_title_transformer = FunctionTransformer(extract_title)
print(kind(extract_title_transformer))
# <class 'sklearn.preprocessing._function_transformer.FunctionTransformer'>

Wie Sie sehen können, wird die Funktion in a eingeschlossen FunctionTransformer verwandelte es in einen Scikit-Be taught-Transformer und gab ihm das .match() Und .rework() Methoden.

Wir können diesen Transformer dann zusammen mit allen zusätzlichen Vorverarbeitungsschritten/Transformatoren, die wir einbeziehen möchten, in unsere Datenvorbereitungspipeline integrieren:

from sklearn.pipeline import Pipeline

preprocessor = Pipeline(steps=[
('extract_title', extract_title_transformer),
# ... any other transformers we want to include, e.g. SimpleImputer or MinMaxScaler
])

X_train_transformed = preprocessor.fit_transform(X_train)
X_train_transformed

Bild vom Autor

Wenn Sie eine komplexere Funktion/einen komplexeren Transformator definieren möchten, der zusätzliche Argumente akzeptiert, können Sie diese an die Funktion übergeben, indem Sie sie in die Funktion integrieren kw_args Argument von FunctionTransformer. Definieren wir zum Beispiel eine weitere Funktion, die anhand ihres Titels erkennt, ob jeder Passagier aus der Oberschicht/Berufsschicht stammt:

def extract_title(X):
"""Extract the title from every passenger's `identify`."""
X['title'] = X['name'].str.cut up(', ', increase=True)[1].str.cut up('.', increase=True)[0]

def is_upper_class(X, upper_class_titles):
"""If the passenger's title is within the checklist of `upper_class_titles`, return 1, else 0."""
X['upper_class'] = X['title'].apply(lambda x: 1 if x in upper_class_titles else 0)
return X

preprocessor = Pipeline(steps=[
('extract_title', FunctionTransformer(extract_title)),
('is_upper_class', FunctionTransformer(is_upper_class,
kw_args={'upper_class_titles':['Dr', 'Col', 'Major', 'Lady', 'Rev', 'Sir', 'Capt']})),
# ... every other transformers we need to embrace, e.g. SimpleImputer or MinMaxScaler
])

X_train_transformed = preprocessor.fit_transform(X_train)
X_train_transformed

Bild vom Autor

Wie Sie sehen können, verwenden Sie FunctionTransformer ist eine wirklich einfache Möglichkeit, diese komplexen Vorverarbeitungsschritte in eine Pipeline zu integrieren, ohne die Struktur unseres Codes grundlegend zu ändern.

FunctionTransformer ist eine leistungsstarke und elegante Lösung, die jedoch nur dann geeignet ist, wenn Sie sie anwenden möchten staatenlos Transformationen (d. h. regelbasierte Transformationen, die nicht von vorherigen Werten abhängig sind, die aus den Trainingsdaten berechnet wurden). Wenn Sie einen benutzerdefinierten Transformer definieren möchten, der Testdatensätze basierend auf den im Trainingsdatensatz beobachteten Werten transformieren kann, können Sie a nicht verwenden FunctionTransformerund Sie müssen einen anderen Ansatz wählen.

Wenn das etwas verwirrend klingt, nehmen Sie sich eine Minute Zeit und überdenken Sie noch einmal die Funktion, die wir gerade geschrieben haben, um die Titel der Passagiere zu extrahieren:

def extract_title(X):
"""Extract the title from every passenger's `identify`."""
X['title'] = X['name'].str.cut up(', ', increase=True)[1].str.cut up('.', increase=True)[0]

Die Funktion ist staatenlos weil es keine Erinnerung an die Vergangenheit hat; Während des Vorgangs werden keine vorberechneten Werte verwendet. Jedes Mal, wenn wir diese Funktion aufrufen, wird sie von Grund auf angewendet, als ob sie zum ersten Mal ausgeführt würde.

A Staatsbürgerlich Im Gegensatz dazu behält die Funktion Informationen aus früheren Vorgängen bei und verwendet diese bei der Implementierung des aktuellen Vorgangs. Um diese Unterscheidung zu veranschaulichen, sind hier zwei Funktionen, die fehlende Werte in unserem Datensatz durch einen Mittelwert ersetzen:

# Stateless - no prior data is used within the transformation
def impute_mean_stateless(X):
X['column1'] = X['column1'].fillna(X['column1'].imply())

# Stateful - prior details about the coaching set is used within the transformation
column1_mean_train = np.imply(X_train['column1'])
def impute_mean(X):
X['column1'] = X['column1'].fillna(column1_mean_train)
return X

Die erste Funktion ist a staatenlos Funktion, da bei der Transformation keine vorherigen Informationen verwendet werden; Der Mittelwert wird nur anhand des Datensatzes berechnet X welches an die Funktion übergeben wird.

Der zweite ist ein Staatsbürgerlich Funktion, die verwendet column1_mean_train (additionally der Mittelwert von column1 aus dem Trainingsset X_train), um fehlende Werte in zu ersetzen X.

Die Unterscheidung zwischen zustandsloser und zustandsbehafteter Transformation magazine etwas unklar erscheinen, ist aber ein unglaublich wichtiges Konzept bei ML-Aufgaben, bei denen wir separate Trainings- und Testdatensätze haben. Wann immer wir fehlende Werte ersetzen, Options skalieren oder One-Scorching-Codierung für unsere Testdatensätze durchführen möchten, möchten wir, dass diese Transformationen auf den im Trainingsdatensatz beobachteten Werten basieren. Mit anderen Worten, wir möchten, dass unser Transformer so ist match zum Trainingsset. Am Beispiel der Imputation fehlender Werte mit dem Mittelwert möchten wir, dass der „Mittelwert“ der Mittelwert des Trainingssatzes ist.

Das Drawback bei der Verwendung FunctionTransformer ist, dass es nicht zur Implementierung zustandsbehafteter Transformationen verwendet werden kann. Auch wenn a Transformer hergestellt mit FunctionTransformer technisch hat die .match() Methode, Wenn du es anrufst, wird es nichts bringenDaher können wir diesen Transformer nicht wirklich an die Trainingsdaten „anpassen“. Warum? Weil die Transformationen in a FunctionTransformer-erstellte Transformer sind immer vom Eingabewert der Funktion abhängig X. Unser Transformer berechnet die Werte immer anhand des übergebenen Datensatzes neu; Es gibt keine Möglichkeit, einen vorberechneten Wert zu imputieren/transformieren.

Um dies zu veranschaulichen, hier ein Beispiel, in dem ich versuche, einen FunctionTransformer-basierten Transformer an einen Trainingssatz „anzupassen“ und dann den Testsatz mit diesem angeblich „angepassten“ Transformator umzuwandeln. Wie Sie sehen, werden die fehlenden Werte im Testsatz nicht durch den Mittelwert aus dem Trainingssatz ersetzt; Sie werden basierend auf dem Testsatz neu berechnet. Mit anderen Worten: Der Transformer warfare nicht in der Lage, eine zustandsbehaftete Transformation durchzuführen.

# Present the take a look at set, pre-transformation
X_test.head(3)
Bild des Autors, das einen fehlenden Wert in der dritten Zeile des Testsatzes in der Spalte „Alter“ zeigt.
print("X_train imply: ", X_train['age'].imply())
# X_train imply: 29.857414148681055

print("X_test imply: ", X_test['age'].imply())
# X_test imply: 29.97444952830189

def impute_mean(X):
X['age'] = X['age'].fillna(X['age'].imply())
return X

impute_mean_FT = FunctionTransformer(impute_mean) # Convert perform to Transformer
prepro = impute_mean_FT.match(X_train) # The Transformer is "fitted" to the practice set
prepro.rework(X_test) # The fitted Transformer is used to remodel the take a look at set

Bild vom Autor. Der fehlende Wert in der dritten Zeile wurde durch den Mittelwert des Testsatzes und nicht durch den Mittelwert des Trainingssatzes ersetzt, was die Unfähigkeit von FuntionTransformer verdeutlicht, Transformer zu erzeugen, die zu zustandsbehafteten Transformationen fähig sind.

Wenn das alles etwas verwirrend klingt, machen Sie sich keine Sorgen. Die wichtigste Botschaft lautet: Wenn Sie einen benutzerdefinierten Transformer definieren möchten, der Testdatensätze basierend auf den im Trainingsdatensatz beobachteten Werten vorverarbeiten kann, können Sie keinen verwenden FunctionTransformerund Sie müssen einen anderen Ansatz wählen.

Ein alternativer Ansatz besteht darin, eine neue Transformer-Klasse zu definieren, die von einer in der gefundenen Klasse erbt sklearn.base Modul: TransformerMixin. Diese neue Klasse fungiert dann als Transformer und eignet sich für die zustandslose Anwendung Und Zustandstransformationen.

So würden wir unsere nehmen extract_title Code-Snippet und verwandeln Sie es mit diesem Ansatz in einen Transformer:

from sklearn.base import TransformerMixin

class ExtractTitle(TransformerMixin):
def match(self, X, y=None):
return self
def rework(self, X, y=None):
X['title'] = X['name'].str.cut up(', ', increase=True)[1].str.cut up('.', increase=True)[0]
return X

preprocessor = Pipeline(steps=[
('extract_title', ExtractTitle()),
])

X_train_transformed = preprocessor.fit_transform(X_train)
X_train_transformed.head()

Bild vom Autor

Wie Sie sehen, erreichen wir genau die gleiche Transformation wie bei der Erstellung unseres Transformers mit FunctionTransformer.

2.1 Übergabe von Argumenten an einen benutzerdefinierten Transformer

Wenn Sie Daten an Ihren benutzerdefinierten Transformer übergeben müssen, definieren Sie einfach einen __init__() Methode vor der Definition der match() Und rework() Methoden:

class IsUpperClass(TransformerMixin):
def __init__(self, upper_class_titles):
self.upper_class_titles = upper_class_titles

def match(self, X, y=None):
return self

def rework(self, X, y=None):
X['upper_class'] = X['title'].apply(lambda x: 1 if x in self.upper_class_titles else 0)
return X

preprocessor = Pipeline(steps=[
('IsUpperClass', IsUpperClass(upper_class_titles=['Dr', 'Col', 'Major', 'Lady', 'Rev', 'Sir', 'Capt'])),
])

X_train_transformed = preprocessor.fit_transform(X_train)
X_train_transformed.head()

Bild vom Autor



Source link

HINTERLASSEN SIE EINE ANTWORT

Please enter your comment!
Please enter your name here