7.3. Daten vorverarbeiten#

Das Paket sklearn.preprocessing stellt mehrere gängige Hilfsfunktionen und Transformator-Klassen bereit, um Roh-Feature-Vektoren in eine Darstellung zu ändern, die für nachgelagerte Schätzer besser geeignet ist.

Im Allgemeinen profitieren viele Lernalgorithmen wie lineare Modelle von der Standardisierung des Datensatzes (siehe Wichtigkeit der Merkmalskalierung). Wenn Ausreißer im Datensatz vorhanden sind, können robuste Skalierer oder andere Transformatoren besser geeignet sein. Das Verhalten der verschiedenen Skalierer, Transformatoren und Normalisierer auf einem Datensatz mit Rand-Ausreißern wird in Vergleich der Wirkung verschiedener Skalierer auf Daten mit Ausreißern hervorgehoben.

7.3.1. Standardisierung oder Mittelwertentfernung und Varianzskalierung#

Die Standardisierung von Datensätzen ist eine häufige Anforderung für viele Machine-Learning-Schätzer, die in scikit-learn implementiert sind; sie können sich schlecht verhalten, wenn die einzelnen Merkmale mehr oder weniger wie standardmäßig normalverteilte Daten aussehen: Gaußisch mit Nullmittelwert und Einheitsvarianz.

In der Praxis ignorieren wir oft die Form der Verteilung und transformieren die Daten nur, um sie durch Entfernen des Mittelwerts jedes Merkmals zu zentrieren, und skalieren sie dann, indem wir nicht-konstante Merkmale durch ihre Standardabweichung dividieren.

Viele Elemente, die in der Zielfunktion eines Lernalgorithmus verwendet werden (wie der RBF-Kern von Support Vector Machines oder die l1- und l2-Regularisierer von linearen Modellen), gehen beispielsweise davon aus, dass alle Merkmale um Null zentriert sind oder eine Varianz in der gleichen Größenordnung aufweisen. Wenn ein Merkmal eine Varianz aufweist, die um Größenordnungen größer ist als andere, kann es die Zielfunktion dominieren und verhindern, dass der Schätzer korrekt von anderen Merkmalen lernt, wie erwartet.

Das Modul preprocessing bietet die Hilfsklasse StandardScaler, die eine schnelle und einfache Möglichkeit darstellt, die folgende Operation auf einem Array-ähnlichen Datensatz durchzuführen:

>>> from sklearn import preprocessing
>>> import numpy as np
>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> scaler
StandardScaler()

>>> scaler.mean_
array([1., 0., 0.33])

>>> scaler.scale_
array([0.81, 0.81, 1.24])

>>> X_scaled = scaler.transform(X_train)
>>> X_scaled
array([[ 0.  , -1.22,  1.33 ],
       [ 1.22,  0.  , -0.267],
       [-1.22,  1.22, -1.06 ]])

Skalierte Daten haben Nullmittelwert und Einheitsvarianz

>>> X_scaled.mean(axis=0)
array([0., 0., 0.])

>>> X_scaled.std(axis=0)
array([1., 1., 1.])

Diese Klasse implementiert die Transformer API, um den Mittelwert und die Standardabweichung auf einem Trainingsdatensatz zu berechnen, um später dieselbe Transformation auf dem Testdatensatz wieder anwenden zu können. Diese Klasse eignet sich daher für die Verwendung in den frühen Schritten einer Pipeline.

>>> from sklearn.datasets import make_classification
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler

>>> X, y = make_classification(random_state=42)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
>>> pipe = make_pipeline(StandardScaler(), LogisticRegression())
>>> pipe.fit(X_train, y_train)  # apply scaling on training data
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('logisticregression', LogisticRegression())])

>>> pipe.score(X_test, y_test)  # apply scaling on testing data, without leaking training data.
0.96

Es ist möglich, entweder das Zentrieren oder das Skalieren zu deaktivieren, indem entweder with_mean=False oder with_std=False an den Konstruktor von StandardScaler übergeben wird.

7.3.1.1. Merkmale auf einen Bereich skalieren#

Eine alternative Standardisierung ist die Skalierung von Merkmalen auf einen gegebenen Minimal- und Maximalwert, oft zwischen null und eins, oder so, dass der maximale absolute Wert jedes Merkmals auf die Einheit skaliert wird. Dies kann mit MinMaxScaler oder MaxAbsScaler erreicht werden.

Die Motivation für diese Skalierung umfasst die Robustheit gegenüber sehr kleinen Standardabweichungen von Merkmalen und die Beibehaltung von Null-Einträgen in spärlichen Daten.

Hier ist ein Beispiel zum Skalieren einer Beispiel-Datenmatrix in den Bereich [0, 1]

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[0.5       , 0.        , 1.        ],
       [1.        , 0.5       , 0.33333333],
       [0.        , 1.        , 0.        ]])

Dasselbe Instanz des Transformators kann dann auf neue Testdaten angewendet werden, die während des Fit-Aufrufs nicht gesehen wurden: dieselben Skalierungs- und Verschiebungsoperationen werden angewendet, um konsistent mit der auf die Trainingsdaten durchgeführten Transformation zu sein.

>>> X_test = np.array([[-3., -1.,  4.]])
>>> X_test_minmax = min_max_scaler.transform(X_test)
>>> X_test_minmax
array([[-1.5       ,  0.        ,  1.66666667]])

Es ist möglich, die Skaliererattribute zu untersuchen, um die genaue Natur der auf den Trainingsdaten gelernten Transformation zu erfahren.

>>> min_max_scaler.scale_
array([0.5       , 0.5       , 0.33])

>>> min_max_scaler.min_
array([0.        , 0.5       , 0.33])

Wenn MinMaxScaler einen expliziten feature_range=(min, max) erhält, lautet die vollständige Formel:

X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))

X_scaled = X_std * (max - min) + min

MaxAbsScaler arbeitet sehr ähnlich, skaliert aber so, dass die Trainingsdaten im Bereich [-1, 1] liegen, indem durch den größten Maximalwert jedes Merkmals dividiert wird. Er ist für Daten gedacht, die bereits um Null zentriert sind oder spärliche Daten sind.

So verwenden Sie die Beispiel-Daten aus dem vorherigen Beispiel mit diesem Skalierer.

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> max_abs_scaler = preprocessing.MaxAbsScaler()
>>> X_train_maxabs = max_abs_scaler.fit_transform(X_train)
>>> X_train_maxabs
array([[ 0.5, -1. ,  1. ],
       [ 1. ,  0. ,  0. ],
       [ 0. ,  1. , -0.5]])
>>> X_test = np.array([[ -3., -1.,  4.]])
>>> X_test_maxabs = max_abs_scaler.transform(X_test)
>>> X_test_maxabs
array([[-1.5, -1. ,  2. ]])
>>> max_abs_scaler.scale_
array([2.,  1.,  2.])

7.3.1.2. Skalierung spärlicher Daten#

Das Zentrieren spärlicher Daten würde die Spärlichkeitsstruktur in den Daten zerstören und ist daher selten sinnvoll. Es kann jedoch sinnvoll sein, spärliche Eingaben zu skalieren, insbesondere wenn die Merkmale unterschiedliche Skalen aufweisen.

