Bedeutung von Feature Scaling#

Feature Scaling durch Standardisierung, auch Z-Score-Normalisierung genannt, ist ein wichtiger Vorverarbeitungsschritt für viele Algorithmen des maschinellen Lernens. Es beinhaltet die Neuskalierung jedes Features, so dass es eine Standardabweichung von 1 und einen Mittelwert von 0 hat.

Auch wenn baumbasierte Modelle (fast) nicht vom Scaling betroffen sind, benötigen viele andere Algorithmen normalisierte Features, oft aus unterschiedlichen Gründen: zur Erleichterung der Konvergenz (wie bei einer nicht bestraften logistischen Regression), um eine völlig andere Modellpassung im Vergleich zur Passung mit unskalierten Daten zu erzielen (wie bei k-Nächste-Nachbarn-Modellen). Letzteres wird im ersten Teil des vorliegenden Beispiels gezeigt.

Im zweiten Teil des Beispiels zeigen wir, wie sich die Hauptkomponentenanalyse (PCA) durch die Normalisierung von Features auswirkt. Um dies zu veranschaulichen, vergleichen wir die Hauptkomponenten, die mit PCA auf unskalierten Daten gefunden wurden, mit denen, die durch die Verwendung eines StandardScaler zur ersten Skalierung der Daten erzielt wurden.

Im letzten Teil des Beispiels zeigen wir die Auswirkung der Normalisierung auf die Genauigkeit eines Modells, das auf PCA-reduzierten Daten trainiert wurde.

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

Daten laden und vorbereiten#

Der verwendete Datensatz ist der Wein-Erkennungsdatensatz, der bei UCI verfügbar ist. Dieser Datensatz verfügt über kontinuierliche Features, die aufgrund unterschiedlicher gemessener Eigenschaften (z. B. Alkoholgehalt und Malicsäure) heterogen in ihrer Skalierung sind.

from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

X, y = load_wine(return_X_y=True, as_frame=True)
scaler = StandardScaler().set_output(transform="pandas")

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.30, random_state=42
)
scaled_X_train = scaler.fit_transform(X_train)

Auswirkung der Neuskalierung auf k-Nächste-Nachbarn-Modelle#

Um die Entscheidungsgrenze eines KNeighborsClassifier zu visualisieren, wählen wir in diesem Abschnitt eine Teilmenge von 2 Features aus, die Werte mit unterschiedlichen Größenordnungen aufweisen.

Beachten Sie, dass die Verwendung einer Teilmenge von Features zum Trainieren des Modells wahrscheinlich ein Feature mit hohem prädiktiven Einfluss weglässt, was zu einer Entscheidungsgrenze führt, die im Vergleich zu einem auf dem vollständigen Satz von Features trainierten Modell wesentlich schlechter ist.

import matplotlib.pyplot as plt

from sklearn.inspection import DecisionBoundaryDisplay
from sklearn.neighbors import KNeighborsClassifier

X_plot = X[["proline", "hue"]]
X_plot_scaled = scaler.fit_transform(X_plot)
clf = KNeighborsClassifier(n_neighbors=20)


def fit_and_plot_model(X_plot, y, clf, ax):
    clf.fit(X_plot, y)
    disp = DecisionBoundaryDisplay.from_estimator(
        clf,
        X_plot,
        response_method="predict",
        alpha=0.5,
        ax=ax,
    )
    disp.ax_.scatter(X_plot["proline"], X_plot["hue"], c=y, s=20, edgecolor="k")
    disp.ax_.set_xlim((X_plot["proline"].min(), X_plot["proline"].max()))
    disp.ax_.set_ylim((X_plot["hue"].min(), X_plot["hue"].max()))
    return disp.ax_


fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 6))

fit_and_plot_model(X_plot, y, clf, ax1)
ax1.set_title("KNN without scaling")

fit_and_plot_model(X_plot_scaled, y, clf, ax2)
ax2.set_xlabel("scaled proline")
ax2.set_ylabel("scaled hue")
_ = ax2.set_title("KNN with scaling")
KNN without scaling, KNN with scaling

