Modellkomplexitätseinfluss#

Demonstriert, wie sich die Modellkomplexität sowohl auf die Vorhersagegenauigkeit als auch auf die Rechenleistung auswirkt.

Wir werden zwei Datensätze verwenden
  • Diabetes-Datensatz für die Regression. Dieser Datensatz besteht aus 10 Messungen von Diabetes-Patienten. Die Aufgabe ist es, den Krankheitsfortschritt vorherzusagen;

  • Der 20 Newsgroups Textdatensatz für die Klassifizierung. Dieser Datensatz besteht aus Newsgroup-Beiträgen. Die Aufgabe ist es, vorherzusagen, zu welchem Thema (von 20 Themen) der Beitrag verfasst wurde.

Wir werden den Einfluss der Komplexität auf drei verschiedene Schätzer modellieren

Wir lassen die Modellkomplexität durch die Wahl relevanter Modellparameter in jedem unserer ausgewählten Modelle variieren. Als Nächstes messen wir den Einfluss auf die Rechenleistung (Latenz) und die Vorhersagekraft (MSE oder Hamming Loss).

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

import time

import matplotlib.pyplot as plt
import numpy as np

from sklearn import datasets
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import hamming_loss, mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.svm import NuSVR

# Initialize random generator
np.random.seed(0)

Daten laden#

Zuerst laden wir beide Datensätze.

Hinweis

Wir verwenden fetch_20newsgroups_vectorized, um den 20 Newsgroups-Datensatz herunterzuladen. Er gibt gebrauchsfertige Merkmale zurück.

Hinweis

X des 20 Newsgroups-Datensatzes ist eine spärliche Matrix, während X des Diabetes-Datensatzes ein Numpy-Array ist.

def generate_data(case):
    """Generate regression/classification data."""
    if case == "regression":
        X, y = datasets.load_diabetes(return_X_y=True)
        train_size = 0.8
    elif case == "classification":
        X, y = datasets.fetch_20newsgroups_vectorized(subset="all", return_X_y=True)
        train_size = 0.4  # to make the example run faster

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, train_size=train_size, random_state=0
    )

    data = {"X_train": X_train, "X_test": X_test, "y_train": y_train, "y_test": y_test}
    return data


regression_data = generate_data("regression")
classification_data = generate_data("classification")

Benchmark-Einfluss#

Als Nächstes können wir den Einfluss der Parameter auf den gegebenen Schätzer berechnen. In jeder Runde werden wir den Schätzer mit dem neuen Wert von changing_param einstellen und die Vorhersagezeiten, die Vorhersageleistung und die Komplexitäten sammeln, um zu sehen, wie sich diese Änderungen auf den Schätzer auswirken. Wir berechnen die Komplexität mithilfe von complexity_computer, der als Parameter übergeben wird.

def benchmark_influence(conf):
    """
    Benchmark influence of `changing_param` on both MSE and latency.
    """
    prediction_times = []
    prediction_powers = []
    complexities = []
    for param_value in conf["changing_param_values"]:
        conf["tuned_params"][conf["changing_param"]] = param_value
        estimator = conf["estimator"](**conf["tuned_params"])

        print("Benchmarking %s" % estimator)
        estimator.fit(conf["data"]["X_train"], conf["data"]["y_train"])
        conf["postfit_hook"](estimator)
        complexity = conf["complexity_computer"](estimator)
        complexities.append(complexity)
        start_time = time.time()
        for _ in range(conf["n_samples"]):
            y_pred = estimator.predict(conf["data"]["X_test"])
        elapsed_time = (time.time() - start_time) / float(conf["n_samples"])
        prediction_times.append(elapsed_time)
        pred_score = conf["prediction_performance_computer"](
            conf["data"]["y_test"], y_pred
        )
        prediction_powers.append(pred_score)
        print(
            "Complexity: %d | %s: %.4f | Pred. Time: %fs\n"
            % (
                complexity,
                conf["prediction_performance_label"],
                pred_score,
                elapsed_time,
            )
        )
    return prediction_powers, prediction_times, complexities

Parameter auswählen#

Wir wählen die Parameter für jeden unserer Schätzer aus, indem wir ein Wörterbuch mit allen erforderlichen Werten erstellen. changing_param ist der Name des Parameters, der in jedem Schätzer variieren wird. Die Komplexität wird durch complexity_label definiert und mithilfe von complexity_computer berechnet. Beachten Sie auch, dass wir je nach Schätzertyp unterschiedliche Daten übergeben.

def _count_nonzero_coefficients(estimator):
    a = estimator.coef_.toarray()
    return np.count_nonzero(a)


