Optuna i Python: Komplett guide till hyperparameteroptimering (2026)

Komplett guide till Optuna 4.x för hyperparameteroptimering i Python: TPE-samplern, pruners, multi-objective sökning och distribuerad körning med körbara exempel för scikit-learn och XGBoost.

Optuna Python: Komplett guide (2026)

Uppdaterad: 14 juni 2026

Optuna är ett open-source-ramverk för hyperparameteroptimering i Python som använder Bayesianska samplers (huvudsakligen Tree-structured Parzen Estimator) för att hitta bra hyperparametrar med betydligt färre försök än rutnätssökning. I version 4.x kombinerar Optuna en imperativ define-by-run-API med inbyggda pruners, multi-objective sökning, distribuerad körning över en delad databas och en webbaserad dashboard. Så, i den här guiden går jag igenom hur ramverket fungerar statistiskt, visar körbar kod med scikit-learn och XGBoost, och jämför det med GridSearchCV, Hyperopt och Ray Tune.

  • Optuna 4.x använder TPE-samplern som standard och konvergerar oftast på 50–200 försök där rutnätssökning skulle behöva tusentals.
  • Pruners (MedianPruner, HyperbandPruner, SuccessiveHalvingPruner) avbryter olovande försök tidigt och kan halvera den totala körningstiden.
  • API:t är define-by-run: hyperparametrarna föreslås inuti din objektivfunktion med trial.suggest_*, vilket gör villkorade sökrymder triviala.
  • Distribuerad sökning fungerar genom att flera processer skriver till samma SQLite-, MySQL- eller PostgreSQL-databas via en RDB-storage.
  • Optuna stödjer multi-objective optimering med NSGA-II och returnerar en Pareto-front i stället för en enskild bästa konfiguration.
  • Officiella integrationer finns för scikit-learn, XGBoost, LightGBM, PyTorch Lightning, Keras och TensorFlow.

Vad är Optuna och varför använda det?

Optuna är ett open-source-bibliotek för hyperparameteroptimering, ursprungligen släppt av Preferred Networks 2018 och beskrivet i KDD-pappret av Akiba et al. (2019). Till skillnad från scikit-learns GridSearchCV och RandomizedSearchCV, som behandlar varje försök som oberoende, bygger Optuna upp en probabilistisk modell över sökrymden och använder den för att föreslå nästa konfiguration. Detta kallas sekventiell modellbaserad optimering (SMBO) och är samma familj av metoder som ligger bakom Spearmint, Hyperopt och SMAC3.

Den praktiska konsekvensen? Jag har sällan behövt mer än 100–200 försök i mitt eget arbete för att hitta hyperparametrar som ligger inom 1 % av en uttömmande sökning, även när sökrymden har tio dimensioner. I version 4.x, släppt under 2024 och med aktivt underhållen dokumentation, är define-by-run-API:t det centrala designvalet. Du anropar trial.suggest_float(), trial.suggest_int() och trial.suggest_categorical() inuti själva objektivfunktionen, vilket gör att villkorade hyperparametrar (till exempel "välj bara max_depth om booster='gbtree'") uttrycks som vanlig Python-kod i stället för deklarativa konfigurationsobjekt.

Om du tidigare läst min artikel om feature engineering med scikit-learn Pipelines är Optuna den naturliga nästa pusselbiten: en pipeline definierar transformationerna, Optuna optimerar hyperparametrarna för både transformatorerna och modellen i samma sökning.

Installation och den första studien

Installera Optuna och dess valfria visualiseringsberoenden. För version 4.x stöder ramverket Python 3.9 och senare. Jag rekommenderar 3.12 eller 3.13 för bästa prestanda.

pip install "optuna>=4.0,<5.0" optuna-dashboard plotly scikit-learn

Två centrala koncept: en study är hela optimeringssessionen och håller historiken över alla försök, medan ett trial är en enskild evaluering av objektivfunktionen. Här är ett minimalt körbart exempel som hittar parametern x som minimerar (x - 2)^2:

import optuna

def objective(trial: optuna.Trial) -> float:
    x = trial.suggest_float("x", -10.0, 10.0)
    return (x - 2.0) ** 2

study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=50, show_progress_bar=True)

print(f"Bästa värde: {study.best_value:.6f}")
print(f"Bästa parameter: {study.best_params}")

Efter 50 försök ligger study.best_params["x"] typiskt inom 0.01 av det sanna optimum 2.0. Notera att vi inte deklarerade sökrymden i förväg, den definieras av anropen till trial.suggest_float() inuti funktionen. För att spara studien till disk så att den överlever en kraschad notebook skickar du in en RDB-URL: storage="sqlite:///study.db". Då kan du ladda om den med optuna.load_study(study_name=..., storage=...) och fortsätta där du slutade, en funktion som blir kritisk i nästa avsnitt om distribuerad sökning.

