K-means
Impara il clustering K-Means in Python con scikit-learn. Algoritmo, scaling, scelta di K, silhouette score e insidie comuni.
K-Means è un algoritmo di machine learning non supervisionato che partiziona un dataset in K cluster non sovrapposti minimizzando la distanza quadratica totale tra ciascun punto dati e il centroide del cluster a cui è assegnato. Questa pagina spiega come funziona l'algoritmo, come implementarlo in Python con scikit-learn, come scegliere il numero corretto di cluster e le principali insidie da evitare.
Cos'è il Clustering K-Means?
K-Means raggruppa i punti dati in modo che i punti all'interno dello stesso cluster siano il più simili possibile, mentre i punti in cluster diversi siano il più diversi possibile. La "similarità" è misurata dalla distanza euclidea — la distanza in linea retta tra due punti nello spazio delle feature.
Poiché K-Means lavora con valori di distanza grezzi, è un algoritmo non supervisionato: scopre strutture in dati non etichettati senza aver bisogno di una variabile target.
Le applicazioni reali più comuni includono:
- Segmentazione dei clienti — raggruppa i clienti per abitudini di spesa e dati demografici.
- Compressione delle immagini — riduce i colori sostituendo ogni pixel con il colore del centroide più vicino.
- Clustering di documenti — raggruppa articoli di notizie o ticket di supporto per argomento.
- Rilevamento delle anomalie — i punti lontani da ogni centroide potrebbero essere outlier.
Come Funziona l'Algoritmo
K-Means alterna tra due passaggi finché le assegnazioni di cluster non smettono di cambiare (convergenza):
- Inizializzazione — scegli K centroidi iniziali (punti di partenza). La strategia predefinita
k-means++li distanzia per ridurre le inizializzazioni problematiche. - Assegnazione — assegna ogni punto dati al centroide più vicino, formando K cluster.
- Aggiornamento — ricalcola ogni centroide come media di tutti i punti assegnati ad esso.
- Ripeti i passaggi 2–3 finché nessun punto cambia cluster, o finché non viene raggiunto il numero massimo di iterazioni.
La quantità che viene minimizzata si chiama inertia (nota anche come WCSS — within-cluster sum of squares):
inertia = sum over all points of (distance from point to its centroid)²Un'inertia più bassa significa cluster più compatti e coesi.
Inizializzazione k-means++
Il valore predefinito init='k-means++' (predefinito in scikit-learn) seleziona il primo centroide casualmente, poi sceglie ciascun centroide successivo con probabilità proporzionale alla distanza quadratica dal centroide già scelto più vicino. Questo distribuisce i centroidi iniziali e generalmente trova cluster migliori più velocemente rispetto all'inizializzazione puramente casuale.
Scaling delle Feature Prima del Clustering
K-Means si basa interamente sulla distanza euclidea. Se una feature è misurata in migliaia (ad esempio, il reddito annuale) e un'altra in valori a singola cifra (ad esempio, una valutazione da 1 a 5), la feature con valori grandi domina il calcolo della distanza e quella con valori piccoli viene ignorata. Applica sempre prima lo scaling delle feature.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)StandardScaler centra ogni feature alla media 0 e deviazione standard 1. Consulta il capitolo sullo Scaling per alternative come MinMaxScaler.
Implementare K-Means con scikit-learn
L'esempio seguente genera un dataset sintetico, lo scala, adatta K-Means e ispeziona i risultati.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import numpy as np
# Generate 300 points in 3 natural clusters
X, y_true = make_blobs(n_samples=300, centers=3, random_state=42)
# Scale the features — always required before K-Means
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Fit K-Means with 3 clusters
kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42)
kmeans.fit(X_scaled)
# Cluster assignment for every training point (0-indexed)
labels = kmeans.labels_
print('Cluster labels (first 10):', labels[:10])
# e.g. [2 2 0 1 0 1 2 2 0 1]
print('Cluster sizes:', np.bincount(labels))
# e.g. [100 100 100]
print('Inertia (WCSS):', round(kmeans.inertia_, 2))
# e.g. 18.26
print('Iterations to converge:', kmeans.n_iter_)
# e.g. 2Attributi chiave dopo l'adattamento:
| Attributo | Descrizione |
|---|---|
labels_ | ID del cluster (da 0 a K-1) per ogni punto di addestramento |
cluster_centers_ | Coordinate dei K centroidi (forma: K × n_features) |
inertia_ | WCSS totale — più basso è meglio |
n_iter_ | Numero di iterazioni EM fino alla convergenza |
Previsione dell'appartenenza al cluster per nuovi dati
Dopo aver adattato lo scaler e il modello sui dati di addestramento, usa scaler.transform() + kmeans.predict() per assegnare nuovi punti ai cluster esistenti. Non riadattare mai lo scaler su nuovi dati.
import numpy as np
# Two new, unseen points (original feature scale)
new_points = np.array([[0.5, -0.5],
[-1.0, 2.0]])
# Transform with the SAME scaler used during training
new_scaled = scaler.transform(new_points)
# Predict cluster membership
predictions = kmeans.predict(new_scaled)
print('Predicted clusters:', predictions)
# e.g. [2 0]Scegliere il Numero Corretto di Cluster (K)
K-Means richiede di specificare K in anticipo, il che è spesso la parte più difficile. Due tecniche complementari aiutano: il metodo del gomito e il silhouette score.
Il metodo del gomito
Traccia l'inertia (WCSS) in funzione di K. All'aumentare di K, l'inertia diminuisce sempre — ma il tasso di miglioramento rallenta. Il "gomito" è il punto in cui aggiungere un altro cluster produce rendimenti decrescenti.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
wcss = []
for k in range(1, 11):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
km.fit(X_scaled)
wcss.append(km.inertia_)
plt.plot(range(1, 11), wcss, marker='o')
plt.xlabel('Number of clusters (K)')
plt.ylabel('Inertia (WCSS)')
plt.title('Elbow method')
plt.tight_layout()
plt.show()Su questo dataset (3 cluster naturali), l'inertia scende bruscamente da K=1 a K=3, poi si stabilizza. Il gomito a K=3 conferma il numero reale di cluster.
Il silhouette score
Il silhouette score misura quanto bene ogni punto si adatta al proprio cluster rispetto al cluster vicino più prossimo. Va da -1 (cluster errato) a +1 (cluster perfettamente separato). Un punteggio superiore a 0.5 è generalmente buono; superiore a 0.7 è eccellente.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
for k in range(2, 8):
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
print(f'k={k} silhouette={score:.3f}')Output:
k=2 silhouette=0.688
k=3 silhouette=0.848
k=4 silhouette=0.679
k=5 silhouette=0.522
k=6 silhouette=0.357
k=7 silhouette=0.371K=3 produce il silhouette score più alto (0.848), confermando che tre cluster descrivono meglio questi dati. Usa entrambi i metodi insieme — il grafico del gomito indica dove il miglioramento si arresta; il silhouette score fornisce un singolo numero per confrontare i vari valori di K.
Visualizzare i Cluster K-Means
I diagrammi a dispersione rivelano se i cluster sono ben separati. Per dataset con più di due feature, applica prima una riduzione della dimensionalità (come la PCA) prima di tracciare il grafico.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
kmeans = KMeans(n_clusters=3, n_init=10, random_state=42)
labels = kmeans.fit_predict(X_scaled)
centers = kmeans.cluster_centers_
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=labels, cmap='viridis', s=40, alpha=0.7)
plt.scatter(centers[:, 0], centers[:, 1],
c='red', marker='X', s=200, zorder=5, label='Centroids')
plt.legend()
plt.title('K-Means clustering (k=3)')
plt.xlabel('Feature 1 (scaled)')
plt.ylabel('Feature 2 (scaled)')
plt.tight_layout()
plt.show()Ogni colore rappresenta un cluster. Le croci rosse segnano i centroidi — la posizione media di tutti i punti in quel cluster.
Vantaggi e Limitazioni
Vantaggi
- Semplice e veloce. K-Means è O(n · k · i) dove n è il numero di punti, k è il numero di cluster e i è il numero di iterazioni. Si scala a milioni di punti dati con
MiniBatchKMeans. - Facile da interpretare. I centroidi forniscono un riepilogo concreto dei valori tipici di ogni cluster.
- Funziona bene quando i cluster sono approssimativamente sferici e di dimensioni uguali.
- Non richiede dati etichettati. Completamente non supervisionato — non è necessaria alcuna variabile target.
Limitazioni
- K deve essere specificato in anticipo. Usa il metodo del gomito e il silhouette score per guidare la scelta, ma nessuno dei due fornisce una risposta definitiva per dati ambigui.
- Sensibile all'inizializzazione. Una configurazione di partenza errata può convergere a un ottimo locale.
k-means++e più riavvii (n_init=10) riducono questo rischio. - Assume cluster sferici e di dimensioni uguali. K-Means ha difficoltà con cluster allungati, a forma di mezzaluna o molto diseguali. Usa il Clustering Gerarchico o DBSCAN per forme non globulari.
- Sensibile agli outlier. Un singolo punto estremo può trascinare un centroide lontano dal vero centro del cluster. Rimuovi o limita gli outlier prima dell'adattamento.
- Sensibile alla scala. Le feature su scale diverse devono essere standardizzate — consulta il capitolo sullo Scaling.
Insidie Comuni
Saltare lo scaling delle feature. È l'errore più comune in assoluto. Senza lo scaling, le feature con grandi intervalli numerici dominano il calcolo della distanza e quelle con intervalli più piccoli vengono ignorate.
Impostare n_init=1. Il valore predefinito nelle versioni precedenti di scikit-learn era n_init='warn' (che avvisava se non lo si impostava). Imposta sempre n_init=10 (o più) per eseguire K-Means con 10 diverse inizializzazioni casuali e mantenere il risultato migliore.
Riadattare lo scaler su nuovi dati. Adatta StandardScaler una sola volta sui dati di addestramento, poi chiama transform() su qualsiasi nuovo dato. Chiamare di nuovo fit_transform() su nuovi dati cambia la scala e rende il modello incoerente.
Trattare gli ID dei cluster come ordine significativo. Gli ID dei cluster (0, 1, 2, …) sono etichette arbitrarie assegnate da scikit-learn. Non sono ordinali — il cluster 2 non è "più grande" o "più importante" del cluster 0. Confronta i cluster in base ai valori dei centroidi e al numero di elementi.
Usare K-Means su dati non numerici. K-Means richiede feature numeriche per calcolare le distanze. Per i dati categorici, codificali prima (ad esempio con la codifica one-hot) e valuta se la distanza euclidea abbia ancora senso per il tuo caso d'uso. Consulta il capitolo sui Dati Categorici.
K-Means vs Clustering Gerarchico
| Caratteristica | K-Means | Clustering Gerarchico |
|---|---|---|
| Numero di cluster | Deve essere specificato K prima dell'esecuzione | Può essere scelto dopo l'esecuzione (ispeziona il dendrogramma) |
| Scalabilità | Si scala a milioni di righe | O(n²) di memoria — impraticabile oltre ~10 000 righe |
| Determinismo | Casuale (usa random_state per la riproducibilità) | Completamente deterministico |
| Forma dei cluster | Ottimale per cluster sferici | Flessibile con diversi metodi di linkage |
| Output | Assegnazioni flat di cluster | Albero (dendrogramma) che mostra la storia delle fusioni |
Usa K-Means quando il dataset è grande e hai già una buona stima di K. Usa il Clustering Gerarchico quando vuoi esplorare più valori di K senza riadattare il modello, o quando i cluster potrebbero non essere sferici.
Checklist Pratica
Segui questa checklist quando applichi K-Means a un nuovo dataset:
- Rimuovi o limita gli outlier — i valori estremi distorcono i centroidi.
- Codifica le variabili categoriche — K-Means richiede input numerici.
- Scala le feature con
StandardScaler(oMinMaxScalerse la distribuzione della feature è limitata). - Usa il grafico del gomito per restringere i valori candidati di K.
- Usa il silhouette score per confrontare quantitativamente valori specifici di K.
- Imposta
n_init=10einit='k-means++'per un'inizializzazione robusta. - Ispeziona le dimensioni dei cluster con
np.bincount(labels)— dimensioni molto diseguali possono indicare un K inadeguato o contaminazione da outlier. - Visualizza con un diagramma a dispersione (o proiezione PCA per dati ad alta dimensionalità).
Capitoli Correlati
- Scaling — standardizzare le feature prima del clustering
- Clustering Gerarchico — un'alternativa che non richiede K in anticipo
- K-Nearest Neighbors — un metodo supervisionato che utilizza anch'esso le distanze
- Decision Tree — classificazione supervisionata senza assunzioni sulle distanze
- Train / Test Split — valutare i modelli di machine learning
- Scatter Plot — visualizzare i cluster con Matplotlib