Sperimentazione con MLFlow e Microsoft Fabric |  di Roger Noble |  Aprile 2024

 | Intelligenza-Artificiale

La follia del tessuto, parte 4

Immagine dell'autore e ChatGPT. Prompt “Progetta un'illustrazione, con immagini che rappresentano esperimenti sui dati, concentrandosi sui dati del basket”. ChatGPT, 4, OpenAI, 15 aprile. 2024. https://chat.openai.com.

Un enorme ringraziamento a Martin Chaves che è stato coautore di questo post e ha sviluppato gli script di esempio.

Non è un segreto che i sistemi di Machine Learning (ML) richiedano un'attenta messa a punto per diventare veramente utili, e sarebbe un evento estremamente raro che un modello funzioni perfettamente la prima volta che viene eseguito!

Quando si inizia il proprio viaggio nel machine learning, è facile cadere in una trappola è provare molte cose diverse per migliorare le prestazioni, senza però registrare queste configurazioni lungo il percorso. Ciò rende quindi difficile sapere quale configurazione (o combinazione di configurazioni) ha ottenuto le prestazioni migliori.

Quando si sviluppano modelli, ci sono molte “manopole” e “leve” che possono essere regolate e spesso il modo migliore per migliorare è provare diverse configurazioni e vedere quale funziona meglio. Queste cose includono migliorare le funzionalità utilizzateprovare diverse architetture di modelli, modificare gli iperparametri del modello e altro. La sperimentazione deve essere sistematica e i risultati devono essere registrati. Ecco perché avere una buona configurazione per eseguire questi esperimenti è fondamentale nello sviluppo di qualsiasi sistema pratico di ML, allo stesso modo in cui il controllo del codice sorgente è fondamentale per il codice.

Qui è dove esperimenti entra a giocare. Gli esperimenti sono un modo per tenere traccia di queste diverse configurazioni e dei risultati che ne derivano.

La cosa fantastica degli esperimenti in Fabric è che in realtà sono un involucro per Flusso MLuna piattaforma open source estremamente popolare per la gestione del ciclo di vita del machine learning end-to-end. Ciò significa che possiamo utilizzare tutte le fantastiche funzionalità che MLFlow ha da offrire, ma con l'ulteriore vantaggio di non doverci preoccupare di configurare l'infrastruttura richiesta da un ambiente MLFlow collaborativo. Questo ci permette di concentrarci sulle cose divertenti 😎!

In questo post esamineremo come utilizzare gli esperimenti in Fabric e come registrare e analizzare i risultati di questi esperimenti. Nello specifico, tratteremo:

  • Come funziona MLFlow?
  • Creazione e impostazione degli esperimenti
  • Esecuzione di esperimenti e registrazione dei risultati
  • Analisi dei risultati

Ad alto livello, MLFlow è una piattaforma che aiuta a gestire il ciclo di vita del machine learning end-to-end. È uno strumento che aiuta a tenere traccia degli esperimenti, a impacchettare il codice in esecuzioni riproducibili e a condividere e distribuire modelli. È essenzialmente un database dedicato a tenere traccia di tutte le diverse configurazioni e risultati degli esperimenti eseguiti.

Esistono due strutture organizzative principali in MLFlow: esperimenti E corre.

Un esperimento è un gruppo di esecuzioni, dove un'esecuzione è l'esecuzione di un blocco di codice, di una funzione o di uno script. Potrebbe trattarsi dell'addestramento di un modello, ma potrebbe anche essere utilizzato per tenere traccia di qualsiasi cosa in cui le cose potrebbero cambiare tra un'esecuzione e l'altra. Un esperimento è quindi un modo per raggruppare esecuzioni correlate.

Per ogni esecuzione, le informazioni possono essere registrate e allegate ad essa: potrebbero essere metriche, iperparametri, tag, artefatti (come grafici, file o altri output utili) e persino modelli! Allegando modelli alle esecuzioni, possiamo tenere traccia di quale modello è stato utilizzato in quale esecuzione e come si è comportato. Pensatelo come il controllo del codice sorgente per i modelli, che è qualcosa di cui parleremo nel prossimo post.

Le corse possono essere filtrate e confrontate. Questo ci consente di capire quali esecuzioni hanno avuto più successo, selezionare l'esecuzione con le migliori prestazioni e utilizzarne la configurazione (ad esempio, nella distribuzione).

Ora che abbiamo trattato le nozioni di base su come funziona MLFlow, vediamo come utilizzarlo in Fabric!

