Hinweis
Gehen Sie zum Ende, um den vollständigen Beispielcode herunterzuladen oder dieses Beispiel über JupyterLite oder Binder in Ihrem Browser auszuführen.
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
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')}
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
Vergleich von Random Forests und Histogram Gradient Boosting Modellen
Vergleich von zufälliger Suche und Gitter-Suche zur Hyperparameter-Schätzung