Hier zeigt die Entscheidungsgrenze, dass die Anpassung skalierter oder nicht skalierter Daten zu völlig unterschiedlichen Modellen führt. Der Grund dafür ist, dass die Variable "Proline" Werte aufweist, die zwischen 0 und 1.000 liegen; während die Variable "Hue" zwischen 1 und 10 schwankt. Aus diesem Grund werden die Abstände zwischen den Stichproben hauptsächlich von den Unterschieden in den Werten von "Proline" beeinflusst, während die Werte von "Hue" vergleichsweise ignoriert werden. Wenn man einen StandardScaler verwendet, um diese Datenbank zu normalisieren, liegen beide skalierten Werte ungefähr zwischen -3 und 3, und die Nachbarschaftsstruktur wird von beiden Variablen mehr oder weniger gleichmäßig beeinflusst.

Auswirkung der Neuskalierung auf die PCA-Dimensionsreduktion#

Die Dimensionsreduktion mittels PCA besteht darin, die Features zu finden, die die Varianz maximieren. Wenn ein Feature mehr als die anderen variiert, nur aufgrund seiner jeweiligen Skalen, würde die PCA feststellen, dass ein solches Feature die Richtung der Hauptkomponenten dominiert.

Wir können die ersten Hauptkomponenten unter Verwendung aller ursprünglichen Features untersuchen

import pandas as pd

from sklearn.decomposition import PCA

pca = PCA(n_components=2).fit(X_train)
scaled_pca = PCA(n_components=2).fit(scaled_X_train)
X_train_transformed = pca.transform(X_train)
X_train_std_transformed = scaled_pca.transform(scaled_X_train)

first_pca_component = pd.DataFrame(
    pca.components_[0], index=X.columns, columns=["without scaling"]
)
first_pca_component["with scaling"] = scaled_pca.components_[0]
first_pca_component.plot.bar(
    title="Weights of the first principal component", figsize=(6, 8)
)

_ = plt.tight_layout()
Weights of the first principal component

Tatsächlich stellen wir fest, dass das Feature "Proline" die Richtung der ersten Hauptkomponente ohne Skalierung dominiert und etwa zwei Größenordnungen über den anderen Features liegt. Dies steht im Gegensatz zu der Beobachtung der ersten Hauptkomponente für die skalierte Version der Daten, bei der die Größenordnungen über alle Features hinweg ungefähr gleich sind.

Wir können die Verteilung der Hauptkomponenten in beiden Fällen visualisieren

fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(10, 5))

target_classes = range(0, 3)
colors = ("blue", "red", "green")
markers = ("^", "s", "o")

for target_class, color, marker in zip(target_classes, colors, markers):
    ax1.scatter(
        x=X_train_transformed[y_train == target_class, 0],
        y=X_train_transformed[y_train == target_class, 1],
        color=color,
        label=f"class {target_class}",
        alpha=0.5,
        marker=marker,
    )

    ax2.scatter(
        x=X_train_std_transformed[y_train == target_class, 0],
        y=X_train_std_transformed[y_train == target_class, 1],
        color=color,
        label=f"class {target_class}",
        alpha=0.5,
        marker=marker,
    )

ax1.set_title("Unscaled training dataset after PCA")
ax2.set_title("Standardized training dataset after PCA")

for ax in (ax1, ax2):
    ax.set_xlabel("1st principal component")
    ax.set_ylabel("2nd principal component")
    ax.legend(loc="upper right")
    ax.grid()

_ = plt.tight_layout()
Unscaled training dataset after PCA, Standardized training dataset after PCA

Aus der obigen Grafik beobachten wir, dass die Skalierung der Features vor der Dimensionsreduktion zu Komponenten mit der gleichen Größenordnung führt. In diesem Fall verbessert sie auch die Trennbarkeit der Klassen. Tatsächlich bestätigen wir im nächsten Abschnitt, dass eine bessere Trennbarkeit positive Auswirkungen auf die Gesamtleistung des Modells hat.

