K-means 3D




Objetivo

Esse projeto é a aplicação de 2 conceitos que aprendi essa semana em cursos diferentes que venho fazendo.

No bootcamp - Analista de dados do IGTI (Instituto de Gestão e Tecnologia da Informação) revisei os conceitos do algorítimo k-means e fiz um exercício muito visual mostrando como ocorre a clusterização dos dados.

No Data Science Career Path do Codecademy, fiz um projeto de visualização 3D da constelação de Orion e o gráfico ficou muito legal.

Achei interessante fazer o exercício de unir esses dois conceitos e ver como ficaria o resultado do k-means em dados tridimensionais. Será que é possível?

Projetos anteriores

Para entender melhor o que quero fazer, segue a visualização final dos dois projetos:

a) Constelação de Orion

In [52]:
'''Pacotes'''
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sqrt

'''Coodenadas da constelação'''
x = [-0.41, 0.57, 0.07, 0.00, -0.29, -0.32,-0.50,-0.23, -0.23]
y = [4.12, 7.71, 2.36, 9.10, 13.35, 8.13, 7.19, 13.25,13.43]
z = [2.06, 0.84, 1.56, 2.07, 2.36, 1.72, 0.66, 1.25,1.38]
In [53]:
'''Plot'''
fig_3d = plt.figure()
fig_3d.add_subplot(1,1,1, projection='3d').scatter(x,y,z)
plt.show()

Observação: No jupyter-notebook dá para rotacionar mas aqui no google colab o gráfico veio como uma figura estática.

b) Algorítimo k-means 2d

In [54]:
'''Pacotes'''
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans

'''Dataset (dados aleatórios)'''
X, y = make_blobs(n_samples=500, centers= 50, random_state=0)

'''Plot dos dados originais'''
plt.close()
plt.scatter(X[:,0] , X[:,1])
plt.show()

Utilizamos a curva curva wcss (Within-Cluster-Sum-of-Squares) para encontrar o número de clusters ideais, chegamos a conclusão de que 5 seria o número ótimo

In [55]:
'''Função Número ideal de clusters (wcss)'''
def calculate_wcss(data):
  wcss = []
  for i in range(1,30):
    kmeans = KMeans(n_clusters=i, init='k-means++', max_iter=300, n_init=10)
    kmeans.fit(data)
    wcss.append(kmeans.inertia_)
  return wcss

'''Cálculo do wcss'''
wcss = calculate_wcss(X)

