DEV Community

Gisely Alves Brandão
Gisely Alves Brandão

Posted on

Aprendizado não supervisionado com K-means

Este artigo trata sobre aprendizado não supervisionado, o método de aprendizado de máquina sem precisar de dados pré-rotulados. No aprendizado não supervisionado existe uma técnica chamada Clustering (ou Aglomeração) que serve exatamente para agrupar dados que tenham algumas características parecidas. Muitas vezes essas técnicas são usadas justamente para rotular dados para que seja possível utilizá-los em um aprendizado supervisionado. O agrupamento dos dados podem ser calculados de muitos modos. Aqui explico um deles, o algoritmo de K-means.

K-means

Este é um dos mais simples e mais rápidos métodos de Clustering. Ele tenta separar os dados em K (um número predefinido) clusters, de acordo com a distância de cada ponto até algo chamado de centroide. Um centroide é como um protótipo para um cluster. Na maioria das técnicas, K pontos aleatórios do dataset são escolhidos para serem as coordenadas dos centroides iniciais. Então cada instância desses dados é atribuída ao centroide mais próximo. Nas demais iterações a posição dos centroides é calculada através da distancia média entre todos os pontos atribuídos àquele centroide na última iteração. O algoritmo termina quando a posição dos centroides não é mais modificada ou a distancia dessa mudança é menor que um threshold predefinido.

Mudança de centroides

Criar clusters com K-means em Python é muito simples também. Vejamos o código:

import numpy as np
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=5)
kmeans.fit(data)
Enter fullscreen mode Exit fullscreen mode

Mas, como escolher o número correto de clusters? A resposta para essa pergunta é bastante subjetiva, entretanto, existem muitos métodos para calcular o melhor número e dois deles explico abaixo, Elbow e Average Silhouette métodos.

Elbow

Neste método é preciso rodar seu algoritmo de clustering com alguns valores, por exemplo, de 1 a 10. Calcular a função de custo, a soma dos quadrados das distâncias internas dos clusters, e traçá-la em um gráfico. O melhor número para a quantidade de clusters é quando a adição de um novo cluster não muda significativamente a função de custo. Isso geralmente acontece no "cotovelo" da linha.

Exemplo de um gráfico com a soma dos quadrados das distancias

Average Silhouette

A análise por Silhouette mede o quão bem um ponto se encaixa em um cluster. Neste método um gráfico é feito medindo quão perto os pontos de um cluster estão dos pontos de outro cluster mais próximo. O coeficiente de Silhouette quando próximo de +1, indica que os pontos estão muito longe dos pontos do outro cluster, e quando próximo de 0, indica que os pontos então muito perto ou até interseccionando um outro cluster.

Para calcular o coeficiente de Silhouette é preciso definir a distância média de um ponto para todos os outros pontos em seu cluster (a(i)) e também definir a distância média até os pontos do cluster mais próximo (b(i)). Então, o coeficiente de Silhouette é calculado assim:

s(i) = (b(i) - a(i)) / max(b(i), a(i))

Para visualizar a análise feita com Silhouette, vamos olhar para o cluster inteiro. Para isso podemos calcular o score médio do Silhouette pegando a média de todos os exemplos no dataset.

Exemplo de análise com Silhouette

Agora, vamos ver o K-means e os métodos para avaliar o número de clusters utilizados em um dataset real. Os dados usado aqui são sobre o consumo em e-commerces de clientes brasileiros. Abaixo está o código para normalizar os dados e criar os gráficos:

mms = MinMaxScaler()
mms.fit(costumers_dataset)
data_transformed = mms.transform(costumers_dataset)
def plot_clustering(data, labels, title=None):
    x_min, x_max = np.min(data, axis=0), np.max(data, axis=0)
    data = (data - x_min) / (x_max - x_min)
    fig = plt.figure(1, figsize=(4, 3))
    plt.figure(figsize=(6, 4))
    plt.scatter(data[:, 0], data[:, 1],
                 c=labels.astype(np.float))
    plt.xticks([])
    plt.yticks([])
    if title is not None:
        plt.title(title, size=17)
    plt.axis('off')
    plt.tight_layout(rect=[0, 0.03, 1, 0.95]) 
Enter fullscreen mode Exit fullscreen mode

Agora, vamos tentar usar o K-means com 5 clusters:

kmeans = KMeans(n_clusters=5)
kmeans.fit(data_transformed)
labels = kmeans.labels_
plot_clustering(data_transformed, labels)
plt.show()
Enter fullscreen mode Exit fullscreen mode

Então, essa separação parece um pouco estranha, não? Temos 5 clusters mas temos muita ocorrência de intersecções. Vamos avaliar o dataset com o método Elbow:

Sum_of_squared_distances = []
K = range(1,15)
for k in K:
    km = KMeans(n_clusters=k)
    km = km.fit(data_transformed)
    Sum_of_squared_distances.append(km.inertia_)
plt.plot(K, Sum_of_squared_distances, 'bx-')
plt.xlabel('k')
plt.ylabel('Sum_of_squared_distances')
plt.title('Elbow Method For Optimal k')
plt.show()
Enter fullscreen mode Exit fullscreen mode

O gráfico mostra que esse dataset não possui exatamente um "cotovelo". Talvez o número correto de clusters esteja entre 4 e 6. Mas talvez este dataset que construímos não possa ser separado com essa configuração… Vamos ver o coeficiente de Silhouette sobre alguns valores para a quantidade de clusters.