Come ogni cosa in Fabric, la creazione di oggetti può essere eseguita in diversi modi, sia dall'area di lavoro + Nuovo menu, utilizzando l'esperienza Data Science o nel codice. In questo caso utilizzeremo l'esperienza di Data Science.

Fig. 1 — Creazione di un esperimento utilizzando l'interfaccia utente. Immagine dell'autore.

Una volta fatto ciò, per utilizzare quell'esperimento in un Notebook, dobbiamo farlo import mlflow e imposta il nome dell'esperimento:

import mlflow

experiment_name = "(name of the experiment goes here)"

# Set the experiment
mlflow.set_experiment(experiment_name)

In alternativa, è possibile creare un esperimento dal codice, che richiede un comando aggiuntivo:

import mlflow

experiment_name = "(name of the experiment goes here)"

# First create the experiment
mlflow.create_experiment(name=experiment_name)

# Then select it
mlflow.set_experiment(experiment_name)

Tieni presente che, se esiste già un esperimento con quel nome, create_experiment genererà un errore. Possiamo evitarlo controllando prima l'esistenza di un esperimento e creandolo solo se non esiste:

# Check if experiment exists
# if not, create it
if not mlflow.get_experiment_by_name(experiment_name):
mlflow.create_experiment(name=experiment_name)

Ora che abbiamo impostato l'esperimento nel contesto corrente, possiamo iniziare a eseguire il codice che verrà salvato nell'esperimento.

Per iniziare a registrare i nostri risultati in un esperimento, dobbiamo avviare un'esecuzione. Questo viene fatto utilizzando il start_run() funzione e restituisce a run gestore del contesto. Ecco un esempio di come iniziare una corsa:


# Start the training job with `start_run()`
with mlflow.start_run(run_name="example_run") as run:
# rest of the code goes here

Una volta avviata l'esecuzione, possiamo iniziare a registrare metriche, parametri e artefatti. Ecco un esempio di codice che potrebbe farlo utilizzando un modello e un set di dati semplici, in cui registriamo il punteggio del modello e gli iperparametri utilizzati:

# Set the hyperparameters
hyper_params = {"alpha": 0.5, "beta": 1.2}

# Start the training job with `start_run()`
with mlflow.start_run(run_name="simple_training") as run:
# Create model and dataset
model = create_model(hyper_params)
X, y = create_dataset()

# Train model
model.fit(X, y)

# Calculate score
score = lr.score(X, y)

# Log metrics and hyper-parameters
print("Log metric.")
mlflow.log_metric("score", score)

print("Log params.")
mlflow.log_param("alpha", hyper_params("alpha"))
mlflow.log_param("beta", hyper_params("beta"))

Nel nostro esempio sopra, viene addestrato un modello semplice e viene calcolato il suo punteggio. Nota come è possibile registrare le metriche utilizzando mlflow.log_metric("metric_name", metric) e gli iperparametri possono essere registrati utilizzando mlflow.log_param("param_name", param).

I dati

Diamo ora un'occhiata al codice utilizzato per addestrare i nostri modelli, che si basano sui risultati delle partite di basket. I dati che stiamo esaminando provengono dai tornei di basket universitari statunitensi del 2024, ottenuti dalla competizione Kaggle di marzo Machine Learning Mania 2024, i cui dettagli possono essere trovati Quied è concesso in licenza con CC BY 4.0

Nella nostra configurazione, abbiamo voluto provare tre modelli diversi, che utilizzavano un numero crescente di parametri. Per ciascun modello, volevamo anche provare tre diversi tassi di apprendimento (un iperparametro che controlla quanto stiamo adattando i pesi della nostra rete per ogni iterazione). L'obiettivo era trovare la migliore combinazione di modello e tasso di apprendimento che ci avrebbe dato il meglio Punteggio Brier sul set di prova.

I modelli

Per definire l'architettura del modello abbiamo utilizzato TensorFlow, creando tre semplici reti neurali. Ecco le funzioni che hanno contribuito a definire i modelli.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

def create_model_small(input_shape):
model = Sequential((
Dense(64, activation='relu', input_shape=(input_shape,)),
Dense(1, activation='sigmoid')
))
return model

def create_model_medium(input_shape):
model = Sequential((
Dense(64, activation='relu', input_shape=(input_shape,)),
Dense(64, activation='relu'),
Dense(1, activation='sigmoid')
))
return model

