W3docs

Grid Search per l'ottimizzazione degli iperparametri in Python

Scopri come usare GridSearchCV e RandomizedSearchCV in Python per ottimizzare gli iperparametri dei modelli di machine learning con scikit-learn.

L'ottimizzazione degli iperparametri è il processo di ricerca dei valori di configurazione per un modello di machine learning che non vengono appresi dai dati — come la profondità di un albero decisionale, la forza di regolarizzazione di una regressione logistica o il numero di neuroni in una rete neurale. La grid search è l'approccio più diretto: si definisce un insieme discreto di valori per ciascun iperparametro, si prova ogni combinazione e si mantiene quella migliore.

Questa pagina tratta:

  • Cosa sono gli iperparametri e perché sono importanti
  • Come GridSearchCV esplora esaustivamente una griglia di parametri
  • Come leggere cv_results_ e capire cosa è stato testato
  • L'uso di n_jobs=-1 per parallelizzare la ricerca
  • RandomizedSearchCV come alternativa più veloce per griglie di grandi dimensioni
  • La combinazione della grid search con una Pipeline per evitare la fuga di dati
  • Quando usare la grid search rispetto ad alternative più veloci

Tutti gli esempi usano i dataset integrati di scikit-learn, quindi puoi eseguirli immediatamente.

Cosa sono gli iperparametri?

Ogni modello di machine learning ha due tipi di parametri:

  • I parametri del modello vengono appresi automaticamente durante l'addestramento (es. i pesi in una rete neurale, le soglie di divisione in un albero decisionale).
  • Gli iperparametri vengono impostati da te prima che l'addestramento inizi. Controllano il processo di apprendimento stesso.

Scegliere iperparametri errati può lasciare un modello capace con prestazioni gravemente insufficienti. Ad esempio, un albero decisionale senza limite di profondità vada in overfitting sui dati di addestramento; uno con un limite di profondità pari a 1 andrà in underfitting. Il limite corretto dipende dai tuoi dati, e la grid search lo trova in modo sistematico anziché per tentativi.

Come funziona GridSearchCV

GridSearchCV di scikit-learn combina due idee:

  1. Enumerazione della griglia — genera ogni combinazione dei valori degli iperparametri che specifichi.
  2. Validazione incrociata — per ciascuna combinazione esegue la k-fold cross-validation (vedi Cross-Validation in Python) e registra il punteggio medio.

Al termine della ricerca, GridSearchCV memorizza la migliore combinazione e ridefinisce automaticamente un modello con quelle impostazioni sull'intero set di addestramento.

Il numero di addestramento è (combinazioni) × (fold di CV). Una griglia con 3 × 3 × 3 = 27 combinazioni e cv=5 esegue 135 addestramento — gestibile per modelli veloci, costoso per quelli lenti.

Esempio base di GridSearchCV

L'esempio seguente ottimizza un Decision Tree Classifier sul dataset Iris.

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)

# Model with no hyperparameters set yet
model = DecisionTreeClassifier(random_state=42)

# Define the grid of values to try
param_grid = {
    'max_depth': [2, 3, 5, None],
    'min_samples_split': [2, 5, 10],
    'criterion': ['gini', 'entropy'],
}

# cv=5 means 5-fold cross-validation for each combination
grid_search = GridSearchCV(
    estimator=model,
    param_grid=param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,       # use all CPU cores
    verbose=0,
)

grid_search.fit(X, y)

print("Best parameters:", grid_search.best_params_)
print("Best CV accuracy: {:.3f}".format(grid_search.best_score_))

Cosa fa ciascun argomento:

ArgomentoScopo
estimatorIl modello da ottimizzare. Funziona con qualsiasi estimator di scikit-learn.
param_gridDict che mappa i nomi dei parametri a liste di valori candidati.
cvNumero di fold nella cross-validation (5 è un valore predefinito comune).
scoringMetrica da ottimizzare. Per impostazione predefinita usa il metodo .score() dell'estimator.
n_jobsNumero di job paralleli. -1 usa tutti i core CPU disponibili.

Output tipico:

Best parameters: {'criterion': 'gini', 'max_depth': 3, 'min_samples_split': 2}
Best CV accuracy: 0.967

Lettura di cv_results_

Dopo l'addestramento, grid_search.cv_results_ è un dict di array — una voce per ogni combinazione testata. Le chiavi più utili sono:

import pandas as pd

results = pd.DataFrame(grid_search.cv_results_)

# Show top 5 combinations by mean test score
cols = ['param_max_depth', 'param_min_samples_split', 'param_criterion',
        'mean_test_score', 'std_test_score', 'rank_test_score']
print(results[cols].sort_values('rank_test_score').head(5).to_string(index=False))

Colonne chiave:

  • mean_test_score — il punteggio CV medio su tutti i fold per quella combinazione.
  • std_test_score — deviazione standard; un valore elevato indica che il punteggio è instabile tra i fold.
  • rank_test_score — il rango 1 è il vincitore.

Opzioni di scoring

Per impostazione predefinita GridSearchCV ottimizza la metrica predefinita dell'estimator. Puoi specificare qualsiasi scorer integrato o uno personalizzato:

# Common scoring strings
scoring_options = [
    'accuracy',       # classification
    'f1_weighted',    # F1 for multi-class
    'roc_auc',        # binary classification
    'neg_mean_squared_error',  # regression (note: negative so higher = better)
    'r2',             # regression
]

# Evaluate multiple metrics at once (refit on the one you care most about)
grid_search = GridSearchCV(
    estimator=DecisionTreeClassifier(random_state=42),
    param_grid={'max_depth': [2, 3, 5]},
    cv=5,
    scoring={'acc': 'accuracy', 'f1': 'f1_weighted'},
    refit='acc',       # use accuracy to pick the best model
    n_jobs=-1,
)

