Gehen Sie über den „Anfängermodus“ hinaus und nutzen Sie die leistungsstärkeren Funktionen von scikit-learn voll aus
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:
- Verwendung einer
FunctionTransformer
- 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.
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:
sklearn.impute.SimpleImputer
– ein Transformer, der fehlende Werte in Ihrem Datensatz ersetztsklearn.preprocessing.MinMaxScaler
– ein Transformer, der die numerischen Merkmale in Ihrem Datensatz neu skalieren kannsklearn.preprocessing.OneHotEncoder
– ein Transformer für One-Scorching-Codierung kategorialer Funktionen
Mit einem Scikit-Be taught sklearn.pipeline.Pipeline
Sie können sogar mehrere Transformer miteinander verketten, um mehrstufige Datenvorbereitungs-Workflows zur Vorbereitung auf die anschließende ML-Modellierung zu erstellen:
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()
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 OneHotEncoder
oder 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 FunctionTransformer
beginnen Sie mit der Definition einer Funktion, die einen Eingabedatensatz entgegennimmt X
führt die gewünschte Transformation durch und gibt eine transformierte Model von zurück X
. Dann packen Sie Ihre Funktion in a FunctionTransformer
und 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 FunctionTransformerdef 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 Pipelinepreprocessor = 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
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
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 FunctionTransformer
und 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)
print("X_train imply: ", X_train['age'].imply())
# X_train imply: 29.857414148681055print("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 Ximpute_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
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 FunctionTransformer
und 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 TransformerMixinclass 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()
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()