Beispiel-Pipeline zur Extraktion und Bewertung von Textmerkmalen#

Der in diesem Beispiel verwendete Datensatz ist Der 20 Newsgroups Textdatensatz, der automatisch heruntergeladen, zwischengespeichert und für das Dokumentklassifizierungsbeispiel wiederverwendet wird.

In diesem Beispiel stimmen wir die Hyperparameter eines bestimmten Klassifikators mithilfe von RandomizedSearchCV ab. Eine Demo zur Leistung einiger anderer Klassifikatoren finden Sie im Notebook Klassifizierung von Textdokumenten unter Verwendung von spärlichen Merkmalen.

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

Daten laden#

Wir laden zwei Kategorien aus dem Trainingssatz. Sie können die Anzahl der Kategorien anpassen, indem Sie ihre Namen zur Liste hinzufügen oder categories=None beim Aufruf des Datensatzladers fetch_20newsgroups einstellen, um alle 20 zu erhalten.

from sklearn.datasets import fetch_20newsgroups

categories = [
    "alt.atheism",
    "talk.religion.misc",
]

data_train = fetch_20newsgroups(
    subset="train",
    categories=categories,
    shuffle=True,
    random_state=42,
    remove=("headers", "footers", "quotes"),
)

data_test = fetch_20newsgroups(
    subset="test",
    categories=categories,
    shuffle=True,
    random_state=42,
    remove=("headers", "footers", "quotes"),
)

print(f"Loading 20 newsgroups dataset for {len(data_train.target_names)} categories:")
print(data_train.target_names)
print(f"{len(data_train.data)} documents")
Loading 20 newsgroups dataset for 2 categories:
['alt.atheism', 'talk.religion.misc']
857 documents

Pipeline mit Hyperparameter-Tuning#

Wir definieren eine Pipeline, die einen Textmerkmal-Vektorisierer mit einem einfachen, aber für die Textklassifizierung effektiven Klassifikator kombiniert.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import ComplementNB
from sklearn.pipeline import Pipeline

pipeline = Pipeline(
    [
        ("vect", TfidfVectorizer()),
        ("clf", ComplementNB()),
    ]
)
pipeline
Pipeline(steps=[('vect', TfidfVectorizer()), ('clf', ComplementNB())])
In einer Jupyter-Umgebung führen Sie diese Zelle bitte erneut aus, um die HTML-Darstellung anzuzeigen, oder vertrauen Sie dem Notebook.
Auf GitHub kann die HTML-Darstellung nicht gerendert werden. Versuchen Sie bitte, diese Seite mit nbviewer.org zu laden.


Wir definieren ein Gitter von Hyperparametern, die von der RandomizedSearchCV untersucht werden sollen. Die Verwendung einer GridSearchCV würde stattdessen alle möglichen Kombinationen im Gitter untersuchen, was rechenintensiv sein kann, während der Parameter n_iter der RandomizedSearchCV die Anzahl der zu bewertenden verschiedenen Zufallskombinationen steuert. Beachten Sie, dass das Setzen von n_iter größer als die Anzahl der möglichen Kombinationen in einem Gitter dazu führen würde, bereits untersuchte Kombinationen zu wiederholen. Wir suchen nach der besten Parameterkombination sowohl für die Merkmalsextraktion (vect__) als auch für den Klassifikator (clf__).

import numpy as np

parameter_grid = {
    "vect__max_df": (0.2, 0.4, 0.6, 0.8, 1.0),
    "vect__min_df": (1, 3, 5, 10),
    "vect__ngram_range": ((1, 1), (1, 2)),  # unigrams or bigrams
    "vect__norm": ("l1", "l2"),
    "clf__alpha": np.logspace(-6, 6, 13),
}

In diesem Fall ist n_iter=40 keine erschöpfende Suche im Gitter der Hyperparameter. In der Praxis wäre es interessant, den Parameter n_iter zu erhöhen, um eine aussagekräftigere Analyse zu erhalten. Folglich erhöht sich die Rechenzeit. Wir können sie reduzieren, indem wir die Parallelisierung über die Bewertung von Parameterkombinationen nutzen, indem wir die Anzahl der verwendeten CPUs über den Parameter n_jobs erhöhen.