MaxAbsScaler wurde speziell für die Skalierung spärlicher Daten entwickelt und ist der empfohlene Weg dorthin. StandardScaler kann jedoch scipy.sparse Matrizen als Eingabe akzeptieren, solange with_mean=False explizit an den Konstruktor übergeben wird. Andernfalls wird ein ValueError ausgelöst, da ein stilles Zentrieren die Spärlichkeit brechen und oft die Ausführung abstürzen lassen würde, indem unnötigerweise übermäßige Mengen an Speicher allokiert werden. RobustScaler kann nicht an spärliche Eingaben angepasst werden, aber Sie können die transform Methode für spärliche Eingaben verwenden.

Beachten Sie, dass die Skalierer sowohl das Compressed Sparse Rows- als auch das Compressed Sparse Columns-Format akzeptieren (siehe scipy.sparse.csr_matrix und scipy.sparse.csc_matrix). Jede andere spärliche Eingabe wird in die Compressed Sparse Rows-Darstellung konvertiert. Um unnötige Speicher kopien zu vermeiden, wird empfohlen, die CSR- oder CSC-Darstellung bereits vorgelagert zu wählen.

Wenn die zentrierten Daten klein genug sind, ist die explizite Konvertierung der Eingabe in ein Array mithilfe der toarray Methode von spärlichen Matrizen eine weitere Option.

7.3.1.3. Skalierung von Daten mit Ausreißern#

Wenn Ihre Daten viele Ausreißer enthalten, funktioniert die Skalierung unter Verwendung des Mittelwerts und der Varianz der Daten wahrscheinlich nicht sehr gut. In diesen Fällen können Sie RobustScaler als Drop-in-Ersatz verwenden. Er verwendet robustere Schätzungen für das Zentrum und den Bereich Ihrer Daten.

Referenzen#

Weitere Diskussionen über die Bedeutung von Zentrierung und Skalierung von Daten finden Sie in dieser FAQ: Sollten die Daten normalisiert/standardisiert/neu skaliert werden?

Skalierung vs. Whitening#

Es reicht manchmal nicht aus, die Merkmale unabhängig voneinander zu zentrieren und zu skalieren, da ein nachgelagertes Modell weitere Annahmen über die lineare Unabhängigkeit der Merkmale treffen kann.

Um dieses Problem zu lösen, können Sie PCA mit whiten=True verwenden, um die lineare Korrelation über Merkmale hinweg weiter zu entfernen.

7.3.1.4. Zentrierung von Kernel-Matrizen#

Wenn Sie eine Kernel-Matrix eines Kernels \(K\) haben, der ein Skalarprodukt in einem Merkmalsraum berechnet (möglicherweise implizit) durch eine Funktion \(\phi(\cdot)\) definiert, kann ein KernelCenterer die Kernel-Matrix transformieren, so dass sie innere Produkte im durch \(\phi\) definierten Merkmalsraum enthält, gefolgt von der Entfernung des Mittelwerts in diesem Raum. Mit anderen Worten, KernelCenterer berechnet die zentrierte Gram-Matrix, die einem positiv semidefiniten Kernel \(K\) zugeordnet ist.

Mathematische Formulierung#

Wir können uns die mathematische Formulierung ansehen, nachdem wir die Intuition erhalten haben. Sei \(K\) eine Kernel-Matrix der Form (n_samples, n_samples), die aus \(X\), einer Datenmatrix der Form (n_samples, n_features), während des fit-Schritts berechnet wird. \(K\) ist definiert durch

\[K(X, X) = \phi(X) . \phi(X)^{T}\]

\(\phi(X)\) ist eine Funktion, die \(X\) in einen Hilbert-Raum abbildet. Ein zentrierter Kernel \(\tilde{K}\) ist definiert als

\[\tilde{K}(X, X) = \tilde{\phi}(X) . \tilde{\phi}(X)^{T}\]

wobei \(\tilde{\phi}(X)\) aus der Zentrierung von \(\phi(X)\) im Hilbert-Raum resultiert.

Daher könnte man \(\tilde{K}\) berechnen, indem man \(X\) mit der Funktion \(\phi(\cdot)\) abbildet und die Daten in diesem neuen Raum zentriert. Kernel werden jedoch oft verwendet, weil sie einige algebraische Berechnungen ermöglichen, die die explizite Berechnung dieser Abbildung mit \(\phi(\cdot)\) vermeiden. Tatsächlich kann man implizit zentrieren, wie in Anhang B in [Scholkopf1998] gezeigt.

\[\tilde{K} = K - 1_{\text{n}_{samples}} K - K 1_{\text{n}_{samples}} + 1_{\text{n}_{samples}} K 1_{\text{n}_{samples}}\]

\(1_{\text{n}_{samples}}\) ist eine Matrix der Form (n_samples, n_samples), bei der alle Einträge gleich \(\frac{1}{\text{n}_{samples}}\) sind. Im transform-Schritt wird der Kernel zu \(K_{test}(X, Y)\), definiert als

\[K_{test}(X, Y) = \phi(Y) . \phi(X)^{T}\]

\(Y\) ist der Testdatensatz der Form (n_samples_test, n_features) und somit ist \(K_{test}\) von der Form (n_samples_test, n_samples). In diesem Fall erfolgt die Zentrierung von \(K_{test}\) wie folgt:

\[\tilde{K}_{test}(X, Y) = K_{test} - 1'_{\text{n}_{samples}} K - K_{test} 1_{\text{n}_{samples}} + 1'_{\text{n}_{samples}} K 1_{\text{n}_{samples}}\]

