Condividi tramite


Ottimizzazione degli iperparametri

L'ottimizzazione degli iperparametri è il processo di ricerca dei valori ottimali per i parametri non appresi dal modello di Machine Learning durante il training, ma piuttosto impostato dall'utente prima dell'inizio del processo di training. Questi parametri sono comunemente denominati iperparametri ed esempi includono la frequenza di apprendimento, il numero di livelli nascosti in una rete neurale, il livello di regolarizzazione e le dimensioni del batch.

Le prestazioni di un modello di Machine Learning possono essere estremamente sensibili alla scelta degli iperparametri e il set ottimale di iperparametri può variare notevolmente a seconda del problema e del set di dati specifico. L'ottimizzazione degli iperparametri è quindi un passaggio critico nella pipeline di apprendimento automatico, in quanto può avere un impatto significativo sull'accuratezza e sulle prestazioni di generalizzazione del modello.

In Infrastruttura i data scientist possono usare FLAML, una libreria Python leggera per l'automazione efficiente delle operazioni di Machine Learning e intelligenza artificiale, per i requisiti di ottimizzazione degli iperparametri. All'interno dei notebook di Fabric, gli utenti possono chiamare flaml.tune per l'ottimizzazione economica degli iperparametri.

Ottimizzazione del flusso di lavoro

Esistono tre passaggi essenziali per usare flaml.tune per portare a termine un'attività di ottimizzazione di base:

  1. Specificare l'obiettivo di ottimizzazione in relazione agli iperparametri.
  2. Specificare uno spazio di ricerca degli iperparametri.
  3. Specificare i vincoli di ottimizzazione, inclusi i vincoli sul budget delle risorse per eseguire l'ottimizzazione, i vincoli sulle configurazioni o/e i vincoli su una o più metriche specifiche.

Obiettivo di ottimizzazione

Il primo passaggio consiste nel specificare l'obiettivo di ottimizzazione. A tale scopo, è necessario innanzitutto specificare la procedura di valutazione in relazione agli iperparametri in una funzione definita dall'utente evaluation_function. La funzione richiede una configurazione degli iperparametri come input. Può semplicemente restituire un valore metrico in forma scalare o restituire un dizionario di coppie di nomi e valori delle metriche.

Nell'esempio seguente è possibile definire una funzione di valutazione rispetto a due iperparametri denominati x e y.

import time

def evaluate_config(config: dict):
    """evaluate a hyperparameter configuration"""
    score = (config["x"] - 85000) ** 2 - config["x"] / config["y"]


    faked_evaluation_cost = config["x"] / 100000
    time.sleep(faked_evaluation_cost)
    # we can return a single float as a score on the input config:
    # return score
    # or, we can return a dictionary that maps metric name to metric value:
    return {"score": score, "evaluation_cost": faked_evaluation_cost, "constraint_metric": config["x"] * config["y"]}

Spazio di ricerca

Specificare quindi lo spazio di ricerca degli iperparametri. Nello spazio di ricerca è necessario specificare valori validi per gli iperparametri e come vengono campionati questi valori, ad esempio da una distribuzione uniforme o da una distribuzione uniforme del log. Nell'esempio seguente è possibile fornire lo spazio di ricerca per gli x iperparametri e y. I valori validi per entrambi sono numeri interi compresi tra [1, 100.000]. Questi iperparametri vengono campionati in modo uniforme negli intervalli specificati.

from flaml import tune

# construct a search space for the hyperparameters x and y.
config_search_space = {
    "x": tune.lograndint(lower=1, upper=100000),
    "y": tune.randint(lower=1, upper=100000)
}

# provide the search space to tune.run
tune.run(..., config=config_search_space, ...)

Con FLAML, gli utenti possono personalizzare il dominio per un particolare iperparametro. In questo modo gli utenti possono specificare un tipo e un intervallo valido da cui campionare i parametri. FLAML supporta i tipi di iperparametri seguenti: float, integer e categorico. È possibile visualizzare l'esempio seguente per i domini di uso comune:

config = {
    # Sample a float uniformly between -5.0 and -1.0
    "uniform": tune.uniform(-5, -1),

    # Sample a float uniformly between 3.2 and 5.4,
    # rounding to increments of 0.2
    "quniform": tune.quniform(3.2, 5.4, 0.2),

    # Sample a float uniformly between 0.0001 and 0.01, while
    # sampling in log space
    "loguniform": tune.loguniform(1e-4, 1e-2),

    # Sample a float uniformly between 0.0001 and 0.1, while
    # sampling in log space and rounding to increments of 0.00005
    "qloguniform": tune.qloguniform(1e-4, 1e-1, 5e-5),

    # Sample a random float from a normal distribution with
    # mean=10 and sd=2
    "randn": tune.randn(10, 2),

    # Sample a random float from a normal distribution with
    # mean=10 and sd=2, rounding to increments of 0.2
    "qrandn": tune.qrandn(10, 2, 0.2),

    # Sample a integer uniformly between -9 (inclusive) and 15 (exclusive)
    "randint": tune.randint(-9, 15),

    # Sample a random uniformly between -21 (inclusive) and 12 (inclusive (!))
    # rounding to increments of 3 (includes 12)
    "qrandint": tune.qrandint(-21, 12, 3),

    # Sample a integer uniformly between 1 (inclusive) and 10 (exclusive),
    # while sampling in log space
    "lograndint": tune.lograndint(1, 10),

    # Sample a integer uniformly between 2 (inclusive) and 10 (inclusive (!)),
    # while sampling in log space and rounding to increments of 2
    "qlograndint": tune.qlograndint(2, 10, 2),

    # Sample an option uniformly from the specified choices
    "choice": tune.choice(["a", "b", "c"]),
}