Hur fungerar TPE-samplern under huven?

Tree-structured Parzen Estimator (TPE), som introducerades av Bergstra et al. i NeurIPS 2011 och är standardsamplern i Optuna, är en av de mest missförstådda komponenterna i biblioteket. Den modellerar inte målfunktionen direkt. I stället delar den upp historiken över försök i två grupper baserat på ett kvantilstrikt: de bästa γ % av försöken (typiskt γ = 25 %) och resten. Sedan estimeras två densiteter, l(x) för de bra försöken och g(x) för de dåliga, med Parzen-fönster (kernel density estimation).

Nästa konfiguration väljs genom att dra många kandidater från l(x) och välja den som maximerar förhållandet l(x) / g(x). Intuitionen? Vi vill ha värden som är sannolika under "bra"-fördelningen och osannolika under "dåliga". Det är en bra approximation av Expected Improvement-kriteriet utan att vi behöver träna en gaussisk processmodell, som skalar dåligt över 20 dimensioner.

I praktiken betyder det att TPE behöver minst 10 slumpmässiga försök innan modellen blir användbar. Kontrollera detta med n_startup_trials. Om din sökrymd är liten och billig att utvärdera kan ren slumpmässig sökning (RandomSampler) faktiskt vara konkurrenskraftig de första 20–30 försöken. Min tumregel: använd TPE när varje försök tar minst tio sekunder. Under det är overheaden från KDE jämförbar med själva träningen.

Pruners: stoppa svaga försök tidigt

En pruner avbryter en pågående utvärdering om dess mellanresultat ser ohjälpligt sämre ut än historiken. För iterativa modeller (gradient boosting, neurala nätverk, alla algoritmer med ett tydligt epok- eller boost-rundsbegrepp) kan detta enkelt halvera den totala körtiden. Du rapporterar mellanresultat via trial.report(value, step) och kontrollerar sedan trial.should_prune().

import optuna
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import log_loss

X, y = load_breast_cancer(return_X_y=True)
X_tr, X_va, y_tr, y_va = train_test_split(X, y, test_size=0.25, random_state=0)

def objective(trial):
    params = {
        "n_estimators": 500,
        "learning_rate": trial.suggest_float("learning_rate", 1e-3, 0.3, log=True),
        "max_depth": trial.suggest_int("max_depth", 2, 8),
        "subsample": trial.suggest_float("subsample", 0.5, 1.0),
    }
    model = GradientBoostingClassifier(**params, random_state=0)
    # Träna stegvis och rapportera valideringsförlust per 25 estimatorer.
    for n in range(25, params["n_estimators"] + 1, 25):
        model.n_estimators = n
        model.fit(X_tr, y_tr)
        proba = model.predict_proba(X_va)
        loss = log_loss(y_va, proba)
        trial.report(loss, step=n)
        if trial.should_prune():
            raise optuna.TrialPruned()
    return loss

study = optuna.create_study(
    direction="minimize",
    pruner=optuna.pruners.MedianPruner(n_warmup_steps=100),
)
study.optimize(objective, n_trials=80)

De tre vanligaste pruner-strategierna är MedianPruner (avbryt om mellanresultatet är sämre än medianen av tidigare försök vid samma steg), HyperbandPruner (allokerar resurser enligt Successive Halving över flera nivåer av "bracket") och SuccessiveHalvingPruner. För deep learning är Hyperband nästan alltid valet. För boosting fungerar Median förvånansvärt bra och kräver mindre tuning.

Optuna med scikit-learn och XGBoost

Den vanligaste användningen i mitt dagliga arbete är att optimera en hel scikit-learn-pipeline (inklusive föregående feature-engineering-steg) tillsammans med modellen. Eftersom Optuna är define-by-run kan vi villkora valet av preprocessing-strategi på ett naturligt sätt.

import optuna
import xgboost as xgb
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, RobustScaler

X, y = fetch_california_housing(return_X_y=True)