\(1'_{\text{n}_{samples}}\) ist eine Matrix der Form (n_samples_test, n_samples), bei der alle Einträge gleich \(\frac{1}{\text{n}_{samples}}\) sind.

Referenzen

[Scholkopf1998]

B. Schölkopf, A. Smola und K.R. Müller, „Nonlinear component analysis as a kernel eigenvalue problem.“ Neural computation 10.5 (1998): 1299-1319.

7.3.2. Nichtlineare Transformation#

Zwei Arten von Transformationen sind verfügbar: Quantil-Transformationen und Potenz-Transformationen. Sowohl Quantil- als auch Potenz-Transformationen basieren auf monotonen Transformationen der Merkmale und bewahren somit den Rang der Werte entlang jedes Merkmals.

Quantil-Transformationen ordnen alle Merkmale der gleichen gewünschten Verteilung zu, basierend auf der Formel \(G^{-1}(F(X))\), wobei \(F\) die kumulative Verteilungsfunktion des Merkmals und \(G^{-1}\) die Quantilfunktion der gewünschten Ausgabe-Verteilung \(G\) ist. Diese Formel verwendet die beiden folgenden Fakten: (i) Wenn \(X\) eine Zufallsvariable mit einer kontinuierlichen kumulativen Verteilungsfunktion \(F\) ist, dann ist \(F(X)\) gleichmäßig auf \([0,1]\) verteilt; (ii) Wenn \(U\) eine Zufallsvariable mit gleichmäßiger Verteilung auf \([0,1]\) ist, dann hat \(G^{-1}(U)\) die Verteilung \(G\). Durch die Durchführung einer Rangtransformation glättet eine Quantiltransformation ungewöhnliche Verteilungen und ist weniger von Ausreißern betroffen als Skalierungsmethoden. Sie verzerrt jedoch Korrelationen und Abstände innerhalb und zwischen Merkmalen.

Potenz-Transformationen sind eine Familie von parametrischen Transformationen, die darauf abzielen, Daten aus beliebigen Verteilungen so nah wie möglich an eine Gaußsche Verteilung abzubilden.

7.3.2.1. Abbildung auf eine gleichmäßige Verteilung#

QuantileTransformer bietet eine nicht-parametrische Transformation zur Abbildung von Daten auf eine gleichmäßige Verteilung mit Werten zwischen 0 und 1.

>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
>>> quantile_transformer = preprocessing.QuantileTransformer(random_state=0)
>>> X_train_trans = quantile_transformer.fit_transform(X_train)
>>> X_test_trans = quantile_transformer.transform(X_test)
>>> np.percentile(X_train[:, 0], [0, 25, 50, 75, 100])
array([ 4.3,  5.1,  5.8,  6.5,  7.9])

Diese Eigenschaft entspricht der Kelchblattlänge in cm. Nach Anwendung der Quantil-Transformation nähern sich diese Landmarken den zuvor definierten Perzentilen stark an.

>>> np.percentile(X_train_trans[:, 0], [0, 25, 50, 75, 100])
...
array([ 0.00 ,  0.24,  0.49,  0.73,  0.99 ])

Dies kann auf einem unabhängigen Testdatensatz mit ähnlichen Anmerkungen bestätigt werden.

>>> np.percentile(X_test[:, 0], [0, 25, 50, 75, 100])
...
array([ 4.4  ,  5.125,  5.75 ,  6.175,  7.3  ])
>>> np.percentile(X_test_trans[:, 0], [0, 25, 50, 75, 100])
...
array([ 0.01,  0.25,  0.46,  0.60 ,  0.94])

7.3.2.2. Abbildung auf eine Gaußsche Verteilung#

In vielen Modellierungsszenarien ist Normalität der Merkmale in einem Datensatz wünschenswert. Potenz-Transformationen sind eine Familie von parametrischen, monotonen Transformationen, die darauf abzielen, Daten aus beliebigen Verteilungen so nah wie möglich an eine Gaußsche Verteilung abzubilden, um die Varianz zu stabilisieren und die Schiefe zu minimieren.

PowerTransformer bietet derzeit zwei solche Potenz-Transformationen an: die Yeo-Johnson-Transformation und die Box-Cox-Transformation.

Yeo-Johnson-Transformation#
\[\begin{split}x_i^{(\lambda)} = \begin{cases} [(x_i + 1)^\lambda - 1] / \lambda & \text{wenn } \lambda \neq 0, x_i \geq 0, \\[8pt] \ln{(x_i + 1)} & \text{wenn } \lambda = 0, x_i \geq 0 \\[8pt] -[(-x_i + 1)^{2 - \lambda} - 1] / (2 - \lambda) & \text{wenn } \lambda \neq 2, x_i < 0, \\[8pt] - \ln (- x_i + 1) & \text{wenn } \lambda = 2, x_i < 0 \end{cases}\end{split}\]
Box-Cox-Transformation#
\[\begin{split}x_i^{(\lambda)} = \begin{cases} \dfrac{x_i^\lambda - 1}{\lambda} & \text{wenn } \lambda \neq 0, \\[8pt] \ln{(x_i)} & \text{wenn } \lambda = 0, \end{cases}\end{split}\]

Box-Cox kann nur auf streng positive Daten angewendet werden. Bei beiden Methoden wird die Transformation durch \(\lambda\) parametrisiert, das durch Maximum-Likelihood-Schätzung bestimmt wird. Hier ist ein Beispiel für die Verwendung von Box-Cox zur Abbildung von Stichproben aus einer lognormalen Verteilung auf eine normale Verteilung.

>>> pt = preprocessing.PowerTransformer(method='box-cox', standardize=False)
>>> X_lognormal = np.random.RandomState(616).lognormal(size=(3, 3))
>>> X_lognormal
array([[1.28, 1.18 , 0.84 ],
       [0.94, 1.60 , 0.388],
       [1.35, 0.217, 1.09 ]])
>>> pt.fit_transform(X_lognormal)
array([[ 0.49 ,  0.179, -0.156],
       [-0.051,  0.589, -0.576],
       [ 0.69 , -0.849,  0.101]])

Während das obige Beispiel die Option standardize auf False setzt, wendet PowerTransformer standardmäßig eine Nullmittelwert-, Einheitsvarianz-Normalisierung auf die transformierte Ausgabe an.

Im Folgenden sind Beispiele für Box-Cox und Yeo-Johnson auf verschiedene Wahrscheinlichkeitsverteilungen angewendet. Beachten Sie, dass die Potenz-Transformationen bei bestimmten Verteilungen sehr Gauß-ähnliche Ergebnisse liefern, bei anderen jedoch unwirksam sind. Dies unterstreicht die Bedeutung der Visualisierung der Daten vor und nach der Transformation.

../_images/sphx_glr_plot_map_data_to_normal_001.png

Es ist auch möglich, Daten mit QuantileTransformer auf eine normale Verteilung abzubilden, indem output_distribution='normal' gesetzt wird. Unter Verwendung des früheren Beispiels mit dem Iris-Datensatz.

>>> quantile_transformer = preprocessing.QuantileTransformer(
...     output_distribution='normal', random_state=0)
>>> X_trans = quantile_transformer.fit_transform(X)
>>> quantile_transformer.quantiles_
array([[4.3, 2. , 1. , 0.1],
       [4.4, 2.2, 1.1, 0.1],
       [4.4, 2.2, 1.2, 0.1],
       ...,
       [7.7, 4.1, 6.7, 2.5],
       [7.7, 4.2, 6.7, 2.5],
       [7.9, 4.4, 6.9, 2.5]])

Somit wird der Median der Eingabe zum Mittelwert der Ausgabe, zentriert auf 0. Die normale Ausgabe wird abgeschnitten, so dass das Minimum und Maximum der Eingabe - die den Quantilen 1e-7 bzw. 1 - 1e-7 entsprechen - bei der Transformation nicht unendlich werden.

7.3.3. Normalisierung#

Normalisierung ist der Prozess der Skalierung einzelner Stichproben, damit sie Norm eins haben. Dieser Prozess kann nützlich sein, wenn Sie eine quadratische Form wie das Skalarprodukt oder einen anderen Kernel verwenden möchten, um die Ähnlichkeit von Stichprobenpaaren zu quantifizieren.

Diese Annahme ist die Grundlage des Vektorraumsmodells, das häufig in der Textklassifikation und im Clustering verwendet wird.

Die Funktion normalize bietet eine schnelle und einfache Möglichkeit, diese Operation auf einem einzelnen Array-ähnlichen Datensatz durchzuführen, entweder unter Verwendung der l1, l2 oder max Normen.

>>> X = [[ 1., -1.,  2.],
...      [ 2.,  0.,  0.],
...      [ 0.,  1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')

>>> X_normalized
array([[ 0.408, -0.408,  0.812],
       [ 1.   ,  0.   ,  0.   ],
       [ 0.   ,  0.707, -0.707]])

Das Modul preprocessing bietet darüber hinaus eine Hilfsklasse Normalizer, die dieselbe Operation unter Verwendung der Transformer API implementiert (auch wenn die fit Methode in diesem Fall nutzlos ist: Die Klasse ist zustandslos, da diese Operation Stichproben unabhängig behandelt).

Diese Klasse eignet sich daher für die Verwendung in den frühen Schritten einer Pipeline.

>>> normalizer = preprocessing.Normalizer().fit(X)  # fit does nothing
>>> normalizer
Normalizer()

Die Normalisierer-Instanz kann dann auf Stichprobenvektoren wie jeder Transformator verwendet werden.

>>> normalizer.transform(X)
array([[ 0.408, -0.408,  0.812],
       [ 1.   ,  0.   ,  0.   ],
       [ 0.   ,  0.707, -0.707]])

>>> normalizer.transform([[-1.,  1., 0.]])
array([[-0.707,  0.707,  0.]])

Hinweis: L2-Normalisierung ist auch als räumliche Vorverarbeitung des Vorzeichens bekannt.

Spärliche Eingabe#

normalize und Normalizer akzeptieren sowohl dichte Array-ähnliche als auch spärliche Matrizen von scipy.sparse als Eingabe.

Für spärliche Eingaben werden die Daten in die Compressed Sparse Rows-Darstellung konvertiert (siehe scipy.sparse.csr_matrix), bevor sie an effiziente Cython-Routinen übergeben werden. Um unnötige Speicher kopien zu vermeiden, wird empfohlen, die CSR-Darstellung bereits vorgelagert zu wählen.

7.3.4. Kategorische Merkmale kodieren#

Oft werden Merkmale nicht als kontinuierliche Werte, sondern als kategorisch gegeben. Zum Beispiel könnte eine Person Merkmale haben wie ["männlich", "weiblich"], ["aus Europa", "aus USA", "aus Asien"], ["verwendet Firefox", "verwendet Chrome", "verwendet Safari", "verwendet Internet Explorer"]. Solche Merkmale können effizient als Ganzzahlen kodiert werden, zum Beispiel ["männlich", "aus USA", "verwendet Internet Explorer"] könnte als [0, 1, 3] ausgedrückt werden, während ["weiblich", "aus Asien", "verwendet Chrome"] [1, 2, 1] wäre.

Um kategorische Merkmale in solche Ganzzahlcodes umzuwandeln, können wir den OrdinalEncoder verwenden. Dieser Schätzer transformiert jedes kategorische Merkmal in ein neues Merkmal aus Ganzzahlen (0 bis n_Kategorien - 1).

>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OrdinalEncoder()
>>> enc.transform([['female', 'from US', 'uses Safari']])
array([[0., 1., 1.]])

Eine solche Ganzzahl-Darstellung kann jedoch nicht direkt mit allen scikit-learn-Schätzern verwendet werden, da diese kontinuierliche Eingaben erwarten und die Kategorien als geordnet interpretieren würden, was oft nicht gewünscht ist (d.h. die Menge der Browser wurde willkürlich geordnet).

Standardmäßig übergibt OrdinalEncoder auch fehlende Werte, die durch np.nan angezeigt werden.

>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [nan],
       [ 0.]])

OrdinalEncoder bietet den Parameter encoded_missing_value, um fehlende Werte zu kodieren, ohne dass eine Pipeline erstellt und SimpleImputer verwendet werden muss.

>>> enc = preprocessing.OrdinalEncoder(encoded_missing_value=-1)
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [-1.],
       [ 0.]])