configurations = [
    {
        "estimator": SGDClassifier,
        "tuned_params": {
            "penalty": "elasticnet",
            "alpha": 0.001,
            "loss": "modified_huber",
            "fit_intercept": True,
            "tol": 1e-1,
            "n_iter_no_change": 2,
        },
        "changing_param": "l1_ratio",
        "changing_param_values": [0.25, 0.5, 0.75, 0.9],
        "complexity_label": "non_zero coefficients",
        "complexity_computer": _count_nonzero_coefficients,
        "prediction_performance_computer": hamming_loss,
        "prediction_performance_label": "Hamming Loss (Misclassification Ratio)",
        "postfit_hook": lambda x: x.sparsify(),
        "data": classification_data,
        "n_samples": 5,
    },
    {
        "estimator": NuSVR,
        "tuned_params": {"C": 1e3, "gamma": 2**-15},
        "changing_param": "nu",
        "changing_param_values": [0.05, 0.1, 0.2, 0.35, 0.5],
        "complexity_label": "n_support_vectors",
        "complexity_computer": lambda x: len(x.support_vectors_),
        "data": regression_data,
        "postfit_hook": lambda x: x,
        "prediction_performance_computer": mean_squared_error,
        "prediction_performance_label": "MSE",
        "n_samples": 15,
    },
    {
        "estimator": GradientBoostingRegressor,
        "tuned_params": {
            "loss": "squared_error",
            "learning_rate": 0.05,
            "max_depth": 2,
        },
        "changing_param": "n_estimators",
        "changing_param_values": [10, 25, 50, 75, 100],
        "complexity_label": "n_trees",
        "complexity_computer": lambda x: x.n_estimators,
        "data": regression_data,
        "postfit_hook": lambda x: x,
        "prediction_performance_computer": mean_squared_error,
        "prediction_performance_label": "MSE",
        "n_samples": 15,
    },
]

Code ausführen und Ergebnisse plotten#

Wir haben alle erforderlichen Funktionen für unseren Benchmark definiert. Nun durchlaufen wir die zuvor definierten verschiedenen Konfigurationen. Anschließend können wir die aus dem Benchmark erhaltenen Diagramme analysieren: Das Entspannen der L1-Strafe im SGD-Klassifikator reduziert den Vorhersagefehler, führt aber zu einer Erhöhung der Trainingszeit. Eine ähnliche Analyse können wir bezüglich der Trainingszeit durchführen, die mit der Anzahl der Support-Vektoren bei einem Nu-SVR zunimmt. Wir haben jedoch beobachtet, dass es eine optimale Anzahl von Support-Vektoren gibt, die den Vorhersagefehler reduziert. Tatsächlich führen zu wenige Support-Vektoren zu einem unterangepassten Modell, während zu viele Support-Vektoren zu einem überangepassten Modell führen. Die exakt gleiche Schlussfolgerung kann für das Gradient-Boosting-Modell gezogen werden. Der einzige Unterschied zum Nu-SVR ist, dass zu viele Bäume im Ensemble nicht so nachteilig sind.

def plot_influence(conf, mse_values, prediction_times, complexities):
    """
    Plot influence of model complexity on both accuracy and latency.
    """

    fig = plt.figure()
    fig.subplots_adjust(right=0.75)

    # first axes (prediction error)
    ax1 = fig.add_subplot(111)
    line1 = ax1.plot(complexities, mse_values, c="tab:blue", ls="-")[0]
    ax1.set_xlabel("Model Complexity (%s)" % conf["complexity_label"])
    y1_label = conf["prediction_performance_label"]
    ax1.set_ylabel(y1_label)

    ax1.spines["left"].set_color(line1.get_color())
    ax1.yaxis.label.set_color(line1.get_color())
    ax1.tick_params(axis="y", colors=line1.get_color())

    # second axes (latency)
    ax2 = fig.add_subplot(111, sharex=ax1, frameon=False)
    line2 = ax2.plot(complexities, prediction_times, c="tab:orange", ls="-")[0]
    ax2.yaxis.tick_right()
    ax2.yaxis.set_label_position("right")
    y2_label = "Time (s)"
    ax2.set_ylabel(y2_label)
    ax1.spines["right"].set_color(line2.get_color())
    ax2.yaxis.label.set_color(line2.get_color())
    ax2.tick_params(axis="y", colors=line2.get_color())

    plt.legend(
        (line1, line2), ("prediction error", "prediction latency"), loc="upper center"
    )

    plt.title(
        "Influence of varying '%s' on %s"
        % (conf["changing_param"], conf["estimator"].__name__)
    )