range_n_clusters = [2, 3, 4, 5, 6]
for n_clusters in range_n_clusters:
    # Create a subplot with 1 row and 2 columns
    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.set_size_inches(18, 7)
    # The 1st subplot is the silhouette plot
    # The silhouette coefficient can range from -1, 1 but in this example all
    # lie within [-0.1, 1]
    ax1.set_xlim([-0.1, 1])
    # The (n_clusters+1)*10 is for inserting blank space between silhouette
    # plots of individual clusters, to demarcate them clearly.
    ax1.set_ylim([0, len(data_transformed) + (n_clusters + 1) * 10])
    # Initialize the clusterer with n_clusters value and a random generator
    # seed of 10 for reproducibility.
    clusterer = KMeans(n_clusters=n_clusters, random_state=10)
    cluster_labels = clusterer.fit_predict(data_transformed)
# The silhouette_score gives the average value for all the samples.
    # This gives a perspective into the density and separation of the formed
    # clusters
    silhouette_avg = silhouette_score(data_transformed, cluster_labels)
    print("Para n_clusters =", n_clusters,
          "O score_silhouette médio é :", silhouette_avg)
    # Compute the silhouette scores for each sample
    sample_silhouette_values = silhouette_samples(data_transformed, cluster_labels)
    y_lower = 10
    for i in range(n_clusters):
        # Aggregate the silhouette scores for samples belonging to
        # cluster i, and sort them
        ith_cluster_silhouette_values = \
            sample_silhouette_values[cluster_labels == i]
        ith_cluster_silhouette_values.sort()
        size_cluster_i = ith_cluster_silhouette_values.shape[0]
        y_upper = y_lower + size_cluster_i
        color = cm.nipy_spectral(float(i) / n_clusters)
        ax1.fill_betweenx(np.arange(y_lower, y_upper),
                          0, ith_cluster_silhouette_values,
                          facecolor=color, edgecolor=color,       alpha=0.7)
        # Label the silhouette plots with their cluster numbers at the middle
        ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
        # Compute the new y_lower for next plot
        y_lower = y_upper + 10  # 10 for the 0 samples
    ax1.set_title("The silhouette plot for the various clusters.")
    ax1.set_xlabel("The silhouette coefficient values")
    ax1.set_ylabel("Cluster label")
    # The vertical line for average silhouette score of all the values
    ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
    ax1.set_yticks([])  # Clear the yaxis labels / ticks
    ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
    # 2nd Plot showing the actual clusters formed
    colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
    ax2.scatter(data_transformed[:, 0], data_transformed[:, 1], marker='.', s=30, lw=0, alpha=0.7,
                c=colors, edgecolor='k')
    # Labeling the clusters
    centers = clusterer.cluster_centers_
    # Draw white circles at cluster centers
    ax2.scatter(centers[:, 0], centers[:, 1], marker='o',
                c="white", alpha=1, s=200, edgecolor='k')
    for i, c in enumerate(centers):
        ax2.scatter(c[0], c[1], marker='$%d$' % i, alpha=1,
                    s=50, edgecolor='k')
    ax2.set_title("The visualization of the clustered data.")
    ax2.set_xlabel("Feature space for the 1st feature")
    ax2.set_ylabel("Feature space for the 2nd feature")
    plt.suptitle(("Silhouette analysis for KMeans clustering on sample data "
                  "with n_clusters = %d" % n_clusters),
                 fontsize=14, fontweight='bold')
plt.show()
Enter fullscreen mode Exit fullscreen mode

Abaixo temos a resposta desse algoritmo. Os gráficos estão separados por quantidade de clusters utilizados. Os gráficos da esquerda nos mostram o score da análise com Silhouette e da direita uma visualização dos clusters.

Para n_clusters = 2 O score_silhouette médio é: 0.24604273339845253

Para n_clusters = 3 O score_silhouette médio é : 0.20670607133321856

Para n_clusters = 4 O score_silhouette médio é : 0.188444914764597

Para n_clusters = 5 O score_silhouette médio é : 0.19090629903451375

Para n_clusters = 6 O score_silhouette médio é : 0.18047082769472864

Como podem ver, os coeficientes de Silhouette não nos mostraram números muito bons sobre nenhum dos cenários apresentados. O melhor que podemos fazer aqui é remontar o dataset com outras informações. Talvez remover algumas das informações pode melhorar os resultados.


Conclusão

O aprendizado não supervisionado usando análise com Cluster pode ser muito fácil de produzir e ao mesmo tempo muito útil e importante para muitas áreas do conhecimento, por exemplo, quais tipos de cliente uma empresa tem, para ter conhecimento sobre qual o jeito certo para fazer propagandas ou para criar produtos melhores para cada tipo de cliente, além disso, outro exemplo é entender sobre algumas espécies na biologia, o pesquisador pode agrupar animais, células, ou qualquer outra coisa de acordo com suas características.

É importante lembrar que K-means não é a única técnica de Clustering. Existem outras métodos muito úteis como o Hierárquico, que também é muito simples de aprender, ou Expectation-maximization, que é muito utilizado pelos Cientístas de Dados.


Referências

O os códigos completos usados para as experiências nesse artigo estão nesse repositório do Github.

Methods to determining the optimal number of clusters

How to determine the optimal number of clusters for K-means

Plot silhouette analysis

Silhouette Analysis

Dataset usado no artigo

Top comments (0)