Die obige Verarbeitung entspricht der folgenden Pipeline.

>>> from sklearn.pipeline import Pipeline
>>> from sklearn.impute import SimpleImputer
>>> enc = Pipeline(steps=[
...     ("encoder", preprocessing.OrdinalEncoder()),
...     ("imputer", SimpleImputer(strategy="constant", fill_value=-1)),
... ])
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [-1.],
       [ 0.]])

Eine weitere Möglichkeit, kategorische Merkmale in für scikit-learn-Schätzer verwendbare Merkmale umzuwandeln, ist die Verwendung einer One-of-K-, auch bekannt als One-Hot- oder Dummy-Kodierung. Diese Art der Kodierung kann mit OneHotEncoder erzielt werden, der jedes kategorische Merkmal mit n_categories möglichen Werten in n_categories Binär-Merkmale umwandelt, wobei eines davon 1 und alle anderen 0 ist.

Fortsetzung des obigen Beispiels.

>>> enc = preprocessing.OneHotEncoder()
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder()
>>> enc.transform([['female', 'from US', 'uses Safari'],
...                ['male', 'from Europe', 'uses Safari']]).toarray()
array([[1., 0., 0., 1., 0., 1.],
       [0., 1., 1., 0., 0., 1.]])

Standardmäßig werden die Werte, die jedes Merkmal annehmen kann, automatisch aus dem Datensatz abgeleitet und sind im Attribut categories_ zu finden.

>>> enc.categories_
[array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object), array(['uses Firefox', 'uses Safari'], dtype=object)]

Es ist möglich, dies explizit mit dem Parameter categories anzugeben. In unserem Datensatz gibt es zwei Geschlechter, vier mögliche Kontinente und vier Webbrowser.

>>> genders = ['female', 'male']
>>> locations = ['from Africa', 'from Asia', 'from Europe', 'from US']
>>> browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari']
>>> enc = preprocessing.OneHotEncoder(categories=[genders, locations, browsers])
>>> # Note that for there are missing categorical values for the 2nd and 3rd
>>> # feature
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder(categories=[['female', 'male'],
                          ['from Africa', 'from Asia', 'from Europe',
                           'from US'],
                          ['uses Chrome', 'uses Firefox', 'uses IE',
                           'uses Safari']])
>>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]])

Wenn die Möglichkeit besteht, dass Trainingsdaten fehlende kategorische Merkmale aufweisen, kann es oft besser sein, handle_unknown='infrequent_if_exist' anzugeben, anstatt categories manuell wie oben einzustellen. Wenn handle_unknown='infrequent_if_exist' angegeben ist und während der Transformation unbekannte Kategorien auftreten, wird kein Fehler ausgelöst, aber die resultierenden One-Hot-kodierten Spalten für dieses Merkmal sind alle Nullen oder werden als seltene Kategorie betrachtet, falls aktiviert. (handle_unknown='infrequent_if_exist' wird nur für One-Hot-Encoding unterstützt)

>>> enc = preprocessing.OneHotEncoder(handle_unknown='infrequent_if_exist')
>>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']]
>>> enc.fit(X)
OneHotEncoder(handle_unknown='infrequent_if_exist')
>>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 0., 0., 0.]])

Es ist auch möglich, jede Spalte anstelle von n_categories Spalten in n_categories - 1 Spalten zu kodieren, indem der Parameter drop verwendet wird. Dieser Parameter ermöglicht es dem Benutzer, für jedes Merkmal eine zu löschende Kategorie anzugeben. Dies ist nützlich, um Kolinearität in der Eingabematrix bei einigen Klassifikatoren zu vermeiden. Eine solche Funktionalität ist beispielsweise bei der Verwendung von nicht-regularisierter Regression (LinearRegression) nützlich, da Kolinearität dazu führen würde, dass die Kovarianzmatrix nicht invertierbar ist.