for conf in configurations:
    prediction_performances, prediction_times, complexities = benchmark_influence(conf)
    plot_influence(conf, prediction_performances, prediction_times, complexities)
plt.show()
  • Influence of varying 'l1_ratio' on SGDClassifier
  • Influence of varying 'nu' on NuSVR
  • Influence of varying 'n_estimators' on GradientBoostingRegressor
Benchmarking SGDClassifier(alpha=0.001, l1_ratio=0.25, loss='modified_huber',
              n_iter_no_change=2, penalty='elasticnet', tol=0.1)
Complexity: 4944 | Hamming Loss (Misclassification Ratio): 0.2687 | Pred. Time: 0.032131s

Benchmarking SGDClassifier(alpha=0.001, l1_ratio=0.5, loss='modified_huber',
              n_iter_no_change=2, penalty='elasticnet', tol=0.1)
Complexity: 1847 | Hamming Loss (Misclassification Ratio): 0.3264 | Pred. Time: 0.026102s

Benchmarking SGDClassifier(alpha=0.001, l1_ratio=0.75, loss='modified_huber',
              n_iter_no_change=2, penalty='elasticnet', tol=0.1)
Complexity: 997 | Hamming Loss (Misclassification Ratio): 0.3383 | Pred. Time: 0.019467s

Benchmarking SGDClassifier(alpha=0.001, l1_ratio=0.9, loss='modified_huber',
              n_iter_no_change=2, penalty='elasticnet', tol=0.1)
Complexity: 799 | Hamming Loss (Misclassification Ratio): 0.3481 | Pred. Time: 0.017868s

Benchmarking NuSVR(C=1000.0, gamma=3.0517578125e-05, nu=0.05)
Complexity: 18 | MSE: 5558.7313 | Pred. Time: 0.000174s

Benchmarking NuSVR(C=1000.0, gamma=3.0517578125e-05, nu=0.1)
Complexity: 36 | MSE: 5289.8022 | Pred. Time: 0.000248s

Benchmarking NuSVR(C=1000.0, gamma=3.0517578125e-05, nu=0.2)
Complexity: 72 | MSE: 5193.8353 | Pred. Time: 0.000390s

Benchmarking NuSVR(C=1000.0, gamma=3.0517578125e-05, nu=0.35)
Complexity: 124 | MSE: 5131.3279 | Pred. Time: 0.000605s

Benchmarking NuSVR(C=1000.0, gamma=3.0517578125e-05)
Complexity: 178 | MSE: 5149.0779 | Pred. Time: 0.000837s

Benchmarking GradientBoostingRegressor(learning_rate=0.05, max_depth=2, n_estimators=10)
Complexity: 10 | MSE: 4066.4812 | Pred. Time: 0.000145s

Benchmarking GradientBoostingRegressor(learning_rate=0.05, max_depth=2, n_estimators=25)
Complexity: 25 | MSE: 3551.1723 | Pred. Time: 0.000165s

Benchmarking GradientBoostingRegressor(learning_rate=0.05, max_depth=2, n_estimators=50)
Complexity: 50 | MSE: 3445.2171 | Pred. Time: 0.000201s

Benchmarking GradientBoostingRegressor(learning_rate=0.05, max_depth=2, n_estimators=75)
Complexity: 75 | MSE: 3433.0358 | Pred. Time: 0.000236s

Benchmarking GradientBoostingRegressor(learning_rate=0.05, max_depth=2)
Complexity: 100 | MSE: 3456.0602 | Pred. Time: 0.000269s

Schlussfolgerung#

Zusammenfassend können wir folgende Erkenntnisse ableiten

  • ein komplexeres (oder ausdrucksstärkeres) Modell erfordert eine längere Trainingszeit;

  • ein komplexeres Modell garantiert nicht, den Vorhersagefehler zu reduzieren.

Diese Aspekte hängen mit der Modellverallgemeinerung und der Vermeidung von Unter- oder Überanpassung zusammen.

Gesamtlaufzeit des Skripts: (0 Minuten 4,435 Sekunden)

Verwandte Beispiele

Gradient Boosting Regression

Gradient Boosting Regression

Analyse des Konzentrations-Prior-Typs der Variation im Bayes'schen Gaußschen Gemisch

Analyse des Konzentrations-Prior-Typs der Variation im Bayes'schen Gaußschen Gemisch

RBF SVM Parameter

RBF SVM Parameter

Frühes Stoppen in Gradient Boosting

Frühes Stoppen in Gradient Boosting

Galerie generiert von Sphinx-Gallery