from pprint import pprint

from sklearn.model_selection import RandomizedSearchCV

random_search = RandomizedSearchCV(
    estimator=pipeline,
    param_distributions=parameter_grid,
    n_iter=40,
    random_state=0,
    n_jobs=2,
    verbose=1,
)

print("Performing grid search...")
print("Hyperparameters to be evaluated:")
pprint(parameter_grid)
Performing grid search...
Hyperparameters to be evaluated:
{'clf__alpha': array([1.e-06, 1.e-05, 1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01,
       1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06]),
 'vect__max_df': (0.2, 0.4, 0.6, 0.8, 1.0),
 'vect__min_df': (1, 3, 5, 10),
 'vect__ngram_range': ((1, 1), (1, 2)),
 'vect__norm': ('l1', 'l2')}
from time import time

t0 = time()
random_search.fit(data_train.data, data_train.target)
print(f"Done in {time() - t0:.3f}s")
Fitting 5 folds for each of 40 candidates, totalling 200 fits
Done in 24.387s
print("Best parameters combination found:")
best_parameters = random_search.best_estimator_.get_params()
for param_name in sorted(parameter_grid.keys()):
    print(f"{param_name}: {best_parameters[param_name]}")
Best parameters combination found:
clf__alpha: 0.01
vect__max_df: 0.2
vect__min_df: 1
vect__ngram_range: (1, 1)
vect__norm: l1
test_accuracy = random_search.score(data_test.data, data_test.target)
print(
    "Accuracy of the best parameters using the inner CV of "
    f"the random search: {random_search.best_score_:.3f}"
)
print(f"Accuracy on test set: {test_accuracy:.3f}")
Accuracy of the best parameters using the inner CV of the random search: 0.816
Accuracy on test set: 0.709

Die Präfixe vect und clf sind erforderlich, um mögliche Mehrdeutigkeiten in der Pipeline zu vermeiden, sind aber für die Visualisierung der Ergebnisse nicht notwendig. Aus diesem Grund definieren wir eine Funktion, die die abgestimmten Hyperparameter umbenennt und die Lesbarkeit verbessert.

import pandas as pd


def shorten_param(param_name):
    """Remove components' prefixes in param_name."""
    if "__" in param_name:
        return param_name.rsplit("__", 1)[1]
    return param_name


cv_results = pd.DataFrame(random_search.cv_results_)
cv_results = cv_results.rename(shorten_param, axis=1)

Wir können ein plotly.express.scatter verwenden, um den Kompromiss zwischen Scoring-Zeit und mittlerem Test-Score (d. h. "CV-Score") zu visualisieren. Das Überfahren eines bestimmten Punkts mit der Maus zeigt die entsprechenden Parameter an. Fehlerbalken entsprechen einer Standardabweichung, wie sie in den verschiedenen Folds der Kreuzvalidierung berechnet wurde.

import plotly.express as px

param_names = [shorten_param(name) for name in parameter_grid.keys()]
labels = {
    "mean_score_time": "CV Score time (s)",
    "mean_test_score": "CV score (accuracy)",
}
fig = px.scatter(
    cv_results,
    x="mean_score_time",
    y="mean_test_score",
    error_x="std_score_time",
    error_y="std_test_score",
    hover_data=param_names,
    labels=labels,
)
fig.update_layout(
    title={
        "text": "trade-off between scoring time and mean test score",
        "y": 0.95,
        "x": 0.5,
        "xanchor": "center",
        "yanchor": "top",
    }
)
fig


Beachten Sie, dass die Modelle im Cluster in der oberen linken Ecke des Diagramms den besten Kompromiss zwischen Genauigkeit und Scoring-Zeit aufweisen. In diesem Fall erhöht die Verwendung von Bigrammen die erforderliche Scoring-Zeit, ohne die Genauigkeit der Pipeline erheblich zu verbessern.