>>> X = [['male', 'from US', 'uses Safari'],
...      ['female', 'from Europe', 'uses Firefox']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='first').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object),
 array(['uses Firefox', 'uses Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 1., 1.],
       [0., 0., 0.]])

Man möchte möglicherweise nur für Merkmale mit 2 Kategorien eine der beiden Spalten löschen. In diesem Fall kann der Parameter drop='if_binary' gesetzt werden.

>>> X = [['male', 'US', 'Safari'],
...      ['female', 'Europe', 'Firefox'],
...      ['female', 'Asia', 'Chrome']]
>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary').fit(X)
>>> drop_enc.categories_
[array(['female', 'male'], dtype=object), array(['Asia', 'Europe', 'US'], dtype=object),
 array(['Chrome', 'Firefox', 'Safari'], dtype=object)]
>>> drop_enc.transform(X).toarray()
array([[1., 0., 0., 1., 0., 0., 1.],
       [0., 0., 1., 0., 0., 1., 0.],
       [0., 1., 0., 0., 1., 0., 0.]])

Im transformierten X ist die erste Spalte die Kodierung des Merkmals mit den Kategorien „male“/„female“, während die verbleibenden 6 Spalten die Kodierung der 2 Merkmale mit jeweils 3 Kategorien darstellen.

Wenn handle_unknown='ignore' und drop nicht `None` ist, werden unbekannte Kategorien als Nullen kodiert.

>>> drop_enc = preprocessing.OneHotEncoder(drop='first',
...                                        handle_unknown='ignore').fit(X)
>>> X_test = [['unknown', 'America', 'IE']]
>>> drop_enc.transform(X_test).toarray()
array([[0., 0., 0., 0., 0.]])

Alle Kategorien in X_test sind während der Transformation unbekannt und werden auf Nullen abgebildet. Das bedeutet, dass unbekannte Kategorien die gleiche Abbildung wie die gelöschte Kategorie haben werden. OneHotEncoder.inverse_transform bildet Nullen auf die gelöschte Kategorie ab, wenn eine Kategorie gelöscht wurde, und auf `None`, wenn keine Kategorie gelöscht wurde.

>>> drop_enc = preprocessing.OneHotEncoder(drop='if_binary', sparse_output=False,
...                                        handle_unknown='ignore').fit(X)
>>> X_test = [['unknown', 'America', 'IE']]
>>> X_trans = drop_enc.transform(X_test)
>>> X_trans
array([[0., 0., 0., 0., 0., 0., 0.]])
>>> drop_enc.inverse_transform(X_trans)
array([['female', None, None]], dtype=object)
Unterstützung von kategorischen Merkmalen mit fehlenden Werten#

OneHotEncoder unterstützt kategorische Merkmale mit fehlenden Werten, indem fehlende Werte als zusätzliche Kategorie betrachtet werden.

>>> X = [['male', 'Safari'],
...      ['female', None],
...      [np.nan, 'Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['female', 'male', nan], dtype=object),
array(['Firefox', 'Safari', None], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0., 1., 0.],
      [1., 0., 0., 0., 0., 1.],
      [0., 0., 1., 1., 0., 0.]])

Wenn ein Merkmal sowohl np.nan als auch None enthält, werden diese als separate Kategorien betrachtet.

>>> X = [['Safari'], [None], [np.nan], ['Firefox']]
>>> enc = preprocessing.OneHotEncoder(handle_unknown='error').fit(X)
>>> enc.categories_
[array(['Firefox', 'Safari', None, nan], dtype=object)]
>>> enc.transform(X).toarray()
array([[0., 1., 0., 0.],
      [0., 0., 1., 0.],
      [0., 0., 0., 1.],
      [1., 0., 0., 0.]])

Siehe Laden von Merkmalen aus Dictionaries für kategorische Merkmale, die als Dictionary und nicht als Skalare dargestellt werden.

7.3.4.1. Seltene Kategorien#

OneHotEncoder und OrdinalEncoder unterstützen die Aggregation von seltenen Kategorien zu einer einzigen Ausgabe für jedes Merkmal. Die Parameter zur Aktivierung des Sammelns seltener Kategorien sind min_frequency und max_categories.

  1. min_frequency ist entweder eine ganze Zahl größer oder gleich 1 oder eine Gleitkommazahl im Intervall (0.0, 1.0). Wenn min_frequency eine ganze Zahl ist, werden Kategorien mit einer Kardinalität kleiner als min_frequency als selten betrachtet. Wenn min_frequency eine Gleitkommazahl ist, werden Kategorien mit einer Kardinalität kleiner als dieser Bruchteil der Gesamtzahl der Stichproben als selten betrachtet. Der Standardwert ist 1, was bedeutet, dass jede Kategorie separat kodiert wird.

  2. max_categories ist entweder None oder eine beliebige ganze Zahl größer als 1. Dieser Parameter setzt eine Obergrenze für die Anzahl der Ausgabemerkmale für jedes Eingabemerkmal. max_categories schließt das Merkmal ein, das seltene Kategorien kombiniert.

Im folgenden Beispiel mit OrdinalEncoder werden die Kategorien 'dog' und 'snake' als selten betrachtet.

>>> X = np.array([['dog'] * 5 + ['cat'] * 20 + ['rabbit'] * 10 +
...               ['snake'] * 3], dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(min_frequency=6).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[2.],
       [0.],
       [1.],
       [2.]])

OrdinalEncoder's max_categories berücksichtigt keine fehlenden oder unbekannten Kategorien. Das Setzen von unknown_value oder encoded_missing_value auf eine Ganzzahl erhöht die Anzahl der eindeutigen Ganzzahlcodes um eins. Dies kann zu bis zu max_categories + 2 Ganzzahlcodes führen. Im folgenden Beispiel werden „a“ und „d“ als selten betrachtet und zu einer einzigen Kategorie zusammengefasst, „b“ und „c“ sind eigene Kategorien, unbekannte Werte werden als 3 und fehlende Werte als 4 kodiert.

>>> X_train = np.array(
...     [["a"] * 5 + ["b"] * 20 + ["c"] * 10 + ["d"] * 3 + [np.nan]],
...     dtype=object).T
>>> enc = preprocessing.OrdinalEncoder(
...     handle_unknown="use_encoded_value", unknown_value=3,
...     max_categories=3, encoded_missing_value=4)
>>> _ = enc.fit(X_train)
>>> X_test = np.array([["a"], ["b"], ["c"], ["d"], ["e"], [np.nan]], dtype=object)
>>> enc.transform(X_test)
array([[2.],
       [0.],
       [1.],
       [2.],
       [3.],
       [4.]])

Ähnlich kann OneHotEncoder konfiguriert werden, um seltene Kategorien zusammenzufassen.

>>> enc = preprocessing.OneHotEncoder(min_frequency=6, sparse_output=False).fit(X)
>>> enc.infrequent_categories_
[array(['dog', 'snake'], dtype=object)]
>>> enc.transform(np.array([['dog'], ['cat'], ['rabbit'], ['snake']]))
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