def create_model_large(input_shape):
model = Sequential((
Dense(128, activation='relu', input_shape=(input_shape,)),
Dense(64, activation='relu'),
Dense(64, activation='relu'),
Dense(1, activation='sigmoid')
))
return model

Creare i nostri modelli in questo modo ci consente di sperimentare facilmente diverse architetture e vedere come si comportano. Possiamo quindi utilizzare un dizionario per crearne un po' fabbrica di modelliche ci permetterà di creare facilmente i modelli che vogliamo sperimentare.

Abbiamo anche definito la forma di input, ovvero il numero di funzionalità disponibili. Abbiamo deciso di addestrare i modelli per 100 epoche, che dovrebbero essere sufficienti per la convergenza 🤞.

model_dict = {
'model_sma': create_model_small, # small
'model_med': create_model_medium, # medium
'model_lar': create_model_large # large
}

input_shape = X_train_scaled_df.shape(1)
epochs = 100

Dopo questa configurazione iniziale, è arrivato il momento di ripetere il dizionario dei modelli. Per ogni modello è stato creato un esperimento. Nota come stiamo utilizzando lo snippet di codice di prima, dove prima controlliamo se l'esperimento esiste e solo se non esiste lo creiamo. Altrimenti lo impostiamo e basta.

import mlflow

for model_name in model_dict:

# create mlflow experiment
experiment_name = "experiment_v2_" + model_name

# Check if experiment exists
# if not, create it
if not mlflow.get_experiment_by_name(experiment_name):
mlflow.create_experiment(name=experiment_name)

# Set experiment
mlflow.set_experiment(experiment_name)

Dopo aver impostato l'esperimento, abbiamo eseguito tre esecuzioni per ciascun modello, provando diverse velocità di apprendimento (0.001, 0.01, 0.1).

for model_name in model_dict:

# Set the experiment
...

learning_rate_list = (0.001, 0.01, 0.1)

for lr in learning_rate_list:

# Create run name for better identification
run_name = f"{model_name}_{lr}"
with mlflow.start_run(run_name=run_name) as run:
...
# Train model
# Save metrics

Quindi, in ogni esecuzione, abbiamo inizializzato un modello, compilato e addestrato. La compilazione e la formazione sono state eseguite in una funzione separata, di cui parleremo in seguito. Poiché volevamo impostare il tasso di apprendimento, abbiamo dovuto inizializzare manualmente l'ottimizzatore Adam. Come metrica abbiamo utilizzato la funzione di perdita dell'errore quadratico medio (MSE), salvando il modello con la migliore perdita di convalida e registrato la perdita di formazione e convalida per garantire che il modello stesse convergendo.

def compile_and_train(model, X_train, y_train, X_val, y_val, epochs=100, learning_rate=0.001):
# Instantiate the Adam optimiser with the desired learning rate
optimiser = Adam(learning_rate=learning_rate)

model.compile(optimizer=optimiser, loss='mean_squared_error', metrics=('mean_squared_error'))

# Checkpoint to save the best model according to validation loss
checkpoint_cb = ModelCheckpoint("best_model.h5", save_best_only=True, monitor='val_loss')

history = model.fit(X_train, y_train, validation_data=(X_val, y_val),
epochs=epochs, callbacks=(checkpoint_cb), verbose=1)

# Load and return the best model saved during training
best_model = load_model("best_model.h5")
return history, best_model

Dopo aver inizializzato un modello, compilato e addestrato, il passo successivo è stato registrare le perdite di addestramento e convalida, calcolare il punteggio Brier per il set di test, quindi registrare il punteggio e il tasso di apprendimento utilizzato. In genere registreremo anche la perdita di formazione e convalida utilizzando il file step argomento in log_metriccosì:

# Log training and validation losses
for epoch in range(epochs):
train_loss = history.history('loss')(epoch)
val_loss = history.history('val_loss')(epoch)
mlflow.log_metric("train_loss", train_loss, step=epoch)
mlflow.log_metric("val_loss", val_loss, step=epoch)

Tuttavia, abbiamo deciso di creare noi stessi il grafico delle perdite di formazione e convalida matplotlib e registrarlo come artefatto.

Ecco la funzione trama:

import matplotlib.pyplot as plt

def create_and_save_plot(train_loss, val_loss, model_name, lr):
epochs = range(1, len(train_loss) + 1)

# Creating the plot
plt.figure(figsize=(10, 6))
plt.plot(epochs, train_loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)

