

Bild vom Autor
# Einführung
Beim Bauen maschinelles Lernen Bei Modellen mit mittlerer bis hoher Komplexität gibt es eine Vielzahl von Modellparametern, die nicht aus Daten gelernt werden, sondern von uns a priori festgelegt werden müssen: Diese werden als bezeichnet Hyperparameter. Modelle wie Random-Forest-Ensembles und neuronale Netze verfügen über eine Vielzahl von Hyperparametern, die angepasst werden müssen, sodass jeder einzelne einen von vielen verschiedenen Werten annehmen kann. Dadurch werden die Möglichkeiten, selbst eine kleine Teilmenge von Hyperparametern zu konfigurieren, nahezu endlos. Dies bringt ein Downside mit sich: Die Identifizierung der optimalen Konfiguration dieser Hyperparameter – additionally derjenigen, die die beste Modellleistung erbringen – könnte wie der Versuch sein, die Nadel im Heuhaufen zu finden – oder noch schlimmer: im Ozean.
Dieser Artikel baut auf einem früheren Leitfaden von auf Beherrschung des maschinellen Lernens behandelt die Kunst der Hyperparameter-Abstimmung und verfolgt einen praktischen Ansatz, um die Verwendung mittelschwerer bis fortgeschrittener Hyperparameter-Abstimmungstechniken in der Praxis zu veranschaulichen.
Konkret erfahren Sie, wie Sie diese drei Hyperparameter-Tuning-Techniken anwenden:
- Randomisierte Suche
- Bayes’sche Optimierung
- sukzessive Halbierung
# Durchführen der Ersteinrichtung
Bevor wir beginnen, importieren wir die erforderlichen Bibliotheken und Abhängigkeiten. Wenn bei einer dieser Bibliotheken die Fehlermeldung „Modul nicht gefunden“ angezeigt wird, sollten Sie dies unbedingt tun pip set up zuerst die betreffende Bibliothek. Wir werden verwenden NumPy, scikit-lernenUnd Optuna:
import numpy as np
import time
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.ensemble import RandomForestClassifier
import optuna
import warnings
warnings.filterwarnings('ignore')
Wir werden auch den in den drei Beispielen verwendeten Datensatz laden: Modifiziertes Nationwide Institute of Requirements and Know-how (MNIST)ein Datensatz zur Klassifizierung niedrigaufgelöster Bilder handgeschriebener Ziffern.
print("=" * 70)
print("LOADING MNIST DATASET FOR IMAGE CLASSIFICATION")
print("=" * 70)
# Load digits dataset (light-weight model of MNIST: 8x8 photos, 1797 samples)
digits = load_digits()
X, y = digits.information, digits.goal
# Prepare-test break up
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Coaching cases: {X_train.form(0)}")
print(f"Check cases: {X_test.form(0)}")
print(f"Options: {X_train.form(1)}")
print(f"Courses: {len(np.distinctive(y))}")
print()
Als nächstes definieren wir einen Hyperparameter-Suchraum; Das heißt, wir identifizieren, welche Parameter und Teilmengen von Werten innerhalb jedes einzelnen Parameters wir in Kombination ausprobieren möchten.
print("=" * 70)
print("HYPERPARAMETER SEARCH SPACE")
print("=" * 70)
# Typical hyperparameters to discover in a random forest ensemble
param_space = {
'n_estimators': (10, 200), # Variety of bushes
'max_depth': (5, 50), # Most tree depth
'min_samples_split': (2, 20), # Min samples to separate node
'min_samples_leaf': (1, 10), # Min samples in leaf node
'max_features': (0.1, 1.0) # Fraction of options to think about
}
print("Search area:")
for param, bounds in param_space.gadgets():
print(f" {param}: {bounds}")
print()
Als letzten vorbereitenden Schritt definieren wir eine Funktion, die wiederverwendet wird. Es kapselt den Prozess des Trainings und der Bewertung eines Zufallswald-Ensemble-Modells unter einer bestimmten Hyperparameterkonfiguration, wobei Kreuzvalidierung (CV) neben Klassifizierungsgenauigkeit verwendet wird, um die Qualität des Modells zu bestimmen. Beachten Sie, dass diese Funktion von jeder der drei Techniken, die wir implementieren werden, sehr oft aufgerufen werden kann – so oft, wie Hyperparameter-Wertkombinationen zum Ausprobieren vorhanden sind.
def evaluate_model(params, X_train, y_train, cv=3):
# Instantiate a random forest mannequin with given hyperparameters
mannequin = RandomForestClassifier(
n_estimators=int(params('n_estimators')),
max_depth=int(params('max_depth')),
min_samples_split=int(params('min_samples_split')),
min_samples_leaf=int(params('min_samples_leaf')),
max_features=float(params('max_features')),
random_state=42,
n_jobs=-1 # Use all CPU cores for velocity
)
# Use CV to measure efficiency
# This provides us a extra sturdy estimate than a single prepare/val break up
scores = cross_val_score(mannequin, X_train, y_train, cv=cv,
scoring='accuracy', n_jobs=-1)
# Return the typical cross-validation accuracy
return np.imply(scores)
Jetzt sind wir bereit, die drei Techniken auszuprobieren!
# Implementierung einer randomisierten Suche
Wie der Identify schon sagt, werden bei der randomisierten Suche Hyperparameterkombinationen zufällig aus dem Suchraum ausgewählt, anstatt es erschöpfend zu versuchen alle mögliche Kombinationen in einem vordefinierten Suchraum, wie es bei der Rastersuche der Fall ist. Jeder Versuch ist unabhängig, ohne dass Erkenntnisse aus früheren Versuchen gewonnen werden. Dennoch ist dies in vielen Situationen eine äußerst effektive Methode, mit der in der Regel schneller qualitativ hochwertige Lösungen gefunden werden als bei der Rastersuche.
So kann eine zufällige Suche implementiert und für zufällige Waldensembles verwendet werden, um MNIST-Daten zu klassifizieren:
def randomized_search(n_trials=30):
start_time = time.time() # Non-compulsory: used to measure execution time
outcomes = ()
print(f"nRunning {n_trials} random trials...")
for i in vary(n_trials):
# RANDOM SAMPLING: hyperparameters are sampled independently utilizing numpy's random quantity era
params = {
'n_estimators': np.random.randint(param_space('n_estimators')(0),
param_space('n_estimators')(1)),
'max_depth': np.random.randint(param_space('max_depth')(0),
param_space('max_depth')(1)),
'min_samples_split': np.random.randint(param_space('min_samples_split')(0),
param_space('min_samples_split')(1)),
'min_samples_leaf': np.random.randint(param_space('min_samples_leaf')(0),
param_space('min_samples_leaf')(1)),
'max_features': np.random.uniform(param_space('max_features')(0),
param_space('max_features')(1))
}
# Consider a randomly outlined configuration
rating = evaluate_model(params, X_train, y_train)
outcomes.append({'params': params, 'rating': rating})
# Present a progress replace each 10 trials, for informative functions
if (i + 1) % 10 == 0:
best_so_far = max(outcomes, key=lambda x: x('rating'))
print(f" Trial {i+1}/{n_trials}: Finest rating up to now = {best_so_far('rating'):.4f}")
# Measure complete time taken
elapsed_time = time.time() - start_time
# Determine finest configuration discovered
best_result = max(outcomes, key=lambda x: x('rating'))
print(f"n✓ Accomplished in {elapsed_time:.2f} seconds")
print(f"Finest validation accuracy: {best_result('rating'):.4f}")
print(f"Finest parameters: {best_result('params')}")
return best_result, outcomes
# Name the tactic to carry out randomized search over 30 trials
random_best, random_results = randomized_search(n_trials=30)
Um das Verständnis zu erleichtern, werden neben dem Code Kommentare bereitgestellt. Die erhaltenen Ergebnisse werden den folgenden ähneln:
Working 30 random trials...
Trial 10/30: Finest rating up to now = 0.9617
Trial 20/30: Finest rating up to now = 0.9617
Trial 30/30: Finest rating up to now = 0.9617
✓ Accomplished in 64.59 seconds
Finest validation accuracy: 0.9617
Finest parameters: {'n_estimators': 195, 'max_depth': 16, 'min_samples_split': 8, 'min_samples_leaf': 2, 'max_features': 0.28306570555707966}
Beachten Sie die Zeit, die für die Ausführung des Hyperparameter-Suchvorgangs benötigt wurde, sowie die beste erreichte Validierungsgenauigkeit. In diesem Fall scheinen 10 Versuche ausreichend gewesen zu sein, um die optimale Konfiguration zu finden.
# Anwenden der Bayes’schen Optimierung
Diese Methode verwendet ein Hilfs- oder Ersatzmodell – insbesondere ein probabilistisches Modell, das auf Gaußschen Prozessen oder baumbasierten Strukturen basiert –, um die leistungsstärksten Hyperparametereinstellungen vorherzusagen. Studien sind nicht unabhängig; Jeder Versuch „lernt“ aus früheren Versuchen. Darüber hinaus versucht diese Methode, die Erkundung (Ausprobieren neuer Bereiche im Lösungsraum) und die Ausbeutung (Verfeinerung vielversprechender Bereiche) in Einklang zu bringen. Zusammenfassend haben wir eine intelligentere Methode als die Raster- und Zufallssuche.
Der Optuna Die Bibliothek bietet eine spezifische Implementierung der Bayes’schen Optimierung für die Optimierung von Hyperparametern, die einen baumstrukturierten Parzen Estimator (TPE) verwendet. Es klassifiziert Versuche in „gute“ oder „schlechte“ Gruppen, modelliert die probabilistische Verteilung über jede und nimmt Stichproben aus vielversprechenden Regionen.
Der gesamte Prozess kann wie folgt implementiert werden:
def bayesian_optimization(n_trials=30):
"""
Implementation of Bayesian optimization utilizing Optuna library.
"""
start_time = time.time()
def goal(trial):
"""
Optuna goal operate: given a trial, returns a rating.
"""
# Optuna can recommend values primarily based on previous efficiency
params = {
'n_estimators': trial.suggest_int('n_estimators',
param_space('n_estimators')(0),
param_space('n_estimators')(1)),
'max_depth': trial.suggest_int('max_depth',
param_space('max_depth')(0),
param_space('max_depth')(1)),
'min_samples_split': trial.suggest_int('min_samples_split',
param_space('min_samples_split')(0),
param_space('min_samples_split')(1)),
'min_samples_leaf': trial.suggest_int('min_samples_leaf',
param_space('min_samples_leaf')(0),
param_space('min_samples_leaf')(1)),
'max_features': trial.suggest_float('max_features',
param_space('max_features')(0),
param_space('max_features')(1))
}
# Consider and return rating (maximizing by default in Optuna)
return evaluate_model(params, X_train, y_train)
# The create_study() operate is utilized in Optuna to handle and run
# the general optimization course of
print(f"nRunning {n_trials} Bayesian optimization trials...")
examine = optuna.create_study(
route='maximize', # We wish to maximize accuracy
sampler=optuna.samplers.TPESampler(seed=42) # Bayesian algorithm
)
# Carry out optimization course of with progress callback
def callback(examine, trial):
if trial.quantity % 10 == 9:
print(f" Trial {trial.quantity + 1}/{n_trials}: Finest rating = {examine.best_value:.4f}")
examine.optimize(goal, n_trials=n_trials, callbacks=(callback), show_progress_bar=False)
elapsed_time = time.time() - start_time
print(f"n✓ Accomplished in {elapsed_time:.2f} seconds")
print(f"Finest validation accuracy: {examine.best_value:.4f}")
print(f"Finest parameters: {examine.best_params}")
return examine.best_params, examine.best_value, examine
bayesian_best_params, bayesian_best_score, bayesian_study = bayesian_optimization(n_trials=30)
Ausgabe (zusammengefasst):
✓ Accomplished in 62.66 seconds
Finest validation accuracy: 0.9673
Finest parameters: {'n_estimators': 150, 'max_depth': 33, 'min_samples_split': 2, 'min_samples_leaf': 1, 'max_features': 0.19145126698170384}
# Verwendung sukzessiver Halbierung
Die letzte der drei Methoden, die sukzessive Halbierung, gleicht die Größe des Suchraums mit den zugewiesenen Rechenressourcen professional möglicher Konfiguration aus. Es beginnt mit einer großen Auswahl an Konfigurationen, aber begrenzten Ressourcen (z. B. Trainingsdaten) professional Konfiguration. Nach und nach werden leistungsschwache Konfigurationen entfernt und mehr Ressourcen vielversprechenden Konfigurationen zugewiesen – ähnlich einem echten Turnier, bei dem stärkere Teilnehmer „überleben“.
Die folgende Implementierung wendet eine sukzessive Halbierung an, die durch eine schrittweise Änderung der Trainingssatzgröße gesteuert wird.
def successive_halving(n_initial=32, min_resource=0.25, max_resource=1.0):
start_time = time.time()
# Step 1: Defining preliminary hyperparameter configurations at random
print(f"nGenerating {n_initial} preliminary random configurations...")
configs = ()
for _ in vary(n_initial):
config = {
'n_estimators': np.random.randint(param_space('n_estimators')(0),
param_space('n_estimators')(1)),
'max_depth': np.random.randint(param_space('max_depth')(0),
param_space('max_depth')(1)),
'min_samples_split': np.random.randint(param_space('min_samples_split')(0),
param_space('min_samples_split')(1)),
'min_samples_leaf': np.random.randint(param_space('min_samples_leaf')(0),
param_space('min_samples_leaf')(1)),
'max_features': np.random.uniform(param_space('max_features')(0),
param_space('max_features')(1))
}
configs.append(config)
# Step 2: apply tournament-like successive rounds of elimination
current_configs = configs
current_resource = min_resource
round_num = 1
whereas len(current_configs) > 1 and current_resource <= max_resource:
# Decide quantity of coaching cases to make use of within the present spherical
n_samples = int(len(X_train) * current_resource)
print(f"n--- Spherical {round_num}: Evaluating {len(current_configs)} configs ---")
print(f" Utilizing {current_resource*100:.0f}% of coaching information ({n_samples} samples)")
# Subsample coaching cases
indices = np.random.alternative(len(X_train), dimension=n_samples, exchange=False)
X_subset = X_train(indices)
y_subset = y_train(indices)
# Consider all present configs with the present sources
scores = ()
for i, config in enumerate(current_configs):
rating = evaluate_model(config, X_subset, y_subset, cv=2) # Use cv=2 (minimal)
scores.append(rating)
if (i + 1) % 10 == 0 or (i + 1) == len(current_configs):
print(f" Evaluated {i+1}/{len(current_configs)} configs...")
# Elimination coverage: hold top-performing half solely
n_keep = max(1, len(current_configs) // 2)
sorted_indices = np.argsort(scores)(::-1) # Descending order
current_configs = (current_configs(i) for i in sorted_indices(:n_keep))
best_score = scores(sorted_indices(0))
print(f" → Retaining high {n_keep} configs. Finest rating: {best_score:.4f}")
# Replace sources, doubling them for the following spherical
current_resource = min(current_resource * 2, max_resource)
round_num += 1
# Remaining analysis of finest config discovered, given full coaching set
best_config = current_configs(0)
final_score = evaluate_model(best_config, X_train, y_train, cv=3)
elapsed_time = time.time() - start_time
print(f"n✓ Accomplished in {elapsed_time:.2f} seconds")
print(f"Finest validation accuracy: {final_score:.4f}")
print(f"Finest parameters: {best_config}")
return best_config, final_score
halving_best, halving_score = successive_halving(n_initial=32, min_resource=0.25, max_resource=1.0)
Das erhaltene Endergebnis könnte wie folgt aussehen:
✓ Accomplished in 56.18 seconds
Finest validation accuracy: 0.9645
Finest parameters: {'n_estimators': 158, 'max_depth': 39, 'min_samples_split': 5, 'min_samples_leaf': 2, 'max_features': 0.2269785516325355}
# Vergleich der Endergebnisse
Zusammenfassend lässt sich sagen, dass alle drei Methoden die optimale Konfiguration mit einer Validierungsgenauigkeit zwischen 96 % und 97 % fanden, wobei die Bayes’sche Optimierung mit geringem Abstand das beste Ergebnis erzielte. Die Ergebnisse sind im Hinblick auf die Effizienz deutlicher erkennbar, wobei die aufeinanderfolgende Halbierung die schnellsten Ergebnisse in etwas mehr als 56 Sekunden liefert, verglichen mit den 62–64 Sekunden, die bei den anderen beiden Techniken benötigt werden.
Iván Palomares Carrascosa ist ein führender Autor, Redner und Berater in den Bereichen KI, maschinelles Lernen, Deep Studying und LLMs. Er schult und leitet andere darin, KI in der realen Welt zu nutzen.