Durch Setzen von `handle_unknown` auf 'infrequent_if_exist' werden unbekannte Kategorien als selten betrachtet.

>>> enc = preprocessing.OneHotEncoder(
...    handle_unknown='infrequent_if_exist', sparse_output=False, min_frequency=6)
>>> enc = enc.fit(X)
>>> enc.transform(np.array([['dragon']]))
array([[0., 0., 1.]])

OneHotEncoder.get_feature_names_out verwendet ‚infrequent‘ als Namen für seltene Merkmale.

>>> enc.get_feature_names_out()
array(['x0_cat', 'x0_rabbit', 'x0_infrequent_sklearn'], dtype=object)

Wenn 'handle_unknown' auf 'infrequent_if_exist' gesetzt ist und eine unbekannte Kategorie während der Transformation angetroffen wird.

  1. Wenn die Unterstützung für seltene Kategorien nicht konfiguriert war oder während des Trainings keine seltene Kategorie vorhanden war, sind die resultierenden One-Hot-kodierten Spalten für dieses Merkmal alle Nullen. Bei der inversen Transformation wird eine unbekannte Kategorie als None bezeichnet.

  2. Wenn während des Trainings eine seltene Kategorie vorhanden ist, wird die unbekannte Kategorie als selten betrachtet. Bei der inversen Transformation wird ‚infrequent_sklearn‘ verwendet, um die seltene Kategorie darzustellen.

Seltene Kategorien können auch mit max_categories konfiguriert werden. Im folgenden Beispiel setzen wir max_categories=2, um die Anzahl der Merkmale in der Ausgabe zu begrenzen. Dies führt dazu, dass alle Kategorien außer der Kategorie 'cat' als selten betrachtet werden, was zu zwei Merkmalen führt: eines für 'cat' und eines für seltene Kategorien – das sind alle anderen.

>>> enc = preprocessing.OneHotEncoder(max_categories=2, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 1.],
       [1., 0.],
       [0., 1.],
       [0., 1.]])

Wenn sowohl max_categories als auch min_frequency nicht die Standardwerte sind, werden Kategorien zuerst basierend auf min_frequency ausgewählt und dann max_categories Kategorien beibehalten. Im folgenden Beispiel betrachtet min_frequency=4 nur snake als selten, aber max_categories=3 zwingt dog ebenfalls als selten zu gelten.

>>> enc = preprocessing.OneHotEncoder(min_frequency=4, max_categories=3, sparse_output=False)
>>> enc = enc.fit(X)
>>> enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

Wenn seltene Kategorien mit derselben Kardinalität am Grenzwert von max_categories vorhanden sind, werden die ersten max_categories basierend auf der lexikalischen Reihenfolge genommen. Im folgenden Beispiel haben „b“, „c“ und „d“ dieselbe Kardinalität und mit max_categories=2 sind „b“ und „c“ selten, da sie eine höhere lexikalische Reihenfolge haben.

>>> X = np.asarray([["a"] * 20 + ["b"] * 10 + ["c"] * 10 + ["d"] * 10], dtype=object).T
>>> enc = preprocessing.OneHotEncoder(max_categories=3).fit(X)
>>> enc.infrequent_categories_
[array(['b', 'c'], dtype=object)]

7.3.4.2. Target Encoder#

Der TargetEncoder verwendet den Zielmittelwert, bedingt durch das kategorische Merkmal, zur Kodierung ungeordneter Kategorien, d.h. nominaler Kategorien [PAR] [MIC]. Dieses Kodierungsschema ist nützlich für kategorische Merkmale mit hoher Kardinalität, bei denen die One-Hot-Kodierung den Merkmalsraum aufbläht und die Verarbeitung durch ein nachgelagertes Modell teurer macht. Ein klassisches Beispiel für Kategorien mit hoher Kardinalität sind standortbezogene wie Postleitzahlen oder Regionen.

Binäre Klassifizierungsziele#

Für binäre Klassifizierungsziele ist die Zielkodierung gegeben durch

\[S_i = \lambda_i\frac{n_{iY}}{n_i} + (1 - \lambda_i)\frac{n_Y}{n}\]

wobei \(S_i\) die Kodierung für Kategorie \(i\) ist, \(n_{iY}\) die Anzahl der Beobachtungen mit \(Y=1\) und Kategorie \(i\) ist, \(n_i\) die Anzahl der Beobachtungen mit Kategorie \(i\) ist, \(n_Y\) die Anzahl der Beobachtungen mit \(Y=1\) ist, \(n\) die Anzahl der Beobachtungen ist und \(\lambda_i\) ein Schrumpffaktor für Kategorie \(i\) ist. Der Schrumpffaktor ist gegeben durch

\[\lambda_i = \frac{n_i}{m + n_i}\]

wobei \(m\) ein Glättungsfaktor ist, der mit dem Parameter smooth in TargetEncoder gesteuert wird. Große Glättungsfaktoren gewichten den globalen Mittelwert stärker. Wenn smooth="auto" ist, wird der Glättungsfaktor als empirischer Bayes-Schätzer berechnet: \(m=\sigma_i^2/\tau^2\), wobei \(\sigma_i^2\) die Varianz von y mit Kategorie \(i\) ist und \(\tau^2\) die globale Varianz von y ist.

Multiklassen-Klassifizierungsziele#

Für Multiklassen-Klassifizierungsziele ist die Formulierung ähnlich wie bei der binären Klassifizierung

\[S_{ij} = \lambda_i\frac{n_{iY_j}}{n_i} + (1 - \lambda_i)\frac{n_{Y_j}}{n}\]

wobei \(S_{ij}\) die Kodierung für Kategorie \(i\) und Klasse \(j\) ist, \(n_{iY_j}\) die Anzahl der Beobachtungen mit \(Y=j\) und Kategorie \(i\) ist, \(n_i\) die Anzahl der Beobachtungen mit Kategorie \(i\) ist, \(n_{Y_j}\) die Anzahl der Beobachtungen mit \(Y=j\) ist, \(n\) die Anzahl der Beobachtungen ist und \(\lambda_i\) ein Schrumpffaktor für Kategorie \(i\) ist.

Kontinuierliche Ziele#

Für kontinuierliche Ziele ist die Formulierung ähnlich wie bei der binären Klassifizierung

\[S_i = \lambda_i\frac{\sum_{k\in L_i}Y_k}{n_i} + (1 - \lambda_i)\frac{\sum_{k=1}^{n}Y_k}{n}\]

wobei \(L_i\) die Menge der Beobachtungen mit Kategorie \(i\) ist und \(n_i\) die Anzahl der Beobachtungen mit Kategorie \(i\) ist.

Hinweis

In TargetEncoder gilt fit(X, y).transform(X) nicht gleich fit_transform(X, y).

fit_transform greift intern auf ein Kreuzvalidierungsverfahren zurück, um zu verhindern, dass Zielinformationen in die Trainingsrepräsentation gelangen, insbesondere bei nicht-informativen kategorischen Variablen mit hoher Kardinalität (Merkmale mit vielen eindeutigen Kategorien, von denen jede nur wenige Male vorkommt), und hilft, das nachgelagerte Modell vor Überanpassung an zufällige Korrelationen zu schützen. In fit_transform wird der Trainingsdatensatz in *k* Folds aufgeteilt (bestimmt durch den Parameter cv) und jeder Fold wird mit den Kodierungen kodiert, die mithilfe der anderen *k-1* Folds gelernt wurden. Aus diesem Grund sollten Trainingsdaten immer mit fit_transform(X_train, y_train) trainiert und transformiert werden.

