Machine Learning Academy · Lezione

Scegliere k: metodo del gomito e curve di validazione

Proverà i valori di k da 1 a 30, traccerà l'accuratezza di validazione e individuerà il punto ottimale che bilancia bias e varianza.

Lezione 2 di 413 passaggi

Scegliere k: metodo del gomito e curve di validazione è una lezione Machine Learning Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Machine Learning Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Machine Learning Academy include 4 lezioni in totale.

Perché la scelta di k è importante

Il valore di k è l'iperparametro più importante in KNN. Un valore di k troppo piccolo (ad esempio, k=1) rende il modello estremamente sensibile al rumore: ogni punto di addestramento forma la propria regione di predizione, facendo sì che il modello memorizzi il rumore invece di apprendere i pattern. Un valore di k troppo grande rende eccessivamente uniforme il confine decisionale e può fondere classi realmente distinte. Trovare il valore corretto di k è un problema di compromesso bias-varianza: k piccolo = bias basso, varianza alta; k grande = bias alto, varianza bassa. Il metodo del gomito e le curve di validazione aiutano a identificare empiricamente il valore ottimale di k.

# k=1: memorises training set perfectly
# Training accuracy = 100%, test accuracy low (overfitting)

# k=N (all neighbors): always predicts majority class
# Training accuracy = majority fraction (underfitting)

# Optimal k: somewhere in between
# Maximises test/validation accuracy

from sklearn.neighbors import KNeighborsClassifier
print('k=1  overfits (memorises noise)')
print('k=N  underfits (ignores all variation)')
print('Best k: maximises cross-validated accuracy')

Esplorazione dei valori di k: il ciclo di base

L'approccio più semplice consiste nell'addestrare KNN per un intervallo di valori di k, valutare ogni modello su un set di validazione e scegliere il valore di k con l'accuratezza di validazione più elevata. Scikit-learn rende questa procedura immediata: esegua un ciclo sui valori di k da 1 fino a un massimo stabilito, addestrando e valutando ogni modello. Valuti sempre il modello su un set di validazione separato o utilizzi la convalida incrociata — valutare il modello sul set di addestramento selezionerebbe sempre k=1 (poiché con k=1 KNN predice i punti di addestramento con un'accuratezza del 100%, memorizzandoli).

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris

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

scaler = StandardScaler()
X_tr = scaler.fit_transform(X_train)
X_v  = scaler.transform(X_val)

val_scores = []
for k in range(1, 31):
    knn = KNeighborsClassifier(n_neighbors=k)
    knn.fit(X_tr, y_train)
    val_scores.append(knn.score(X_v, y_val))

best_k = val_scores.index(max(val_scores)) + 1
print('Best k:', best_k, 'with accuracy:', max(val_scores).round(3))

Tracciare la curva di validazione

Visualizzare l'accuratezza di validazione in funzione di k rivela due andamenti importanti. Con valori bassi di k, la curva è irregolare e presenta un'elevata varianza (il modello reagisce ai singoli punti di addestramento). All'aumentare di k, l'accuratezza generalmente migliora fino a raggiungere un picco, per poi diminuire lentamente quando il modello diventa troppo uniforme. Il valore ottimale di k corrisponde al picco della curva di validazione. Questo andamento non è sempre evidente con una singola suddivisione dei dati, perciò la convalida incrociata fornisce una stima più affidabile calcolando la media su più suddivisioni.

import matplotlib.pyplot as plt
import numpy as np

k_values = range(1, 31)
# val_scores computed from previous sweep
plt.figure(figsize=(10, 5))
plt.plot(k_values, val_scores, marker='o', label='Validation accuracy')
plt.axvline(x=best_k, color='r', linestyle='--', label=f'Best k={best_k}')
plt.xlabel('k (Number of Neighbors)')
plt.ylabel('Validation Accuracy')
plt.title('KNN Validation Curve')
plt.legend()
plt.grid(True)
plt.show()

Accuratezza con convalida incrociata per ogni k

Una singola suddivisione di validazione può essere condizionata dai campioni finiti casualmente nel set di validazione. La convalida incrociata calcola la media su k suddivisioni, fornendo una stima più stabile. È possibile chiamare cross_val_score per ogni valore di k. L'accuratezza media mostra l'andamento, mentre la deviazione standard tra i fold indica l'affidabilità. Scelga il valore di k con la maggiore accuratezza media ottenuta tramite convalida incrociata; se più valori di k sono vicini, preferisca quello più grande per ottenere predizioni più uniformi e generalizzabili.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
import numpy as np

X, y = load_iris(return_X_y=True)

cv_means, cv_stds = [], []
for k in range(1, 31):
    pipe = Pipeline([('sc', StandardScaler()),
                     ('knn', KNeighborsClassifier(n_neighbors=k))])
    scores = cross_val_score(pipe, X, y, cv=10)
    cv_means.append(scores.mean())
    cv_stds.append(scores.std())

best_k_cv = np.argmax(cv_means) + 1
print('Best k by CV:', best_k_cv, 'mean accuracy:', max(cv_means).round(3))