Usare una Pipeline per prevenire la fuga di dati

Quando il preprocessamento dipende dai dati di addestramento (scaling, imputazione, selezione delle feature), devi addestrare il preprocessore solo sui fold di addestramento — mai sull'intero dataset prima della divisione. Una Pipeline gestisce questo automaticamente e funziona perfettamente con GridSearchCV.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import GridSearchCV

X, y = load_breast_cancer(return_X_y=True)

# Build a pipeline: scale then classify
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('svc', SVC()),
])

# Reference pipeline steps with double-underscore: step__param
param_grid = {
    'svc__C': [0.1, 1, 10],
    'svc__kernel': ['linear', 'rbf'],
    'svc__gamma': ['scale', 'auto'],
}

grid_search = GridSearchCV(pipe, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid_search.fit(X, y)

print("Best params:", grid_search.best_params_)
print("Best CV accuracy: {:.3f}".format(grid_search.best_score_))

La sintassi con il doppio underscore (svc__C) è la chiave: indica a scikit-learn di passare C allo step svc all'interno della pipeline. Senza una pipeline, scalare l'intero dataset prima della cross-validation farebbe trapelare informazioni dal fold di test nello scaler, producendo un punteggio eccessivamente ottimistico.

RandomizedSearchCV: più veloce per griglie grandi

La grid search esaustiva diventa impraticabile quando ciascun iperparametro ha molti valori candidati. RandomizedSearchCV campiona un numero fisso di combinazioni casuali invece di testarle tutte:

from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from scipy.stats import randint

X, y = load_iris(return_X_y=True)

# Use distributions instead of discrete lists
param_dist = {
    'n_estimators': randint(50, 500),   # random integer in [50, 500)
    'max_depth': [3, 5, 10, None],
    'min_samples_split': randint(2, 20),
    'max_features': ['sqrt', 'log2'],
}

rand_search = RandomizedSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_distributions=param_dist,
    n_iter=30,        # try 30 random combinations instead of all
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    random_state=42,
)

rand_search.fit(X, y)

print("Best params:", rand_search.best_params_)
print("Best CV accuracy: {:.3f}".format(rand_search.best_score_))

GridSearchCV vs RandomizedSearchCV:

GridSearchCVRandomizedSearchCV
Strategia di ricercaEsaustiva (tutte le combinazioni)Campionamento casuale
RiproducibilitàCompletamente deterministicaImposta random_state
Ideale perGriglie piccole e ben definiteSpazi di ricerca ampi
Distribuzioni continueNon supportateSupportate tramite scipy.stats
Garantisce il miglior risultatoSì (all'interno della griglia)No, ma spesso vicino

Per griglie grandi, RandomizedSearchCV con n_iter=50–100 trova frequentemente una soluzione quasi ottimale in una frazione del tempo di calcolo.

Fare previsioni con il modello migliore

Dopo l'addestramento, GridSearchCV si comporta come un estimator normale. Il suo attributo best_estimator_ contiene il modello ri-addestrato:

from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

param_grid = {'max_depth': [2, 3, 5], 'criterion': ['gini', 'entropy']}
grid_search = GridSearchCV(
    DecisionTreeClassifier(random_state=42),
    param_grid, cv=5, n_jobs=-1
)
grid_search.fit(X_train, y_train)

# Evaluate on the held-out test set
test_score = grid_search.score(X_test, y_test)
print("Test accuracy: {:.3f}".format(test_score))

# Access the best model directly
best_model = grid_search.best_estimator_
predictions = best_model.predict(X_test[:5])
print("Predictions for first 5 test samples:", predictions)

Importante: mantieni sempre un set di test separato e mai visto durante la grid search. Il punteggio di cross-validation all'interno di GridSearchCV stima la generalizzazione, ma la vera valutazione finale dovrebbe essere effettuata su dati che la ricerca non ha mai toccato.

La grid search è una scelta predefinita solida quando:

  • Hai un modello di piccole o medie dimensioni che si addestra rapidamente (da secondi a pochi minuti per addestramento).
  • Sai all'incirca quali iperparametri contano di più e hai un insieme sensato di valori candidati.
  • La riproducibilità e l'esaustività sono importanti.

Considera alternative quando:

  • La griglia è grande (molti parametri con molti valori) — usa prima RandomizedSearchCV per restringere lo spazio, poi affina con GridSearchCV.
  • L'addestramento è costoso (deep learning, grandi ensemble) — librerie di ottimizzazione bayesiana come scikit-optimize o Optuna fanno scelte più intelligenti del campionamento casuale.
  • Vuoi un arresto automatico — le strategie di dimezzamento (HalvingGridSearchCV) eliminano i candidati con prestazioni basse in anticipo e richiedono meno addestramento complessivi.

Consigli pratici

  • Inizia in modo grossolano. Usa una griglia piccola con valori distribuiti su ordini di grandezza (es. C: [0.01, 0.1, 1, 10, 100]). Una volta trovata una regione promettente, affina con una griglia più fine.
  • Osserva le deviazioni standard. Se std_test_score è elevato, il modello è sensibile alla particolare divisione dei dati. Considera di aumentare cv o raccogliere più dati.
  • Imposta n_jobs=-1 per usare tutti i core CPU — non costa nulla e spesso offre un'accelerazione da 4 a 8 volte su una macchina moderna.
  • Abbina a una Pipeline. Racchiudi sempre il preprocessamento e il modello insieme in una Pipeline prima di passarla a GridSearchCV. Questa è la pratica più importante per ottenere punteggi affidabili.
  • Usa fold stratificati per la classificazione. GridSearchCV usa StratifiedKFold automaticamente per i classificatori, preservando le proporzioni delle classi tra i fold.

Argomenti correlati

Was this page helpful?