Per altre informazioni su come personalizzare i domini all'interno dello spazio di ricerca, visitare la documentazione FLAML sulla personalizzazione degli spazi di ricerca.

Vincoli di ottimizzazione

L'ultimo passaggio consiste nello specificare i vincoli dell'attività di ottimizzazione. Una proprietà rilevante di flaml.tune è che è in grado di completare il processo di ottimizzazione all'interno di un vincolo di risorsa obbligatorio. A tale scopo, un utente può fornire vincoli di risorsa in termini di tempo di esecuzione (in secondi) usando l'argomento time_budget_s, o in termini di numero di tentativi usando l'argomento num_samples.

# Set a resource constraint of 60 seconds wall-clock time for the tuning.
flaml.tune.run(..., time_budget_s=60, ...)

# Set a resource constraint of 100 trials for the tuning.
flaml.tune.run(..., num_samples=100, ...)

# Use at most 60 seconds and at most 100 trials for the tuning.
flaml.tune.run(..., time_budget_s=60, num_samples=100, ...)

Per saperne di più sui vincoli di configurazione aggiuntivi, è possibile visitare la documentazione FLAML per le opzioni di ottimizzazione avanzate.

Mettere tutto insieme

Dopo aver definito i criteri di ottimizzazione, possiamo eseguire la prova di ottimizzazione. Per tenere traccia dei risultati della nostra prova, possiamo usare MLFlow autologging per acquisire le metriche e i parametri per ognuna di queste esecuzioni. Questo codice acquisisce l'intera versione di valutazione dell'ottimizzazione degli iperparametri, evidenziando ognuna delle combinazioni di iperparametri esaminate da FLAML.

import mlflow
mlflow.set_experiment("flaml_tune_experiment")
mlflow.autolog(exclusive=False)

with mlflow.start_run(nested=True, run_name="Child Run: "):
    analysis = tune.run(
        evaluate_config,  # the function to evaluate a config
        config=config_search_space,  # the search space defined
        metric="score",
        mode="min",  # the optimization mode, "min" or "max"
        num_samples=-1,  # the maximal number of configs to try, -1 means infinite
        time_budget_s=10,  # the time budget in seconds
    )

Nota

Quando la registrazione automatica di MLflow è abilitata, metriche, parametri e modelli devono essere registrati automaticamente durante l'esecuzione di MLflow. Tuttavia, questo varia in base al framework. Le metriche e i parametri per modelli specifici potrebbero non essere registrati. Ad esempio, nessuna metrica viene registrata per i modelli XGBoost, LightGBM, Spark e SynapseML. Per ulteriori informazioni su quali metriche e parametri vengono acquisiti da ogni framework, consultare la documentazione MLFlow autologging.

Ottimizzazione parallela con Apache Spark

La funzionalità flaml.tune supporta l'ottimizzazione sia di Apache Spark che degli apprenditori a nodo singolo. Inoltre, quando si ottimizzano i learner a nodo singolo (ad esempio, i learner di Scikit-Learn), è anche possibile parallelizzare l'ottimizzazione per velocizzare il processo di ottimizzazione impostando use_spark = True. Per i cluster Spark, per impostazione predefinita, FLAML avvia una versione di valutazione per ogni executor. È anche possibile personalizzare il numero di prove simultanee usando l'argomento n_concurrent_trials.


analysis = tune.run(
    evaluate_config,  # the function to evaluate a config
    config=config_search_space,  # the search space defined
    metric="score",
    mode="min",  # the optimization mode, "min" or "max"
    num_samples=-1,  # the maximal number of configs to try, -1 means infinite
    time_budget_s=10,  # the time budget in seconds
    use_spark=True,
)
print(analysis.best_trial.last_result)  # the best trial's result
print(analysis.best_config)  # the best config

Per altre informazioni su come parallelizzare i percorsi di ottimizzazione, è possibile visitare la documentazione FLAML per i processi Spark paralleli.

Visualizzare i risultati

Il flaml.visualization modulo fornisce funzioni di utilità per tracciare il processo di ottimizzazione usando Plotly. Usando Plotly, gli utenti possono esplorare in modo interattivo i risultati dell'esperimento AutoML. Per utilizzare queste funzioni di tracciamento, fornire come input il vostro oggetto ottimizzato flaml.AutoML o flaml.tune.tune.ExperimentAnalysis.

È possibile usare le funzioni seguenti all'interno del notebook:

  • plot_optimization_history: Visualizzare la cronologia di ottimizzazione di tutte le prove nell'esperimento.
  • plot_feature_importance: Importanza del grafico per ogni caratteristica nel set di dati.
  • plot_parallel_coordinate: traccia le relazioni tra i parametri ad alta dimensionalità nell'esperimento.
  • plot_contour: traccia la relazione tra i parametri come diagramma di contorno nell'esperimento.
  • plot_edf: Traccia il valore obiettivo EDF (funzione di distribuzione empirica) dell'esperimento.
  • plot_timeline: tracciare il cronogramma dell'esperimento.
  • plot_slice: tracciare la relazione di parametro come tracciato di sezione in uno studio.
  • plot_param_importance: tracciare l'importanza degli iperparametri dell'esperimento.
  • Regolare un modello SynapseML Spark LightGBM
  • Visualizzare i risultati di AutoML