Il concetto del metodo del gomito

Il metodo del gomito è una tecnica visiva per individuare il punto ottimale in cui il miglioramento dell'accuratezza diventa marginale. Tracci l'accuratezza (o l'errore) di validazione in funzione di k: in genere la curva mostra un miglioramento marcato per i valori piccoli di k, per poi stabilizzarsi. Il gomito — il punto in cui la curva passa da una crescita ripida a un andamento piatto — è spesso il valore ottimale di k. L'intuizione alla base è quella dei rendimenti decrescenti: aggiungere altri vicini oltre questo punto non migliora significativamente l'accuratezza, ma aumenta il bias. Il gomito non è sempre evidente, perciò per la selezione finale si preferiscono metodi quantitativi come la convalida incrociata.

import matplotlib.pyplot as plt
import numpy as np

k_range = range(1, 31)
error_rates = [1 - acc for acc in cv_means]  # Convert accuracy to error

plt.figure(figsize=(10, 5))
plt.plot(list(k_range), error_rates, marker='o')
plt.xlabel('k')
plt.ylabel('Cross-Validated Error Rate')
plt.title('Elbow Method for Optimal k')
plt.grid(True)

# Mark the elbow visually
plt.axvline(x=best_k_cv, color='r', linestyle='--', label=f'Elbow at k={best_k_cv}')
plt.legend()
plt.show()

Usare validation_curve di scikit-learn

Scikit-learn mette a disposizione validation_curve(), una funzione di utilità che esplora un intervallo di iperparametri e restituisce i punteggi di addestramento e di validazione per ogni valore. È più semplice di un ciclo manuale perché gestisce internamente la convalida incrociata. L'argomento param_name utilizza la notazione con doppio underscore per i parametri delle pipeline (ad esempio, knn__n_neighbors). Tracciare insieme le curve di addestramento e di validazione mostra se le prestazioni ridotte sono dovute a underfitting (entrambe basse), overfitting (addestramento alto, validazione bassa) o a una buona generalizzazione (entrambe alte).

from sklearn.model_selection import validation_curve
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

pipe = Pipeline([('sc', StandardScaler()), ('knn', KNeighborsClassifier())])

train_scores, val_scores = validation_curve(
    pipe, X, y,
    param_name='knn__n_neighbors',
    param_range=range(1, 31),
    cv=10, scoring='accuracy'
)

train_mean = np.mean(train_scores, axis=1)
val_mean   = np.mean(val_scores, axis=1)

print('Best k:', np.argmax(val_mean) + 1)

Compromesso bias-varianza in KNN

La curva di validazione illustra direttamente il compromesso bias-varianza. Per valori piccoli di k: l'accuratezza di addestramento si avvicina al 100% (bias basso, il modello si adatta perfettamente ai dati di addestramento), mentre l'accuratezza di validazione è inferiore (varianza alta, il modello è troppo sensibile ai singoli punti). Per valori grandi di k: l'accuratezza di addestramento diminuisce (il modello presenta underfitting) e diminuisce anche quella di validazione (bias alto). Il valore ottimale di k si trova nel punto di equilibrio in cui il divario tra l'accuratezza di addestramento e quella di validazione è ridotto ed entrambe raggiungono valori elevati: questo è il punto ottimale per la generalizzazione.

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(10, 5))
k_range = list(range(1, 31))

ax.plot(k_range, train_mean, label='Training accuracy', color='blue')
ax.plot(k_range, val_mean, label='Validation accuracy', color='orange')
ax.fill_between(k_range,
    np.mean(train_scores, axis=1) - np.std(train_scores, axis=1),
    np.mean(train_scores, axis=1) + np.std(train_scores, axis=1),
    alpha=0.1, color='blue')

ax.set_xlabel('k')
ax.set_ylabel('Accuracy')
ax.set_title('Bias-Variance Trade-off: KNN Validation Curve')
ax.legend()
plt.show()

Scegliere k in base alla parità o disparità

Per la classificazione binaria, preferisca sempre valori dispari di k per evitare i pareggi. Con k=4 e due classi, si possono ottenere 2 voti per ciascuna classe: in tal caso, il criterio di spareggio determina il risultato, che può essere arbitrario. Usando k=3 o k=5, con due classi i pareggi sono impossibili. Per i problemi multiclasse con C classi, per lo stesso motivo k non dovrebbe essere un multiplo di C. È un dettaglio piccolo ma importante nella pratica quando i valori di k vicini all'ottimo presentano prestazioni simili.

# Best practice for binary classification: pick odd k
# For multi-class (C classes): avoid multiples of C

def recommend_k(k_optimal, n_classes):
    if n_classes == 2:
        # Make odd
        return k_optimal if k_optimal % 2 == 1 else k_optimal + 1
    else:
        # Avoid multiples of n_classes
        while k_optimal % n_classes == 0:
            k_optimal += 1
        return k_optimal

print('Binary, k=4 -> recommended:', recommend_k(4, 2))  # 5
print('3-class, k=6 -> recommended:', recommend_k(6, 3)) # 7