Dieses Diagramm zeigt das Kreuzvalidierungsverfahren in fit_transform mit dem Standardwert cv=5.

../_images/target_encoder_cross_validation.svg

Die Methode fit verwendet keine Kreuzvalidierungsverfahren und lernt eine Kodierung auf dem gesamten Trainingsdatensatz. Es wird davon abgeraten, diese Methode zu verwenden, da sie, wie oben erwähnt, zu Datenlecks führen kann. Verwenden Sie stattdessen fit_transform.

Während fit_transform lernt der Encoder Kategorienkodierungen aus den vollständigen Trainingsdaten und speichert sie im Attribut encodings_. Die während des Kreuzvalidierungsverfahrens für jeden Fold gelernten Zwischenkodierungen sind temporär und werden nicht gespeichert. Die gespeicherten Kodierungen können dann verwendet werden, um Testdaten mit encoder.transform(X_test) zu transformieren.

Hinweis

TargetEncoder berücksichtigt fehlende Werte wie np.nan oder None als eine weitere Kategorie und kodiert sie wie jede andere Kategorie. Kategorien, die während fit nicht gesehen werden, werden mit dem Zielmittelwert, d.h. target_mean_, kodiert.

Beispiele

Referenzen

7.3.5. Diskretisierung#

Diskretisierung (auch bekannt als Quantisierung oder Binning) bietet eine Möglichkeit, kontinuierliche Merkmale in diskrete Werte zu partitionieren. Bestimmte Datensätze mit kontinuierlichen Merkmalen können von der Diskretisierung profitieren, da sie den Datensatz von kontinuierlichen Attributen in einen mit nur nominalen Attributen umwandeln kann.

One-Hot-kodierte diskretisierte Merkmale können ein Modell ausdrucksstärker machen und gleichzeitig die Interpretierbarkeit beibehalten. Beispielsweise kann eine Vorverarbeitung mit einem Diskretisierer Nichtlinearität in lineare Modelle einführen. Für fortgeschrittenere Möglichkeiten, insbesondere glatte, siehe Erzeugen von Polynommerkmalen weiter unten.

7.3.5.1. K-Bins-Diskretisierung#

KBinsDiscretizer diskretisiert Merkmale in k Bins.

>>> X = np.array([[ -3., 5., 15 ],
...               [  0., 6., 14 ],
...               [  6., 3., 11 ]])
>>> est = preprocessing.KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit(X)

Standardmäßig wird die Ausgabe One-Hot-kodiert in eine Sparse-Matrix (siehe Kodierung von kategorischen Merkmalen) und dies kann mit dem Parameter encode konfiguriert werden. Für jedes Merkmal werden die Bin-Grenzen während fit berechnet und zusammen mit der Anzahl der Bins definieren sie die Intervalle. Daher sind für das aktuelle Beispiel diese Intervalle definiert als

  • Merkmal 1: \({[-\infty, -1), [-1, 2), [2, \infty)}\)

  • Merkmal 2: \({[-\infty, 5), [5, \infty)}\)

  • Merkmal 3: \({[-\infty, 14), [14, \infty)}\)

Basierend auf diesen Bin-Intervallen wird X wie folgt transformiert:

>>> est.transform(X)
array([[ 0., 1., 1.],
       [ 1., 1., 1.],
       [ 2., 0., 0.]])

Der resultierende Datensatz enthält ordinale Attribute, die weiter in einer Pipeline verwendet werden können.

Diskretisierung ist ähnlich der Erstellung von Histogrammen für kontinuierliche Daten. Histogramme konzentrieren sich jedoch auf das Zählen von Merkmalen, die in bestimmte Bins fallen, während sich die Diskretisierung auf die Zuordnung von Merkmalswerten zu diesen Bins konzentriert.

KBinsDiscretizer implementiert verschiedene Binning-Strategien, die mit dem Parameter strategy ausgewählt werden können. Die Strategie ‚uniform‘ verwendet Bins mit konstanter Breite. Die Strategie ‚quantile‘ verwendet Quantilwerte, um gleichmäßig besetzte Bins in jedem Merkmal zu haben. Die Strategie ‚kmeans‘ definiert Bins basierend auf einem k-Means-Clustering-Verfahren, das für jedes Merkmal unabhängig durchgeführt wird.

Beachten Sie, dass benutzerdefinierte Bins durch Übergabe einer aufrufbaren Funktion, die die Diskretisierungsstrategie definiert, an FunctionTransformer angegeben werden können. Wir können beispielsweise die Pandas-Funktion pandas.cut verwenden.

>>> import pandas as pd
>>> import numpy as np
>>> from sklearn import preprocessing
>>>
>>> bins = [0, 1, 13, 20, 60, np.inf]
>>> labels = ['infant', 'kid', 'teen', 'adult', 'senior citizen']
>>> transformer = preprocessing.FunctionTransformer(
...     pd.cut, kw_args={'bins': bins, 'labels': labels, 'retbins': False}
... )
>>> X = np.array([0.2, 2, 15, 25, 97])
>>> transformer.fit_transform(X)
['infant', 'kid', 'teen', 'adult', 'senior citizen']
Categories (5, object): ['infant' < 'kid' < 'teen' < 'adult' < 'senior citizen']

Beispiele

7.3.5.2. Merkmalsbinarisierung#

Merkmalsbinarisierung ist der Prozess des Schwellenwertens numerischer Merkmale, um boolesche Werte zu erhalten. Dies kann für nachgelagerte probabilistische Schätzer nützlich sein, die annehmen, dass die Eingabedaten nach einer multivariaten Bernoulli-Verteilung verteilt sind. Dies ist beispielsweise bei BernoulliRBM der Fall.

Es ist auch in der Textverarbeitungs-Community üblich, binäre Merkmalswerte zu verwenden (wahrscheinlich zur Vereinfachung der probabilistischen Schlussfolgerungen), auch wenn normalisierte Zählungen (auch Termfrequenzen genannt) oder TF-IDF-basierte Merkmale in der Praxis oft etwas besser abschneiden.

Ähnlich wie die Normalizer ist die Dienstklasse Binarizer für die frühen Phasen einer Pipeline vorgesehen. Die Methode fit tut nichts, da jede Stichprobe unabhängig von anderen behandelt wird.

>>> X = [[ 1., -1.,  2.],
...      [ 2.,  0.,  0.],
...      [ 0.,  1., -1.]]

>>> binarizer = preprocessing.Binarizer().fit(X)  # fit does nothing
>>> binarizer
Binarizer()

>>> binarizer.transform(X)
array([[1., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])

Es ist möglich, den Schwellenwert des Binarisierers anzupassen.

>>> binarizer = preprocessing.Binarizer(threshold=1.1)
>>> binarizer.transform(X)
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 0., 0.]])

Wie die Klasse Normalizer bietet das Modul `preprocessing` eine begleitende Funktion binarize, die verwendet werden kann, wenn die Transformer-API nicht benötigt wird.