'''Plot da curva css'''
plt.close()
plt.plot(range(1,30), wcss)
plt.title('Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()
In [56]:
'''Plot dos clusters'''
kmeans = KMeans(n_clusters=5, init='k-means++', max_iter=300, n_init=10, random_state=0)
pred_y = kmeans.fit_predict(X)
plt.scatter(X[:,0], X[:,1], c=pred_y)
plt.scatter(kmeans.cluster_centers_[:,0], kmeans.cluster_centers_[:,1], s=50, c='red')
plt.show()

K-means 3D

In [57]:
'''Dataset (dados aleatórios)'''
X, y = make_blobs(n_samples=800, centers= 100, random_state=0, n_features=3)
# Para mudar para dados 3d bastou adicionar o n_features = 3.
# Par melhorar a distribuição dos dados no espaço 3d aumentei o n_samples para 800 e o n_centers para 100.

'''Plot dos dados originais'''
fig_3d = plt.figure()
fig_3d.add_subplot(1,1,1, projection='3d').scatter(X[:,0], X[:,1], X[:,2])
plt.show()
In [58]:
'''Número ideal de clusters - Curva wcss'''
wcss = []
for i in range(1,30):
  kmeans = KMeans(n_clusters=i, init='k-means++', max_iter=300, n_init=10)
  kmeans.fit(X)
  wcss.append(kmeans.inertia_)
# 5 clusters continua sendo a divisão ideal para esse dataset
 
'''Plot da curva css'''
plt.plot(range(1,30), wcss)
plt.title('Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()

Quantidade ideal de clusters

Esse tópico não havia sido abordado inicialmente. Acontece que o Elbow method é geralmente é passado como um método visual: "Ah! Dá uma olhada aqui e são mais ou menos 7 clusters" e eu não gosto de resultados inexatos. Procurei na internet alguma função que pudesse me passar a fórmula exata. Cheguei em uma solução muito interessante: https://jtemporal.com/kmeans-and-elbow-method/

In [59]:
'''Pacote math'''
from math import sqrt

'''Função optimal n_clusters'''
def optimal_number_of_clusters(wcss):
    x1, y1 = 2, wcss[0]
    x2, y2 = 30, wcss[len(wcss)-1] # Devemos mudar aqui conforme o número de iterações que nós fizemos. No caso, 30. Algumas pessoas fazem mais e outras menos.

    distances = []
    for i in range(len(wcss)):
        x0 = i+2
        y0 = wcss[i]
        numerator = abs((y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)
        denominator = sqrt((y2 - y1)**2 + (x2 - x1)**2)
        distances.append(numerator/denominator)
    
    return distances.index(max(distances)) + 1
In [60]:
'''Cálculo do n_clusters ideal'''
n_ideal = optimal_number_of_clusters(wcss)
print(n_ideal)
# Aqui descobrimos que o n_clusters ideal é 8. Vamos fazer com 5 apenas para montar uma visualização melhor.
7
In [61]:
'''Plot dos clusters'''
kmeans = KMeans(n_clusters=5, init='k-means++', max_iter=300, n_init=10, random_state=0)
pred_y = kmeans.fit_predict(X)
fig_3d = plt.figure()
fig_3d.add_subplot(1,1,1, projection='3d').scatter(X[:,0], X[:,1], X[:,2], c=pred_y)
plt.show()

Evolução dos clusters

O exercício era para ter acabado aqui mas fiquei curiosa em saber como é a evolução do cluster à cada iteração em uma amostra 3D. Sabendo que n_init é o número de vezes que um centroide é esolhido e max_iter é o número máximo de iterações que o k-means irá fazer, vou fazer uma escolha de centroide e X iterações e plotar em subplots utilizando um loop for:

In [ ]:
'''Pacote para fazer o download de imagens do colab'''
from google.colab import files

'''Plot dos clusters'''
for i in range(1,11):
  kmeans = KMeans(n_clusters=5, init='k-means++', max_iter=i, n_init=1, random_state=0)
  pred_y = kmeans.fit_predict(X)
  fig_3d = plt.figure()
  fig_3d.add_subplot(1,1,1, projection='3d').scatter(X[:,0], X[:,1], X[:,2], c=pred_y)
  plt.title('Iteration {}'.format(i))
  plt.savefig('kmeans{}.jpg'.format(i))
  files.download('kmeans{}.jpg'.format(i))
plt.close()

Visualização do resultado

Para finalizar, fiz um pequeno vídeo com os plots gerados para as 10 iterações, dê uma conferida:

In [63]:
from IPython.display import YouTubeVideo
YouTubeVideo('1HhTusjL42k', width=560, height=315)
Out[63]:

Conclusão

Ao final do experimento aprendi algumas coisas interessantes:

  • Sim, é possível fazer a clusterização com dados 3D.
  • A junção dos dois conceitos demandou apenas entender como é a notação dos arrays do numpy, os quais eu não era muito familiarizada.
  • É possível programar o download de imagens com um pacote próprio do google colab (se não houvesse essa possibilidade, eu teria que clicar em imagem por imagem para fazer o download...)
  • É possível incluir vídeos no notebook editável (google colab ou jupyter notebook). Descobri 4 maneiras diferentes de fazer isso, porém, nenhuma delas funciona quando subo o notebook no github. A solução é colocar o thumbnail do vídeo e um link abaixo para quem quiser acessar.