GridSearchCV per la selezione di k

GridSearchCV automatizza la selezione di k valutando ogni valore candidato tramite convalida incrociata e restituendo il migliore. Lo combini con una pipeline di scalatura e passi a param_grid la chiave con doppio underscore. GridSearchCV riaddestra inoltre il modello migliore sull'intero set di addestramento, quindi grid.best_estimator_ è immediatamente pronto per l'uso in produzione dopo l'addestramento. Questo è l'approccio consigliato quando la selezione di k rientra in un'ottimizzazione più ampia degli iperparametri.

from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

pipe = Pipeline([
    ('sc', StandardScaler()),
    ('knn', KNeighborsClassifier())
])

param_grid = {
    'knn__n_neighbors': list(range(1, 31, 2)),  # odd values 1-29
    'knn__weights': ['uniform', 'distance']
}

grid = GridSearchCV(pipe, param_grid, cv=10, scoring='accuracy', n_jobs=-1)
grid.fit(X_train, y_train)

print('Best k:', grid.best_params_['knn__n_neighbors'])
print('Best weights:', grid.best_params_['knn__weights'])
print('Best CV accuracy:', grid.best_score_.round(3))

Interpretare i risultati e scegliere il valore finale di k

Quando più valori di k producono punteggi di validazione simili, preferisca il valore di k più grande per ottenere predizioni più uniformi e robuste, meno sensibili ai singoli punti rumorosi. Esamini la deviazione standard dei punteggi di convalida incrociata: se un valore di k più piccolo presenta una media maggiore ma anche una deviazione standard più alta, quello più grande potrebbe risultare effettivamente più affidabile in produzione. Il modello finale deve essere riaddestrato sull'intero set di addestramento (non solo sui fold di addestramento della convalida incrociata) usando il valore di k selezionato e valutato una sola volta sul set di test separato.

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# Select best k from validation
best_k = 11  # determined from CV

# Retrain on full training data
final_model = Pipeline([
    ('sc', StandardScaler()),
    ('knn', KNeighborsClassifier(n_neighbors=best_k, weights='distance'))
])
final_model.fit(X_train, y_train)

# Evaluate once on held-out test set
test_accuracy = final_model.score(X_test, y_test)
print(f'Final test accuracy with k={best_k}: {test_accuracy:.3f}')

Errori comuni nella scelta di k

Tre errori comuni da evitare: (1) valutare i dati di addestramento — k=1 otterrà sempre un punteggio del 100%, facendo apparire questa scelta come ottimale; utilizzi sempre dati separati o sottoposti a convalida incrociata. (2) Non scalare le feature prima di scegliere k — il valore ottimale di k dipende dalla geometria delle distanze, che cambia con la scalatura; includa sempre lo scaler nella pipeline prima della ricerca degli iperparametri. (3) Scegliere k indipendentemente dalle dimensioni del dataset — una regola empirica consiste nel partire da un valore di k vicino a sqrt(N), dove N è la dimensione del set di addestramento, e poi perfezionarlo tramite convalida incrociata.

import numpy as np

N_train = 1000  # training samples

# Rule of thumb starting point
k_start = int(np.sqrt(N_train))
print(f'sqrt(N) starting point: k = {k_start}')

# Then sweep around this value
k_candidates = list(range(max(1, k_start - 10), k_start + 11, 2))
print('Candidates to sweep:', k_candidates)

# AVOID:
# knn.score(X_train, y_train) -- always pick k=1
# Not including scaler in pipeline before CV

Verifica rapida

Verifichi la Sua comprensione dei concetti di Machine Learning con Python presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato: come un valore piccolo di k causi overfitting e un valore grande di k causi underfitting, come utilizzare la convalida incrociata e validation_curve per esplorare i valori di k e trovare quello ottimale e perché i valori dispari di k evitino i pareggi nella classificazione binaria. Nella prossima lezione esploreremo diverse metriche di distanza — euclidea, Manhattan e Minkowski — e vedremo come scegliere tra loro.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Scegliere k: metodo del gomito e curve di validazione» è gratuita?

Sì — il testo completo di «Scegliere k: metodo del gomito e curve di validazione» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Machine Learning Academy, passa a CoddyKit PRO. Il corso Machine Learning Academy include 4 lezioni in totale.

Cosa imparerò in «Scegliere k: metodo del gomito e curve di validazione»?

Proverà i valori di k da 1 a 30, traccerà l'accuratezza di validazione e individuerà il punto ottimale che bilancia bias e varianza. Eserciti Machine Learning Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Machine Learning Academy?

Non è richiesta alcuna esperienza precedente. Machine Learning Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Scegliere k: metodo del gomito e curve di validazione»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Machine Learning Academy?

Sì. Ogni lezione Machine Learning Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Come funziona KNN: distanza, vicini e voti
  2. Scegliere k: metodo del gomito e curve di validazione
  3. Metriche di distanza: euclidea, Manhattan e Minkowski
  4. KNN per la regressione e i suoi limiti di scalabilità
← Torna a Machine Learning Academy