Beachten Sie, dass Binarizer ähnlich wie KBinsDiscretizer ist, wenn k = 2 ist und der Bin-Rand bei dem Wert threshold liegt.

7.3.6. Imputation fehlender Werte#

Werkzeuge zur Imputation fehlender Werte werden unter Imputation fehlender Werte besprochen.

7.3.7. Erzeugen von Polynommerkmalen#

Es ist oft nützlich, einem Modell Komplexität zu verleihen, indem nichtlineare Merkmale der Eingabedaten berücksichtigt werden. Wir zeigen zwei Möglichkeiten, die beide auf Polynomen basieren: Die erste verwendet reine Polynome, die zweite verwendet Splines, d.h. stückweise Polynome.

7.3.7.1. Polynommerkmale#

Eine einfache und gängige Methode ist die Verwendung von Polynommerkmalen, die hochgradige und Interaktionsterme von Merkmalen erzeugen können. Dies ist in PolynomialFeatures implementiert.

>>> import numpy as np
>>> from sklearn.preprocessing import PolynomialFeatures
>>> X = np.arange(6).reshape(3, 2)
>>> X
array([[0, 1],
       [2, 3],
       [4, 5]])
>>> poly = PolynomialFeatures(2)
>>> poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])

Die Merkmale von X wurden von \((X_1, X_2)\) zu \((1, X_1, X_2, X_1^2, X_1X_2, X_2^2)\) transformiert.

In einigen Fällen werden nur Interaktionsterme zwischen Merkmalen benötigt, was mit der Einstellung interaction_only=True erreicht werden kann.

>>> X = np.arange(9).reshape(3, 3)
>>> X
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> poly = PolynomialFeatures(degree=3, interaction_only=True)
>>> poly.fit_transform(X)
array([[  1.,   0.,   1.,   2.,   0.,   0.,   2.,   0.],
       [  1.,   3.,   4.,   5.,  12.,  15.,  20.,  60.],
       [  1.,   6.,   7.,   8.,  42.,  48.,  56., 336.]])

Die Merkmale von X wurden von \((X_1, X_2, X_3)\) zu \((1, X_1, X_2, X_3, X_1X_2, X_1X_3, X_2X_3, X_1X_2X_3)\) transformiert.

Beachten Sie, dass Polynommerkmale implizit in Kernel-Methoden (z.B. SVC, KernelPCA) verwendet werden, wenn Polynom- Kernel-Funktionen verwendet werden.

Siehe Polynom- und Spline-Interpolation für Ridge-Regression mit erzeugten Polynommerkmalen.

7.3.7.2. Spline Transformer#

Eine weitere Möglichkeit, nichtlineare Terme anstelle von reinen Polynomen von Merkmalen hinzuzufügen, ist die Erzeugung von Spline-Basis-Funktionen für jedes Merkmal mit dem SplineTransformer. Splines sind stückweise Polynome, die durch ihren Polynomgrad und die Positionen der Knoten parametrisiert sind. Der SplineTransformer implementiert eine B-Spline-Basis, siehe die untenstehenden Referenzen.

Hinweis

Der SplineTransformer behandelt jedes Merkmal separat, d.h. er liefert keine Interaktionsterme.

Einige der Vorteile von Splines gegenüber Polynomen sind:

  • B-Splines sind sehr flexibel und robust, wenn man einen festen niedrigen Grad, üblicherweise 3, beibehält und die Anzahl der Knoten sparsam anpasst. Polynome würden einen höheren Grad erfordern, was zum nächsten Punkt führt.

  • B-Splines weisen keine oszillierenden Verhaltensweisen an den Grenzen auf, wie es Polynome tun (je höher der Grad, desto schlimmer). Dies ist als Runge-Phänomen bekannt.

  • B-Splines bieten gute Optionen für die Extrapolation über die Grenzen hinaus, d.h. über den Bereich der angepassten Werte hinaus. Werfen Sie einen Blick auf die Option extrapolation.

  • B-Splines erzeugen eine Feature-Matrix mit bandstrukturierter Form. Für ein einzelnes Merkmal enthält jede Zeile nur degree + 1 Nicht-Null-Elemente, die aufeinanderfolgend auftreten und sogar positiv sind. Dies ergibt eine Matrix mit guten numerischen Eigenschaften, z.B. einer niedrigen Konditionszahl, im scharfen Gegensatz zu einer Matrix von Polynomen, die als Vandermonde-Matrix bekannt ist. Eine niedrige Konditionszahl ist wichtig für stabile Algorithmen linearer Modelle.

Der folgende Codeausschnitt zeigt Splines in Aktion.

>>> import numpy as np
>>> from sklearn.preprocessing import SplineTransformer
>>> X = np.arange(5).reshape(5, 1)
>>> X
array([[0],
       [1],
       [2],
       [3],
       [4]])
>>> spline = SplineTransformer(degree=2, n_knots=3)
>>> spline.fit_transform(X)
array([[0.5  , 0.5  , 0.   , 0.   ],
       [0.125, 0.75 , 0.125, 0.   ],
       [0.   , 0.5  , 0.5  , 0.   ],
       [0.   , 0.125, 0.75 , 0.125],
       [0.   , 0.   , 0.5  , 0.5  ]])

Da die X sortiert ist, kann man die Bandmatrix-Ausgabe leicht erkennen. Nur die drei mittleren Diagonalen sind für degree=2 nicht Null. Je höher der Grad, desto mehr Überlappung der Splines.

Interessanterweise ist ein SplineTransformer mit degree=0 dasselbe wie ein KBinsDiscretizer mit encode='onehot-dense' und n_bins = n_knots - 1, wenn knots = strategy.

Beispiele

Referenzen#

7.3.8. Benutzerdefinierte Transformer#

Oft möchten Sie eine bestehende Python-Funktion in einen Transformer umwandeln, um die Datenbereinigung oder -verarbeitung zu unterstützen. Mit FunctionTransformer können Sie einen Transformer aus einer beliebigen Funktion implementieren. Um beispielsweise einen Transformer zu erstellen, der eine Log-Transformation in einer Pipeline anwendet, gehen Sie wie folgt vor:

>>> import numpy as np
>>> from sklearn.preprocessing import FunctionTransformer
>>> transformer = FunctionTransformer(np.log1p, validate=True)
>>> X = np.array([[0, 1], [2, 3]])
>>> # Since FunctionTransformer is no-op during fit, we can call transform directly
>>> transformer.transform(X)
array([[0.        , 0.69314718],
       [1.09861229, 1.38629436]])

Sie können sicherstellen, dass func und inverse_func die Inversen voneinander sind, indem Sie check_inverse=True setzen und fit vor transform aufrufen. Beachten Sie, dass eine Warnung ausgegeben wird und mit filterwarnings in einen Fehler umgewandelt werden kann.

>>> import warnings
>>> warnings.filterwarnings("error", message=".*check_inverse*.",
...                         category=UserWarning, append=False)

Ein vollständiges Codebeispiel, das die Verwendung eines FunctionTransformer zur Extraktion von Merkmalen aus Textdaten zeigt, finden Sie unter Column Transformer mit heterogenen Datenquellen und Zeitbezogenes Feature Engineering.