plt.title(f"Training and Validation Loss (M: {model_name}, LR: {lr})")

# Save plot to a file
plot_path = f"{model_name}_{lr}_loss_plot.png"
plt.savefig(plot_path)
plt.close()

return plot_path

Mettendo tutto insieme, ecco come appare il codice:


with mlflow.start_run(run_name=run_name) as run:
# Create model and dataset
model = model_dict(model_name)(input_shape)

# Train model
history, best_model = compile_and_train(model,
X_train_scaled_df, y_train,
X_validation_scaled_df, y_validation,
epochs,
lr)

# Log training and validation loss plot as an artifact
train_loss = history.history('loss')
val_loss = history.history('val_loss')

plot_path = create_and_save_plot(train_loss, val_loss, model_name, lr)
mlflow.log_artifact(plot_path)

# Calculate score
brier_score = evaluate_model(best_model, X_test_scaled_df, y_test)

# Log metrics and hyper-parameters
mlflow.log_metric("brier", brier_score)

# Log hyper-param
mlflow.log_param("lr", lr)

# Log model
...

Per ogni corsa abbiamo anche registrato il modello, che ci sarà utile in seguito.

Sono stati eseguiti gli esperimenti, creando un esperimento per ciascun modello e tre diverse esecuzioni per ciascun esperimento con ciascuna velocità di apprendimento.

Ora che abbiamo eseguito alcuni esperimenti, è il momento di analizzare i risultati! Per fare ciò, possiamo tornare nell'area di lavoro, dove troveremo i nostri esperimenti appena creati con diverse esecuzioni.

Fig. 2 — Elenco degli esperimenti. Immagine dell'autore.

Cliccando su un esperimento, ecco cosa vedremo:

Fig. 3 — L'interfaccia utente dell'esperimento. Immagine dell'autore.

A sinistra troveremo tutte le esecuzioni relative a quell'esperimento. In questo caso, stiamo esaminando l'esperimento del modello piccolo. Per ogni esecuzione sono presenti due artefatti, il grafico delle perdite di convalida e il modello addestrato. Sono inoltre disponibili informazioni sulle proprietà della corsa, ovvero lo stato e la durata, nonché le metriche e gli iperparametri registrati.

Cliccando su Visualizza l'elenco delle corsesotto il Confronta le corse sezione, possiamo confrontare le diverse corse.

Fig. 4 — Confronto delle corse. Immagine dell'autore.

All'interno della visualizzazione dell'elenco delle corse, possiamo selezionare le corse che desideriamo confrontare. Nel confronto metrico scheda, possiamo trovare grafici che mostrano il punteggio Brier rispetto al tasso di apprendimento. Nel nostro caso, sembra che minore è il tasso di apprendimento, migliore è il punteggio. Potremmo anche andare oltre e creare più grafici per le diverse metriche rispetto ad altri iperparametri (se fossero state registrate metriche e iperparametri diversi).

Fig. 5 — Grafico che mostra il punteggio Brier rispetto al tasso di apprendimento. Immagine dell'autore.

Forse vorremmo filtrare le esecuzioni: è possibile farlo utilizzando Filtri. Ad esempio possiamo selezionare le piste che hanno un punteggio Brier inferiore a 0,25. È possibile creare filtri in base alle metriche e ai parametri registrati e alle proprietà delle esecuzioni.

Fig. 6 – Filtraggio delle analisi in base al punteggio Brier. Immagine dell'autore.

In questo modo possiamo confrontare visivamente le diverse esecuzioni e valutare quale configurazione ha portato alle prestazioni migliori. Questo può essere fatto anche utilizzando il codice: questo è qualcosa che verrà esplorato ulteriormente nel prossimo post.

Utilizzando l'interfaccia utente dell'esperimento, siamo quindi in grado di esplorare visivamente i diversi esperimenti ed esecuzioni, confrontandoli e filtrandoli secondo necessità, per capire quale configurazione funziona meglio.

E con questo si conclude la nostra esplorazione degli esperimenti in Fabric!

Non solo abbiamo spiegato come creare e impostare gli esperimenti, ma abbiamo anche spiegato come eseguirli e registrare i risultati. Abbiamo anche mostrato come analizzare i risultati, utilizzando l'interfaccia utente dell'esperimento per confrontare e filtrare le esecuzioni.

Nel prossimo post vedremo come selezionare il modello migliore e come implementarlo. Rimani sintonizzato!

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *