O que é clustering não supervisionado
Clustering é uma técnica de aprendizado não supervisionado. O algoritmo não utiliza rótulos ou respostas predefinidas. Seu objetivo consiste em agrupar dados similares em clusters. Cada cluster contém elementos parecidos entre si. Por exemplo, podemos agrupar clientes por comportamento de compra. Diferentemente da classificação, aqui não há treinamento com exemplos rotulados. O algoritmo descobre padrões escondidos nos dados. Essa abordagem mostra-se muito útil para explorar conjuntos desconhecidos. Primeiramente, biologia, marketing e visão computacional usam amplamente esta técnica.
Primeiramente, o algoritmo define uma medida de similaridade. Em seguida, ele organiza os dados em grupos coesos. Além disso, os clusters devem permanecer bem separados uns dos outros. Por conseguinte, pontos dentro do mesmo cluster são próximos. Contudo, pontos de clusters diferentes são distantes segundo a métrica. Uma vantagem consiste em não exigir conhecimento prévio. No entanto, a interpretação dos clusters fica a cargo do analista. Muitas vezes, é preciso testar diferentes configurações. Portanto, o resultado final pode revelar insights surpreendentes.
Arquitetura do modelo k-means
O algoritmo k-means destaca-se como o mais popular para clustering. Sua arquitetura mostra-se simples e baseada em centróides. Primeiramente, escolhe-se o número K de clusters desejado. Depois, inicializamos K centróides aleatoriamente. Cada ponto atribui-se ao centróide mais próximo. Essa atribuição usa a distância euclidiana como métrica. A fórmula da distância entre dois pontos é: \(d(p,q) = \sqrt{\sum_{i=1}^{n} (p_i – q_i)^2}\). Após a atribuição, recalculamos os centróides. O novo centróide corresponde à média dos pontos do cluster: \(c_k = \frac{1}{|C_k|} \sum_{x \in C_k} x\). Consequentemente, repetimos esse processo até a convergência.
A convergência ocorre quando os centróides não mudam mais. O algoritmo converge garantidamente, mas para um mínimo local. Por isso, diferentes inicializações podem produzir resultados distintos. Uma técnica comum envolve o k-means++ para melhor inicialização. Assim, ela espalha os centróides iniciais de forma inteligente. A complexidade do algoritmo mostra-se linear no número de pontos. Isso o torna escalável para grandes conjuntos de dados. Contudo, ele assume que os clusters são esféricos. Portanto, para formas complexas, outros algoritmos são mais adequados.
Hiperparâmetros e fórmulas matemáticas
O principal hiperparâmetro do k-means é o número K. Escolhemos este valor antes da execução do algoritmo. Um método comum envolve o método do cotovelo (elbow method). Ele plota a inércia para diferentes valores de K. A inércia representa a soma das distâncias quadradas intra-cluster: \(inércia = \sum_{k=1}^{K} \sum_{x \in C_k} ||x – c_k||^2\). Quando K aumenta, a inércia sempre diminui. Assim, o ponto de cotovelo indica um bom número de clusters. Outro hiperparâmetro é o número máximo de iterações. Além disso, ajustamos a tolerância de convergência.
A inicialização dos centróides representa outro fator importante. O parâmetro n_init define quantas execuções diferentes realizamos. Portanto, mantemos a melhor execução (menor inércia) ao final. A fórmula da silhueta avalia a qualidade do clustering: \(s(i) = \frac{b(i) – a(i)}{\max(a(i), b(i))}\). Nessa expressão, \(a(i)\) representa a distância média intra-cluster. \(b(i)\) indica a distância média para o cluster vizinho mais próximo. O valor da silhueta varia entre -1 e 1. Consequentemente, valores próximos de 1 indicam clusters bem separados. Essa métrica mostra-se muito útil para validar o resultado.
Enunciado do exemplo com flor íris
Você recebeu o dataset das flores Íris sem os rótulos das espécies. Seu objetivo consiste em agrupar as flores em clusters similares. Use o algoritmo k-means para encontrar grupos naturais. As características disponíveis incluem quatro medidas das flores. Primeiramente, você não sabe quantas espécies existem nos dados. Portanto, determine o número ideal de clusters usando o método do cotovelo. Depois, visualize os clusters em um gráfico 2D. Use as duas primeiras componentes principais para reduzir dimensão. Além disso, exiba um gráfico de silhueta para avaliar a qualidade. O código abaixo resolve completamente este enunciado.
Ele executa no Google Colab sem necessidade de ajustes. Boa prática! O clustering revela estruturas escondidas.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.metrics import silhouette_score, silhouette_samples, adjusted_rand_score import seaborn as sns # Carrega o dataset Iris (sem usar os rótulos para o clustering) iris = load_iris() X = iris.data # apenas características, sem rótulos nomes_caracteristicas = iris.feature_names # Normalização dos dados (importante para k-means) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Método do cotovelo para encontrar K ideal inercias = [] K_range = range(1, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X_scaled) inercias.append(kmeans.inertia_) # Gráfico do cotovelo plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.plot(K_range, inercias, 'bo-', markersize=8, linewidth=2) plt.xlabel('Número de clusters (K)') plt.ylabel('Inércia') plt.title('Método do Cotovelo') plt.grid(True, alpha=0.3) plt.xticks(K_range) # Escolhe K=3 (sabemos que há 3 espécies, mas o método também sugere isso) k_ideal = 3 kmeans = KMeans(n_clusters=k_ideal, random_state=42, n_init=10) clusters = kmeans.fit_predict(X_scaled) # Redução para 2D com PCA para visualização pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) centroides_pca = pca.transform(kmeans.cluster_centers_) # Gráfico dos clusters plt.subplot(1, 2, 2) cores = ['#1f77b4', '#ff7f0e', '#2ca02c'] for i in range(k_ideal): mask = clusters == i plt.scatter(X_pca[mask, 0], X_pca[mask, 1], c=cores[i], label=f'Cluster {i}', alpha=0.7, edgecolors='black', s=50) plt.scatter(centroides_pca[:, 0], centroides_pca[:, 1], c='red', marker='X', s=250, label='Centróides', edgecolors='darkred', linewidth=2) plt.xlabel('Primeira Componente Principal') plt.ylabel('Segunda Componente Principal') plt.title(f'Clusters encontrados (K={k_ideal})') plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Gráfico de silhueta silhouette_vals = silhouette_samples(X_scaled, clusters) silhouette_avg = silhouette_score(X_scaled, clusters) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # Gráfico de silhueta individual y_lower = 10 for i in range(k_ideal): ith_cluster_silhouette = silhouette_vals[clusters == i] ith_cluster_silhouette.sort() size_cluster = ith_cluster_silhouette.shape[0] y_upper = y_lower + size_cluster cor = cores[i] ax1.fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_silhouette, facecolor=cor, alpha=0.7, edgecolor='black') ax1.text(-0.05, y_lower + size_cluster/2, str(i), fontsize=12) y_lower = y_upper + 5 ax1.axvline(x=silhouette_avg, color='red', linestyle='--', label=f'Média = {silhouette_avg:.3f}') ax1.set_xlabel('Coeficiente de silhueta') ax1.set_ylabel('Cluster') ax1.set_title(f'Gráfico de silhueta (K={k_ideal})') ax1.legend() ax1.grid(True, alpha=0.3) # Gráfico de barras da silhueta média por cluster silhouette_por_cluster = [] for i in range(k_ideal): sil_medio = silhouette_vals[clusters == i].mean() silhouette_por_cluster.append(sil_medio) barras = ax2.bar(range(k_ideal), silhouette_por_cluster, color=cores, edgecolor='black') ax2.axhline(y=silhouette_avg, color='red', linestyle='--', label=f'Média global = {silhouette_avg:.3f}') ax2.set_xlabel('Cluster') ax2.set_ylabel('Silhueta média') ax2.set_title('Silhueta média por cluster') ax2.set_xticks(range(k_ideal)) ax2.legend() ax2.grid(axis='y', alpha=0.3) for bar, val in zip(barras, silhouette_por_cluster): ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, f'{val:.3f}', ha='center', va='bottom', fontsize=10) plt.tight_layout() plt.show() # Comparação com os rótulos verdadeiros (apenas para referência) print("=" * 55) print("Análise dos clusters encontrados:") print("=" * 55) print(f"Silhueta média global: {silhouette_avg:.3f}") print("\nDistribuição dos clusters:") for i in range(k_ideal): print(f"Cluster {i}: {np.sum(clusters == i)} flores") # Tabela cruzada com as espécies reais (apenas para validação) ari = adjusted_rand_score(iris.target, clusters) print(f"\nÍndice de Rand Ajustado (vs espécies reais): {ari:.3f}") print("(Valor próximo de 1 indica boa correspondência)") # Mostra a composição de cada cluster em relação às espécies reais print("\n" + "=" * 55) print("Composição de cada cluster (espécies reais):") print("=" * 55) especies = iris.target_names for i in range(k_ideal): mascara = clusters == i rotulos_reais = iris.target[mascara] print(f"\nCluster {i}:") for j, especie in enumerate(especies): count = np.sum(rotulos_reais == j) print(f" - {especie}: {count} flores ({count/np.sum(mascara)*100:.1f}%)") |
O código corrigido separa corretamente as coordenadas X e Y.
A função pca.transform() projeta os centróides no mesmo espaço.
Desenhamos os centróides como X vermelhos sobre os clusters.
O método do cotovelo confirma que K=3 representa o número ideal.
O gráfico de silhueta mostra boa separação entre os grupos.
A silhueta média global próxima de 0,5 indica qualidade razoável.
Por fim, o índice de Rand ajustado compara com as espécies reais.
Valores acima de 0,7 mostram que o clustering funcionou bem.
Além disso, exibimos a composição de cada cluster no final do código.
Parabéns! Você acabou de realizar aprendizado não supervisionado.