Fin qui hai un job che addestra un modello. Perché diventi un processo ripetibile mancano tre cose: cercare gli iperparametri senza lanciare a mano venti varianti, spezzare il lavoro in passi riusabili e versionati, e decidere con un criterio dichiarato quale run ha vinto. In Azure Machine Learning sono tre strumenti distinti: sweep job, pipeline job e query MLflow.
Sweep job: automatizzare la ricerca degli iperparametri
Uno sweep job prende un command job (o un command component) e lo esegue molte volte variando i parametri dentro uno spazio di ricerca dichiarato: gli iperparametri discreti si esprimono con choice, quelli continui con distribuzioni come uniform, loguniform, normal e lognormal, più le varianti quantizzate tipo quniform.
Gli algoritmi di campionamento sono tre e la scelta è quasi sempre una domanda d’esame. Il random sampling accetta valori discreti e continui, supporta la terminazione anticipata delle prove scarse ed è il punto di partenza tipico. Il grid sampling è esaustivo e funziona solo con choice: ha senso quando lo spazio è piccolo. Il bayesian sampling sceglie le nuove prove sulla base dei risultati precedenti, supporta choice, uniform e quniform, e rende meglio con budget ampio (Microsoft suggerisce un numero massimo di job pari ad almeno venti volte il numero di iperparametri) e con poche prove concorrenti, perché una concorrenza bassa lascia a ogni nuova prova più risultati da cui imparare.
L’obiettivo si dichiara con primary_metric e goal, che vale Maximize o Minimize. Qui sta l’errore più comune: il nome in primary_metric deve coincidere esattamente con il nome della metrica che lo script logga, tipicamente con mlflow.log_metric("accuracy", value). Se il nome non combacia, il servizio non riceve nulla e la terminazione anticipata non ha su cosa lavorare.
Le politiche di terminazione anticipata sono tre, più il default «nessuna», con cui tutte le prove arrivano alla fine. La bandit policy chiude un job quando la sua metrica esce dal margine consentito rispetto alla migliore, margine espresso come slack_factor (rapporto) o slack_amount (valore assoluto). La median stopping policy calcola medie mobili e ferma i job peggiori della mediana. La truncation selection policy taglia a ogni intervallo una percentuale fissa dei peggiori, data da truncation_percentage. Tutte accettano evaluation_interval, la frequenza di applicazione, e delay_evaluation, che rimanda le prime valutazioni per non uccidere prove lente a partire.
I limiti sono il controllo di budget: max_total_trials, max_concurrent_trials, timeout per l’intero sweep e trial_timeout per la singola prova; se imposti sia il numero di prove sia il timeout, vince la soglia raggiunta per prima. Un dettaglio che si scopre tardi: ogni prova riparte da zero, ricostruendo anche i data loader, quindi la preparazione dei dati va spostata a monte in un passo di pipeline.
Pipeline: passi versionati invece di script incatenati
Un component è un pezzo di codice autoconsistente con metadati (nome, versione, tipo), un’interfaccia di input e output tipizzati e un comando con il proprio environment. Si registra con az ml component create, si versiona e si riusa fra progetti; la proprietà is_deterministic permette a Azure Machine Learning di riusare l’output di un’esecuzione precedente invece di rieseguire il passo.
Il pipeline job li mette in sequenza. Nel YAML, jobs elenca i passi (oggi command job e sweep job), settings porta default_compute, default_datastore, continue_on_step_failure (vero per default, quindi la pipeline prosegue sui rami indipendenti anche se un passo fallisce) e force_rerun (falso per default: se lo attivi, disabiliti il riuso della cache). Le dipendenze si esprimono con le espressioni: ${{parent.inputs.nome}} per un input della pipeline e ${{parent.jobs.passo.outputs.nome}} per collegare l’output di uno step all’input del successivo. È il grafo, non l’ordine di scrittura, a decidere l’esecuzione.
$schema: https://azuremlschemas.azureedge.net/latest/pipelineJob.schema.json
type: pipeline
settings:
default_compute: azureml:cpu-cluster
continue_on_step_failure: false
jobs:
prep:
type: command
component: azureml:data-prep@latest
inputs:
raw: ${{parent.inputs.raw_data}}
train:
type: command
component: azureml:train-model:7
inputs:
dataset: ${{parent.jobs.prep.outputs.clean_data}}
La pipeline si lancia in CI come qualsiasi altro job: un workflow GitHub Actions si autentica ad Azure (preferibilmente con OpenID Connect, senza segreti da ruotare), installa l’estensione e chiama az ml job create --file pipeline.yml --stream, così lo step fallisce se fallisce la pipeline. Per il riaddestramento periodico c’è l’alternativa nativa: uno schedule con trigger cron o recurrence, indipendente dal runner della CI.
Confrontare i job senza guardarli a occhio
Il confronto fra run si fa con MLflow, perché l’SDK v2 non espone API di lettura delle metriche. mlflow.search_runs() accetta experiment_names, experiment_ids o search_all_experiments=True; senza nessuno dei tre cerca solo nell’esperimento attivo. Restituisce un DataFrame pandas con colonne params.nome e metrics.nome, dove ogni metrica compare col suo ultimo valore.
import mlflow
runs = mlflow.search_runs(
experiment_names=["credit-default"],
filter_string="metrics.auc > 0.80 and params.model_family = 'lightgbm'",
)
best = runs.sort_values("metrics.auc", ascending=False).iloc[0]
Tre limiti da conoscere. Il filtro combina condizioni solo con AND, mai con OR. L’ordinamento con order_by funziona sugli attributi del run, non su metriche o parametri: per quelli si usa sort_values di pandas, come sopra. E per la serie completa di una metrica ripetuta serve MlflowClient.get_metric_history(). Le prove figlie di uno sweep o di una pipeline si recuperano filtrando sul tag mlflow.parentRunId.
Il salto MLOps è trasformare questo confronto in un cancello automatico: la pipeline promuove il candidato solo se batte il modello in produzione sulla stessa metrica primaria e sullo stesso set di valutazione, e altrimenti fa fallire il workflow. Confrontare due run addestrati su dati diversi non è un confronto.
Da ricordare per l esame
- I campionamenti sono random, grid (solo
choice, esaustivo) e bayesian; il default di early termination è «nessuna politica», quindi tutte le prove arrivano in fondo. - Le tre politiche sono bandit (
slack_factoroslack_amount), median stopping e truncation selection (truncation_percentage), tutte conevaluation_intervaledelay_evaluation. primary_metricdeve corrispondere esattamente al nome della metrica loggata con MLflow, egoalvaleMaximizeoMinimize.- Nel pipeline job
continue_on_step_failureè vero per default eforce_rerunè falso: la cache dei passi si riusa se non la disattivi. - Il confronto fra run si fa con
mlflow.search_runs(): filtri solo inAND, ordinamento su metriche via pandas, serie complete conget_metric_history().