def objective(trial):
    scaler_name = trial.suggest_categorical("scaler", ["standard", "robust", "none"])
    scaler = {"standard": StandardScaler(), "robust": RobustScaler(),
              "none": "passthrough"}[scaler_name]

    params = {
        "objective": "reg:squarederror",
        "eval_metric": "rmse",
        "tree_method": "hist",
        "n_estimators": trial.suggest_int("n_estimators", 200, 1500),
        "learning_rate": trial.suggest_float("eta", 1e-3, 0.3, log=True),
        "max_depth": trial.suggest_int("max_depth", 3, 10),
        "min_child_weight": trial.suggest_float("min_child_weight", 1.0, 20.0, log=True),
        "subsample": trial.suggest_float("subsample", 0.5, 1.0),
        "colsample_bytree": trial.suggest_float("colsample_bytree", 0.5, 1.0),
        "reg_lambda": trial.suggest_float("reg_lambda", 1e-3, 10.0, log=True),
        "random_state": 0,
    }
    pipe = Pipeline([("scaler", scaler), ("model", xgb.XGBRegressor(**params))])
    score = cross_val_score(pipe, X, y, cv=3, scoring="neg_root_mean_squared_error").mean()
    return -score  # Optuna minimerar; vi vänder tecknet.

study = optuna.create_study(direction="minimize", sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=120, n_jobs=2, show_progress_bar=True)

print(study.best_params)

Lägg märke till några detaljer. Jag använder log=True för learning_rate, min_child_weight och reg_lambda. Alla tre är parametrar där en log-skala är meningsfull, eftersom de naturligt spänner över flera storleksordningar. n_jobs=2 i optimize() kör två försök parallellt i trådpoolen. För CPU-bundna modeller som XGBoost måste du dock vara försiktig så att antalet trådar per försök gånger n_jobs inte överstiger antalet kärnor (jag har bränt mer än en kväll på den fällan). Mer om äkta parallellism i avsnittet om distribuerad sökning.

För scikit-learns egna estimatorer finns dessutom optuna.integration.OptunaSearchCV, en drop-in-ersättare för GridSearchCV som följer Estimator-protokollet. Jag använder det när jag vill behålla cross_val_score-flödet i ett befintligt projekt utan att skriva om objektivfunktionen.

Multi-objective optimering och avancerade samplers

I produktion är "bästa modell" sällan endimensionellt. Du vill kanske maximera AUROC och minimera inferenslatens, eller balansera RMSE mot modellstorlek på disk. Optuna stödjer detta direkt med directions=["maximize", "minimize"] och NSGA-II-samplern, som returnerar en hel Pareto-front i stället för en enskild bästa konfiguration.

import optuna
import time
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

X, y = load_digits(return_X_y=True)

def objective(trial):
    n = trial.suggest_int("n_estimators", 10, 500)
    d = trial.suggest_int("max_depth", 2, 30)
    model = RandomForestClassifier(n_estimators=n, max_depth=d, n_jobs=1, random_state=0)

    t0 = time.perf_counter()
    auc = cross_val_score(model, X, y, cv=3, scoring="roc_auc_ovr").mean()
    fit_time = time.perf_counter() - t0
    return auc, fit_time

study = optuna.create_study(
    directions=["maximize", "minimize"],
    sampler=optuna.samplers.NSGAIISampler(seed=0),
)
study.optimize(objective, n_trials=100)

for t in study.best_trials:  # En lista, inte ett enskilt försök.
    print(f"AUC={t.values[0]:.4f}, tid={t.values[1]:.2f}s, params={t.params}")

Förutom TPE och NSGA-II innehåller version 4.x även CmaEsSampler (CMA-ES, lämplig för stora kontinuerliga sökrymder med upp till några hundra dimensioner), QMCSampler (kvasi-monte carlo, bra som ren ersättning för slumpmässig sökning) och BoTorchSampler som internt använder Gaussiska processer via PyTorch. För <20 dimensioner och blandade typer är TPE fortfarande standardvalet. För rent kontinuerliga problem med många dimensioner ger CMA-ES ofta bättre resultat.

Distribuerad sökning och Optuna Dashboard

Det mest underutnyttjade verktyget i Optunas portfölj är, ärligt talat, distribuerad sökning. Genom att skapa en study med en delad RDB-storage (SQLite för lokala experiment, PostgreSQL eller MySQL i kluster) kan flera processer eller noder skriva till samma historik och samarbeta om sökningen.

# Process 1, 2, 3 ... N på olika maskiner eller GPU:er
import optuna

study = optuna.create_study(
    study_name="xgb-housing",
    storage="postgresql://user:pw@db:5432/optuna",
    load_if_exists=True,
    direction="minimize",
)
study.optimize(objective, n_trials=200)

Varje arbetare hämtar nästa försök från databasen och rapporterar resultatet tillbaka. TPE-modellen byggs alltid om från den globala historiken, så att försöket från en arbetare ger informationsvärde till alla andra. Det är fundamentalt annorlunda än enklare "embarrassingly parallel" rutnätssökning och är anledningen till att jag aldrig använder joblib.Parallel manuellt för hyperparameter-sökning längre.