Auswirkung der Neuskalierung auf die Modellleistung#

Zuerst zeigen wir, wie die optimale Regularisierung einer LogisticRegressionCV von der Skalierung oder Nicht-Skalierung der Daten abhängt

import numpy as np

from sklearn.linear_model import LogisticRegressionCV
from sklearn.pipeline import make_pipeline

Cs = np.logspace(-5, 5, 20)

unscaled_clf = make_pipeline(
    pca, LogisticRegressionCV(Cs=Cs, use_legacy_attributes=False, l1_ratios=(0,))
)
unscaled_clf.fit(X_train, y_train)

scaled_clf = make_pipeline(
    scaler,
    pca,
    LogisticRegressionCV(Cs=Cs, use_legacy_attributes=False, l1_ratios=(0,)),
)
scaled_clf.fit(X_train, y_train)

print(f"Optimal C for the unscaled PCA: {unscaled_clf[-1].C_:.4f}\n")
print(f"Optimal C for the standardized data with PCA: {scaled_clf[-1].C_:.2f}")
Optimal C for the unscaled PCA: 0.0004

Optimal C for the standardized data with PCA: 20.69

Die Notwendigkeit der Regularisierung ist höher (niedrigere Werte von C) für die Daten, die vor der Anwendung von PCA nicht skaliert wurden. Wir bewerten nun die Auswirkung der Skalierung auf die Genauigkeit und den mittleren Log-Loss der optimalen Modelle

from sklearn.metrics import accuracy_score, log_loss

y_pred = unscaled_clf.predict(X_test)
y_pred_scaled = scaled_clf.predict(X_test)
y_proba = unscaled_clf.predict_proba(X_test)
y_proba_scaled = scaled_clf.predict_proba(X_test)

print("Test accuracy for the unscaled PCA")
print(f"{accuracy_score(y_test, y_pred):.2%}\n")
print("Test accuracy for the standardized data with PCA")
print(f"{accuracy_score(y_test, y_pred_scaled):.2%}\n")
print("Log-loss for the unscaled PCA")
print(f"{log_loss(y_test, y_proba):.3}\n")
print("Log-loss for the standardized data with PCA")
print(f"{log_loss(y_test, y_proba_scaled):.3}")
Test accuracy for the unscaled PCA
35.19%

Test accuracy for the standardized data with PCA
96.30%

Log-loss for the unscaled PCA
0.957

Log-loss for the standardized data with PCA
0.0825

Ein deutlicher Unterschied in der Vorhersagegenauigkeit wird beobachtet, wenn die Daten vor der PCA skaliert werden, da diese die unskalierte Version bei weitem übertrifft. Dies entspricht der Intuition aus der Grafik im vorherigen Abschnitt, wo die Komponenten linear trennbar werden, wenn vor der Verwendung von PCA skaliert wird.

Beachten Sie, dass in diesem Fall die Modelle mit skalierten Features besser abschneiden als die Modelle mit nicht skalierten Features, da erwartet wird, dass alle Variablen prädiktiv sind und wir vermeiden, dass einige von ihnen vergleichsweise ignoriert werden.

Wenn die Variablen mit niedrigeren Skalen nicht prädiktiv wären, könnte man nach der Skalierung der Features eine Leistungsverschlechterung erfahren: verrauschte Features würden nach der Skalierung stärker zur Vorhersage beitragen und daher würde die Skalierung das Overfitting erhöhen.

Nicht zuletzt beobachten wir, dass durch den Skalierungsschritt ein niedrigerer Log-Loss erzielt wird.

Gesamtlaufzeit des Skripts: (0 Minuten 1.041 Sekunden)

Verwandte Beispiele

Skalierung des Regularisierungsparameters für SVCs

Skalierung des Regularisierungsparameters für SVCs

Vergleich von linearen Bayes'schen Regressoren

Vergleich von linearen Bayes'schen Regressoren

Kernel PCA

Kernel PCA

Nearest Neighbors Klassifikation

Nearest Neighbors Klassifikation

Galerie generiert von Sphinx-Gallery