Ottimizzare gli iperparametri con Hyperopt

Completato

Hyperopt è una libreria Python open source per l'ottimizzazione dell'iperparametro. Hyperopt viene installato automaticamente quando si crea un cluster con una variante ML del runtime di Databricks. Per usarlo durante il training di un modello, segui questi passaggi:

  1. Definisci una funzione obiettivo per eseguire il training e valutare un modello.
  2. Definire lo spazio di ricerca degli iperparametri.
  3. Specificare l'algoritmo di ricerca.
  4. Esegui la funzione fmin Hyperopt per ottimizzare la funzione di training.

Definisci una funzione obiettivo

Hyperopt funziona chiamando in modo iterativo una funzione (spesso denominata funzione obiettivo) che restituisce un valore numerico e l'ottimizzazione dei parametri passati alla funzione in modo che il valore restituito sia ridotto al minimo; un approccio comunemente definito come ottimizzazione. Il primo requisito consiste quindi nell'incapsulare la logica di training e valutazione del modello in una funzione che:

  • Accetta un parametro contenente un elenco di valori iperparametri.
  • Esegue il training di un modello usando i valori di iperparametri specificati.
  • Valuta il modello in base a una metrica di destinazione per le prestazioni predittive.
  • Restituisce un valore numerico che riflette la metrica delle prestazioni in modo da ridurre le prestazioni del modello.

Ad esempio, la funzione seguente esegue il training di un modello di Machine Learning usando l'algoritmo LogisticRegression dalla libreria Spark MLlib.

def objective(params):
    from pyspark.ml.classification import LogisticRegression
    from pyspark.ml.evaluation import MulticlassClassificationEvaluator
    from hyperopt import STATUS_OK

    data_df = get_training_data() # This is just an example!
    splits = data_df.randomSplit([0.7, 0.3])
    training_df = splits[0]
    validation_df = splits[1]

    # Train a model using the provided hyperparameter values
    lr = LogisticRegression(labelCol="label", featuresCol="features",
                            maxIter=params['Iterations'],
                            regParam=params['Regularization'])
    model = lr.fit(training_df)

    # Evaluate the model
    predictions = model.transform(validation_df)
    eval = MulticlassClassificationEvaluator(labelCol="label",
                                             predictionCol="prediction",
                                             metricName="accuracy")
    accuracy = eval.evaluate(predictions)
    
    # Hyperopt *minimizes* the function, so return *negative* accuracy.
    return {'loss': -accuracy, 'status': STATUS_OK}

In questo esempio, il parametro params è un dizionario contenente valori per due valori con nome assegnato: iterazioni e regolarizzazione. Questi valori vengono assegnati agli iperparametri maxIter e regParam dell'algoritmo di regressione logistica usata per eseguire il training del modello.

La funzione valuta quindi il modello sottoposto a training per calcolare la metrica di accuratezza, ovvero un valore compreso tra 0,0 e 1,0 che indica la percentuale di stime effettuate dal modello corretto.

Infine, la funzione restituisce un valore che Hyperopt deve ridurre al minimo per migliorare il modello. In questo caso, la metrica di destinazione è accuratezza, per cui un valore superiore indica un modello migliore; quindi la funzione restituisce il valore come negativo (quindi maggiore è la precisione, minore il valore restituito).

Definire lo spazio di ricerca degli iperparametri

Ogni volta che viene chiamata la funzione obiettivo, è necessario provare un parametro contenente i valori dell'iperparametro. Per provare tutte le possibili combinazioni di valori, è necessario definire uno spazio di ricerca per Hyperopt per selezionare i valori da per ogni prova.

Hyperopt fornisce espressioni che è possibile usare per definire un intervallo di valori per ogni iperparametro, tra cui:

  • hp.choice(label, options): restituisce uno dei valori options elencati.
  • hp.randint(label, upper): restituisce un intero casuale nell'intervallo [0, superiore].
  • hp.uniform(label, low, high): restituisce un valore in modo uniforme tra low e high.
  • hp.normal(label, mu, sigma): restituisce un valore reale distribuito in modo normale con la media mu e la deviazione standard sigma.

Suggerimento

Per l'elenco completo delle espressioni, vedere la documentazione di Hyperopt.

Il codice di esempio seguente definisce uno spazio di ricerca per gli iperparametri usati nell'esempio precedente:

from hyperopt import hp

search_space = {
    'Iterations': hp.randint('Iterations', 10),
    'Regularization': hp.uniform('Regularization', 0.0, 1.0)
}

Specificare l'algoritmo di ricerca

Hyperopt usa un algoritmo di ricerca per selezionare i valori di iperparametri nello spazio di ricerca e provare a ottimizzare la funzione obiettivo. Esistono due opzioni principali per il modo in cui Hyperopt esegue le prove sullo spazio di ricerca:

  • hyperopt.tpe.suggest: Tree of Parzen Estimators (TPE), un approccio bayesiano che seleziona in modo adattivo nuove impostazioni degli iperparametri in base ai risultati precedenti.
  • hyperopt.rand.suggest: ricerca casuale, un approccio non adattivo che campiona casualmente lo spazio di ricerca.

Il codice di esempio seguente specifica l'algoritmo TPE.

from hyperopt import tpe

algo = tpe.suggest

Esegui la funzione fmin di Hyperopt

Infine, per eseguire un'esecuzione Hyperopt, è possibile usare la funzione fmin, che chiama ripetutamente la funzione obiettivo usando combinazioni di iperparametri dallo spazio di ricerca in base all'algoritmo di ricerca. L'obiettivo della funzione fmin consiste nel ridurre al minimo il valore restituito dalla funzione obiettivo (e quindi ottimizzare le prestazioni del modello).

Il codice di esempio seguente usa la funzione fmin per chiamare la funzione obiettivo definita in precedenza. Vengono usati lo spazio di ricerca e l’algoritmo definiti negli esempi precedenti e la funzione viene valutata fino a 100 volte prima che la funzione fmin restituisca la combinazione di valori del parametro con prestazioni migliori trovata.

from hyperopt import fmin

argmin = fmin(
  fn=objective,
  space=search_space,
  algo=algo,
  max_evals=100)

print("Best param values: ", argmin)

L'output del codice precedente è simile all'esempio seguente.

Best param values:  {'Iterations': 6, 'Regularization': 0.5461699702338606}