Parallellt med körningen kan du starta Optuna Dashboard, ett webbgränssnitt som visar parallel coordinate-, slice- och hyperparameter importance-plots i realtid:

optuna-dashboard postgresql://user:pw@db:5432/optuna

Hyperparameter importance-analysen bakom optuna.importance.get_param_importances() använder en fANOVA- eller MeanDecreaseImpurity-modell och svarar på frågan "vilken parameter förklarar mest variation i resultatet?". Det är ett extremt värdefullt verktyg innan du publicerar en modell. Om en parameter har försumbar betydelse kan du fixera den och fokusera nästa sökning på det som faktiskt rör nålen.

Optuna vs GridSearchCV, Hyperopt och Ray Tune

Den vanligaste frågan jag får är "varför inte bara använda GridSearchCV?". Här är en saklig jämförelse mellan de fyra ramverken jag oftast väljer mellan.

EgenskapOptuna 4.xGridSearchCV (sklearn)HyperoptRay Tune
StandardsamplerTPEUttömmandeTPESlump / TPE / ASHA
Pruning / tidig stoppInbyggtNejBegränsatInbyggt (ASHA, PBT)
Multi-objectiveNSGA-II, NSGA-IIINejNejJa
Define-by-run APIJaNejNejJa
Distribuerad sökningVia RDBVia joblibVia MongoDBVia Ray-kluster
Inbyggd visualiseringDashboard + PlotlyNejTredjepartTensorBoard, MLflow
InlärningskurvaLågLägstMediumHög
Bäst förDe flesta ML-projektSmå sökrymderÄldre kodbaserStora kluster, RL

I korthet: använd GridSearchCV när sökrymden är liten (≤4 hyperparametrar med få diskreta värden vardera) och du vill ha ett rättvist baslinjeresultat. Använd Optuna för allt däremellan. Det är den bästa balansen mellan ergonomi, prestanda och ekosystem. Använd Ray Tune när du redan har ett Ray-kluster, kör population-based training eller behöver djupinlärnings-specifika scheduler som Async Successive Halving för hundratals GPU-noder. Hyperopt har samma algoritmiska kärna som Optuna men ett betydligt klumpigare API och en mindre aktiv community. Jag rekommenderar det bara för migrering av äldre kod, och även då är det värt att porta över till Optuna.

För djupinlärningsspecifika integrationer, se Optunas releasenoter på GitHub som dokumenterar förändringar i optuna.integration mellan minor-versioner. Det ursprungliga TPE-pappret av Bergstra et al. på NeurIPS 2011 är fortfarande den bästa primärkällan för den statistiska grunden.

Vanliga frågor

Är Optuna bättre än GridSearchCV?

För sökrymder med mer än fyra hyperparametrar eller någon kontinuerlig parameter är Optuna nästan alltid snabbare till bättre lösning, eftersom TPE-samplern lär sig vilka regioner av rymden som ger bra resultat. GridSearchCV är fortfarande användbart för små, helt diskreta sökrymder där du vill ha en uttömmande och därmed reproducerbar baslinje.

Hur många försök behöver jag för en Optuna-studie?

Som tumregel: minst 10 startup-försök plus 10 gånger antalet hyperparametrar. För en typisk XGBoost-sökning med 7 parametrar landar jag på 80–150 försök. För deep learning där varje försök är dyrt sätter jag oftast 50 och kompletterar med en aggressiv Hyperband-pruner.

Kan Optuna optimera flera mål samtidigt?

Ja. Skapa studien med directions=["maximize", "minimize"] och returnera en tuple från objektivfunktionen. Använd NSGAIISampler för att hämta en Pareto-front via study.best_trials. Det är vanligt när du balanserar prediktiv prestanda mot inferenslatens eller modellstorlek.

Hur sparar och återupptar jag en Optuna-studie?

Skicka in storage="sqlite:///study.db" och study_name="..." när du skapar studien. Återuppta med optuna.load_study(study_name=..., storage=...) eller skapa om med load_if_exists=True. Den persisterade historiken låter dig också köra flera processer mot samma studie för distribuerad sökning.

Vilken sampler ska jag välja i Optuna?

För blandade och måttligt små sökrymder (under 20 dimensioner) är TPE standardvalet. För rent kontinuerliga problem med högre dimensionalitet provar du CMA-ES. För multi-objective använder du NSGA-II. För mycket små rymder eller som baslinje fungerar QMC eller ren slumpmässig sökning förvånansvärt bra.

Dr. Elena Vasquez
Om Författaren Dr. Elena Vasquez

Data scientist with a PhD in computational statistics. Translates papers into pandas one notebook at a time.