Hinweis

Weitere Informationen zur Anpassung eines automatisierten Tunings zur Maximierung des Scores und Minimierung der Scoring-Zeit finden Sie im Beispiel-Notebook Benutzerdefinierte Nachverarbeitungsstrategie einer Gitter-Suche mit Kreuzvalidierung.

Wir können auch plotly.express.parallel_coordinates verwenden, um den mittleren Test-Score als Funktion der abgestimmten Hyperparameter weiter zu visualisieren. Dies hilft, Wechselwirkungen zwischen mehr als zwei Hyperparametern zu finden und Einblicke in ihre Relevanz für die Verbesserung der Leistung einer Pipeline zu geben.

Wir wenden eine math.log10-Transformation auf der alpha-Achse an, um den aktiven Bereich zu erweitern und die Lesbarkeit des Diagramms zu verbessern. Ein Wert \(x\) auf dieser Achse ist als \(10^x\) zu verstehen.

import math

column_results = param_names + ["mean_test_score", "mean_score_time"]

transform_funcs = dict.fromkeys(column_results, lambda x: x)
# Using a logarithmic scale for alpha
transform_funcs["alpha"] = math.log10
# L1 norms are mapped to index 1, and L2 norms to index 2
transform_funcs["norm"] = lambda x: 2 if x == "l2" else 1
# Unigrams are mapped to index 1 and bigrams to index 2
transform_funcs["ngram_range"] = lambda x: x[1]

fig = px.parallel_coordinates(
    cv_results[column_results].apply(transform_funcs),
    color="mean_test_score",
    color_continuous_scale=px.colors.sequential.Viridis_r,
    labels=labels,
)
fig.update_layout(
    title={
        "text": "Parallel coordinates plot of text classifier pipeline",
        "y": 0.99,
        "x": 0.5,
        "xanchor": "center",
        "yanchor": "top",
    }
)
fig


Das Parallelkoordinaten-Diagramm zeigt die Werte der Hyperparameter auf verschiedenen Spalten, während die Leistungskennzahl farblich kodiert ist. Es ist möglich, einen Bereich von Ergebnissen auszuwählen, indem Sie auf einer beliebigen Achse des Parallelkoordinaten-Diagramms klicken und halten. Sie können dann den Bereich auswählen und zwei Auswahlen kreuzen, um die Schnittpunkte zu sehen. Sie können eine Auswahl rückgängig machen, indem Sie erneut auf dieselbe Achse klicken.

Insbesondere bei dieser Hyperparameter-Suche ist interessant zu bemerken, dass die Top-Performing-Modelle nicht von der Regularisierung norm abzuhängen scheinen, sondern von einem Kompromiss zwischen max_df, min_df und der Regularisierungsstärke alpha. Der Grund dafür ist, dass das Einbeziehen von verrauschten Merkmalen (d. h. max_df nahe \(1.0\) oder min_df nahe \(0\)) dazu neigt, zu überanpassen, und daher eine stärkere Regularisierung erfordert, um dies auszugleichen. Weniger Merkmale zu haben, erfordert weniger Regularisierung und weniger Scoring-Zeit.

Die besten Genauigkeitswerte werden erzielt, wenn alpha zwischen \(10^{-6}\) und \(10^0\) liegt, unabhängig vom Hyperparameter norm.

Gesamtlaufzeit des Skripts: (0 Minuten 26,612 Sekunden)

Verwandte Beispiele

Klassifikation von Textdokumenten mit spärlichen Merkmalen

Klassifikation von Textdokumenten mit spärlichen Merkmalen

Vergleich von Random Forests und Histogram Gradient Boosting Modellen

Vergleich von Random Forests und Histogram Gradient Boosting Modellen

Column Transformer mit gemischten Typen

Column Transformer mit gemischten Typen

Vergleich von zufälliger Suche und Gitter-Suche zur Hyperparameter-Schätzung

Vergleich von zufälliger Suche und Gitter-Suche zur Hyperparameter-Schätzung

Galerie generiert von Sphinx-Gallery