Aprendizagem não supervisionada · laboratório visual

K-Means,
do zero.

Entenda o que o algoritmo procura, como ele mede distância, por que os centróides se movem, o que significa convergir e como avaliar se os grupos encontrados realmente fazem sentido.

sem rótulos Y distância Euclidiana centróides inércia / WCSS k-means++

Antes do algoritmo

O que o K-Means está tentando descobrir?

Ele recebe apenas os atributos X. Não existem classes corretas previamente informadas. O objetivo é criar K grupos compactos em que os pontos fiquem, tanto quanto possível, próximos do centro do próprio grupo.

K

Quantos grupos?

K é a quantidade de clusters que desejamos formar. O algoritmo não “descobre K sozinho” na versão clássica: esse valor precisa ser escolhido ou avaliado externamente.

μ

O que é um centróide?

É o vetor médio dos pontos de um cluster. Ele representa o “centro matemático” do grupo e não precisa coincidir com um ponto real do conjunto de dados.

J

O que significa “bom agrupamento”?

O K-Means procura reduzir a soma das distâncias quadráticas de cada ponto ao centróide do seu cluster. Essa quantidade é chamada de inércia, SSE ou WCSS.

Ideia central: o algoritmo alterna duas decisões simples — “qual centróide é o mais próximo?” e “onde fica a média deste grupo?” — até que repetir essas duas decisões praticamente não altere mais a solução.

A dúvida mais importante

Se eu já formei o cluster, por que mover o centróide?

Porque o primeiro cluster não é definitivo. Ele foi criado usando centróides que ainda eram apenas palpites iniciais. O K-Means alterna entre duas perguntas diferentes.

A

Com os centróides fixos: quem pertence a quem?

Primeiro, os centróides ficam parados. Cada ponto escolhe o centróide mais próximo. Isso produz clusters provisórios.

cᵢ = arg minⱼ ||xᵢ − μⱼ||²

Enquanto os centróides estão fixos, essa escolha é a melhor atribuição possível para cada ponto.

B

Com os clusters fixos: onde deveria estar o centro?

Agora seguramos temporariamente os membros de cada cluster e calculamos a média deles. A média é a posição que minimiza a soma das distâncias quadráticas para aquele grupo atual.

μⱼ = (1 / |Cⱼ|) · Σ xᵢ

Ao mover μⱼ, porém, mudamos as distâncias. Por isso, a atribuição feita no passo anterior pode deixar de ser a melhor.

Um exemplo pequeno mostra por que não é redundante

Considere os pontos 0, 4, 5, 6 e 20 em uma linha, com dois centróides iniciais em 0 e 6. Veja que um ponto realmente troca de grupo depois que o centróide se move.

1ª atribuição

μ₁=0 recebe {0}
μ₂=6 recebe {4,5,6,20}

Esses grupos surgiram apenas porque 0 e 6 eram os centros atuais.

Atualização

novo μ₁=0
novo μ₂=(4+5+6+20)/4=8,75

Os centros agora representam melhor os grupos provisórios.

Nova atribuição

As distâncias são calculadas de novo usando 0 e 8,75.

O ponto 4 troca de cluster: antes estava a apenas 2 unidades de μ₂=6 e a 4 de μ₁=0; agora está a 4 de μ₁=0 e a 4,75 de μ₂=8,75. Depois disso, as médias serão recalculadas outra vez.

Portanto: atualizar o centróide resolve o melhor centro para os membros atuais; reatribuir resolve os melhores membros para os centros atuais. Uma etapa altera o problema da outra.

Em linguagem direta: o K-Means funciona assim

Se você quiser guardar o algoritmo em uma única ideia, pense exatamente desta forma:

Resumo didático: inicializo K centróides, associo cada ponto ao centróide mais próximo, calculo a média dos pontos de cada grupo e substituo o centróide antigo por essa média. Depois repito tudo iterativamente até que os centróides quase não mudem mais — ou até nenhum ponto trocar de grupo.

Passo a passo mental

  1. Escolho o valor de K.
  2. Posiciono K centróides iniciais (geralmente de forma aleatória ou com k-means++).
  3. Calculo a distância de cada ponto até todos os centróides.
  4. Cada ponto entra no grupo do centróide mais próximo.
  5. Para cada grupo formado, calculo a média das coordenadas.
  6. Essa média vira a nova posição do centróide.
  7. Como os centróides mudaram, recalculo todas as atribuições.
  8. Repito até estabilizar.

Importante: “mover” não é arrastar aos poucos

No desenho, parece que o centróide anda suavemente, mas matematicamente o que acontece é apenas uma substituição.

centróide antigo → média do grupo atual

Se o centróide estava em (8, 7) e a nova média calculada deu (5, 4), o algoritmo simplesmente passa a usar (5, 4) na próxima iteração.

Depois disso, todos os pontos medem novamente sua distância aos novos centróides. É por isso que um ponto pode trocar de grupo.

Experimente antes de decorar

Laboratório passo a passo

Avance uma fase por vez. A lateral mostra a conta que acabou de ser executada; a inércia permite observar se a solução está melhorando.

1 · Inicializar
2 · Atribuir
3 · Atualizar médias
4 · Convergir
ponto / observação centróide μ

A matemática por trás da animação

Quatro expressões explicam o K-Means

As fórmulas abaixo correspondem diretamente às fases do laboratório. O objetivo é associar cada símbolo a uma ação visual.

1. Representar os dados

X = { x₁, x₂, …, xₙ },   xᵢ ∈ ℝᵈ

Cada observação xᵢ é um vetor com d atributos. Em um gráfico 2D, por exemplo, xᵢ = (x, y).

nnúmero de observações.
dnúmero de atributos (dimensões).
xᵢi-ésima observação do conjunto.

2. Medir a distância

d(xᵢ, μⱼ) = √[ Σₘ₌₁ᵈ (xᵢₘ − μⱼₘ)² ]

A distância Euclidiana mede o comprimento da linha reta entre o ponto xᵢ e o centróide μⱼ. O cluster escolhido será o do menor valor.

μⱼcentróide do cluster j.
míndice do atributo comparado.
d(·)distância entre dois vetores.

3. Atribuir o cluster

cᵢ = arg minⱼ || xᵢ − μⱼ ||²

arg min significa: “qual índice j produz o menor valor?”. Portanto, cᵢ guarda o número do centróide mais próximo de xᵢ.

cᵢcluster atribuído ao ponto xᵢ.
arg minposição/índice que minimiza a expressão.

4. Recalcular o centróide

μⱼ = (1 / |Cⱼ|) · Σ₍xᵢ∈Cⱼ₎ xᵢ

O novo centróide é simplesmente a média de cada atributo dos pontos atualmente pertencentes ao cluster Cⱼ.

Cⱼconjunto de pontos do cluster j.
|Cⱼ|quantidade de pontos no cluster j.

Exemplo numérico completo

Considere o ponto x = (2, 3) e dois centróides: μ₁ = (1, 1) e μ₂ = (5, 4).

Distância até μ₁
d = √[(2−1)² + (3−1)²] = √5 ≈ 2,24
Distância até μ₂
d = √[(2−5)² + (3−4)²] = √10 ≈ 3,16

Conclusão: como 2,24 < 3,16, esse ponto é atribuído ao cluster 1. A próxima fase recalculará μ₁ usando a média de todos os pontos que também foram enviados para esse cluster.

Função objetivo

O algoritmo tenta fazer J diminuir

A forma clássica do K-Means minimiza a soma das distâncias Euclidianas quadráticas entre cada ponto e o centróide do seu próprio cluster.

Inércia / SSE / WCSS

J = Σᵢ₌₁ⁿ || xᵢ − μcᵢ ||²

Quanto menor J, mais compactos são os grupos para aquele valor de K. Durante as iterações de Lloyd, a função objetivo não aumenta: cada fase de atribuição e atualização procura manter ou reduzir esse custo.

Mas cuidado: menor inércia não significa automaticamente “melhor explicação do mundo”. A inércia sempre tende a cair quando aumentamos K.

Por que justamente a média?

Para um conjunto de pontos fixo, o valor que minimiza a soma dos erros quadráticos é a média. Por isso, depois que os membros de um cluster estão definidos, mover μ para a média é a melhor atualização possível para esse cluster dentro desse critério.

minμ Σ ||xᵢ − μ||²   ⇒   μ = (1/n) Σxᵢ

Essa ligação entre erro quadrático e média é a razão matemática do nome K-Means.

Escolha do K

Se K é informado pelo usuário, como escolher?

Não existe um único teste universal. Duas ferramentas didáticas comuns são o método do cotovelo e o coeficiente de silhouette. Use-os junto com conhecimento do problema.

Método do cotovelo

Execute o K-Means para vários valores de K e observe a queda da inércia. Procura-se um ponto a partir do qual aumentar K traz ganhos proporcionalmente menores.

O gráfico acima é recalculado para o conjunto de dados do laboratório.

Silhouette

s(i) = [ b(i) − a(i) ] / max{ a(i), b(i) }

a(i) mede a distância média do ponto aos membros do próprio cluster; b(i) mede a menor distância média desse ponto a outro cluster. O resultado fica entre −1 e 1.

—

Próximo de +1: ponto bem separado.
Próximo de 0: fronteira ambígua.
Negativo: possível atribuição ruim.

Pré-processamento

Distância só faz sentido se as escalas fizerem sentido

Como K-Means depende de distância, um atributo numericamente muito maior pode dominar os demais. Por isso, escalonamento e escolha de atributos fazem parte do problema.

Sem padronizar

Imagine idade = 20–70 e renda = 1.500–30.000. A diferença de renda pode ser centenas de vezes maior e dominar a distância Euclidiana, mesmo quando isso não deveria representar a importância real do atributo.

Z-score

z = (x − média) / desvio padrão

A padronização transforma os atributos para escalas comparáveis. Após isso, “um desvio padrão de diferença” em idade e em renda passa a ter magnitude numérica semelhante.

Não é só apertar “fit”

  • trate valores ausentes;
  • avalie outliers;
  • evite atributos redundantes;
  • codifique variáveis com cuidado;
  • verifique se distância representa similaridade no domínio.

Inicialização e convergência

O ponto de partida pode mudar o resultado

O algoritmo clássico converge para um mínimo local. Por isso, inicializações diferentes podem levar a soluções finais diferentes.

Inicialização aleatória

Seleciona posições/pontos iniciais sem uma estratégia de espalhamento. Se dois centróides começam muito próximos, o algoritmo pode gastar mais iterações ou terminar em uma solução ruim.

k-means++

Escolhe novos centros favorecendo pontos distantes dos centros já escolhidos. A ideia é começar com sementes mais bem distribuídas e reduzir a chance de uma inicialização ruim.

Várias execuções

Na prática, é comum executar o algoritmo com sementes diferentes e manter a solução de menor inércia. Bibliotecas modernas automatizam esse procedimento por meio de parâmetros como n_init.

Quando ele falha

K-Means não encontra “qualquer tipo” de cluster

Troque o conjunto do laboratório para grupos alongados ou luas. Você verá que proximidade a um centróide favorece regiões aproximadamente convexas e isotrópicas.

Casos problemáticos

  • clusters não convexos (como duas luas);
  • grupos muito diferentes em tamanho ou densidade;
  • presença forte de outliers;
  • atributos em escalas inadequadas;
  • distância Euclidiana sem significado semântico;
  • dados categóricos puros.

Alternativas dependendo do problema

  • DBSCAN / HDBSCAN: úteis quando densidade e ruído são importantes.
  • Clustering hierárquico: útil quando se deseja uma hierarquia de grupos.
  • Gaussian Mixture Models: modelam probabilidades e clusters elípticos.
  • K-Medoids: pode ser mais robusto a outliers e usa pontos reais como centros.

Do conceito para o código

K-Means do zero — sem NumPy, sem scikit-learn

A implementação abaixo usa apenas listas, laços, soma e divisão. Os centróides iniciais são passados explicitamente para que fique visível que eles são apenas o ponto de partida do algoritmo.

Leitura correta do laço: primeiro o algoritmo classifica os pontos com os centróides atuais; depois recalcula os centróides como médias; em seguida volta e classifica tudo novamente com os novos centros. Esse ciclo é o coração do K-Means.

kmeans_do_zero.py100% explícito
# Nenhuma biblioteca é necessária.


def distancia_quadrada(ponto, centroide):
    soma = 0.0

    for i in range(len(ponto)):
        diferenca = ponto[i] - centroide[i]
        soma = soma + diferenca * diferenca

    return soma


def centroide_do_cluster(pontos_do_cluster):
    quantidade = len(pontos_do_cluster)
    dimensoes = len(pontos_do_cluster[0])

    novo_centroide = []

    for dimensao in range(dimensoes):
        soma = 0.0

        for ponto in pontos_do_cluster:
            soma = soma + ponto[dimensao]

        media = soma / quantidade
        novo_centroide.append(media)

    return novo_centroide


def atribuir_pontos(dados, centroides):
    # Guarda o índice do centróide escolhido para cada ponto.
    rotulos = []

    for ponto in dados:
        melhor_cluster = 0
        menor_distancia = distancia_quadrada(ponto, centroides[0])

        for j in range(1, len(centroides)):
            distancia = distancia_quadrada(ponto, centroides[j])

            if distancia < menor_distancia:
                menor_distancia = distancia
                melhor_cluster = j

        rotulos.append(melhor_cluster)

    return rotulos


def atualizar_centroides(dados, rotulos, centroides_atuais):
    k = len(centroides_atuais)
    novos_centroides = []

    for j in range(k):
        pontos_do_cluster = []

        # Separa os pontos que atualmente pertencem ao cluster j.
        for i in range(len(dados)):
            if rotulos[i] == j:
                pontos_do_cluster.append(dados[i])

        # Se o cluster ficou vazio, mantém o centróide anterior.
        if len(pontos_do_cluster) == 0:
            novos_centroides.append(centroides_atuais[j][:])
        else:
            novos_centroides.append(
                centroide_do_cluster(pontos_do_cluster)
            )

    return novos_centroides


def centroides_iguais(a, b, tolerancia=0.000001):
    for j in range(len(a)):
        for d in range(len(a[j])):
            if abs(a[j][d] - b[j][d]) > tolerancia:
                return False

    return True


def calcular_inercia(dados, rotulos, centroides):
    total = 0.0

    for i in range(len(dados)):
        cluster = rotulos[i]
        total = total + distancia_quadrada(
            dados[i],
            centroides[cluster]
        )

    return total


def kmeans(dados, centroides_iniciais, max_iteracoes=100):
    # Fazemos uma cópia para não alterar a lista recebida.
    centroides = []
    for centroide in centroides_iniciais:
        centroides.append(centroide[:])

    for iteracao in range(max_iteracoes):
        print("\nITERACAO", iteracao + 1)
        print("Centroides no inicio:", centroides)

        # PASSO A ---------------------------------------------
        # Com os centroides PARADOS, cada ponto escolhe
        # o centroide mais proximo.
        rotulos = atribuir_pontos(dados, centroides)
        print("Rotulos:", rotulos)

        # PASSO B ---------------------------------------------
        # Com os membros dos clusters PARADOS, calculamos
        # a media de cada grupo e movemos seus centroides.
        novos_centroides = atualizar_centroides(
            dados,
            rotulos,
            centroides
        )
        print("Novos centroides:", novos_centroides)

        # Se a media nao mudou mais, chegamos a um ponto fixo.
        if centroides_iguais(centroides, novos_centroides):
            centroides = novos_centroides
            print("Convergiu.")
            break

        # IMPORTANTE:
        # ao mover os centroides, as distancias mudaram.
        # Portanto, na proxima volta precisamos atribuir
        # TODOS OS PONTOS NOVAMENTE.
        centroides = novos_centroides

    # Recalcula os rotulos uma ultima vez usando os centros finais.
    rotulos = atribuir_pontos(dados, centroides)
    inercia = calcular_inercia(dados, rotulos, centroides)

    return rotulos, centroides, inercia


# -----------------------------------------------------------
# EXEMPLO 1D — o mesmo da explicação acima
# Cada ponto continua sendo um vetor; aqui ele tem 1 dimensão.
# -----------------------------------------------------------

dados = [
    [0.0],
    [4.0],
    [5.0],
    [6.0],
    [20.0]
]

# Não são os centros "corretos".
# São somente dois palpites iniciais.
centroides_iniciais = [
    [0.0],
    [6.0]
]

rotulos, centroides, inercia = kmeans(
    dados,
    centroides_iniciais
)

print("\nRESULTADO FINAL")
print("Rotulos:", rotulos)
print("Centroides:", centroides)
print("Inercia:", inercia)

Onde o centróide realmente se move?

Na função atualizar_centroides(). Ela seleciona os membros que naquela iteração receberam o mesmo rótulo e chama centroide_do_cluster(), que calcula manualmente a média de cada dimensão.

Onde o cluster pode mudar?

Na próxima chamada de atribuir_pontos(). Como os centróides acabaram de mudar de posição, todas as distâncias são calculadas novamente. Um ponto pode permanecer no grupo ou trocar para outro.

Leia o laço principal como duas operações alternadas: atribuir pontos usando os centros atuais → recalcular os centros usando os grupos atuais → repetir. Isso é o coração do K-Means.

Resumo para revisão

O algoritmo em uma frase por etapa

1

Escolha K

Defina quantos grupos serão procurados.

2

Inicialize μ

Posicione K centróides, preferencialmente com uma estratégia como k-means++.

3

Atribua

Cada ponto vai para o centróide mais próximo.

4

Atualize

Substitua cada centróide pela média do seu cluster.

5

Repita

Alterne atribuição e atualização enquanto houver mudança relevante.

6

Avalie

Analise inércia, silhouette, estabilidade e, principalmente, significado para o domínio.

Fontes e aprofundamento

De onde vêm os conceitos desta página

A página combina a formulação histórica do K-Means, a interpretação do algoritmo de Lloyd, a inicialização k-means++ e documentação contemporânea de bibliotecas e cursos de Machine Learning.

  1. MacQueen, J. (1967). Some Methods for Classification and Analysis of Multivariate Observations. Berkeley Symposium. UC Berkeley.
  2. Lloyd, S. P. (1982). Least Squares Quantization in PCM. IEEE Transactions on Information Theory, 28(2), 129–137. DOI: 10.1109/TIT.1982.1056489.
  3. Arthur, D.; Vassilvitskii, S. (2007). k-means++: The Advantages of Careful Seeding. SODA. Stanford.
  4. scikit-learn. Clustering / KMeans / Silhouette analysis. Documentação oficial.
  5. Google for Developers. Machine Learning — Clustering: k-means, preparação de dados e limitações. Curso oficial.

INTERPRETANDO OS CLUSTERS

Como transformar centróides em perfis de clientes?

Depois que o K-Means termina, ele não devolve nomes como “VIP”, “econômico” ou “caçador de ofertas”. Ele devolve grupos e seus respectivos centróides. A interpretação desses grupos é feita analisando os valores médios de cada variável.

1. O centróide como o “cliente médio” do grupo

Geometricamente, o K-Means posiciona um centróide no centro matemático de cada cluster. Se sua base possui 10 variáveis, cada centróide também terá 10 valores.

Ideia principal: o centróide funciona como uma espécie de “cliente médio” daquele grupo.

Imagine que cada cliente seja representado por um vetor com 10 características:

x = [idade, gasto, frequência, cupons, visitas, ...]

Depois da convergência, um centróide poderia ter a seguinte forma:

μ₁ = [48, 450, 12, 0.05, 18, ...]

Esses números representam a média de cada característica dos clientes que pertencem ao Cluster 1.

Portanto, se existirem 3 clusters, teremos 3 vetores de centróides. Comparar esses vetores é uma das maneiras mais importantes de descobrir o que diferencia cada grupo.

2. O significado aparece no contraste

Um valor isolado normalmente diz pouco. Saber que um cluster possui frequência média de compra igual a 15, por exemplo, só é realmente útil quando comparamos esse valor com os demais clusters ou com a média geral da base.

A

Comparação entre clusters

Considere a variável Frequência de Compra:

Cluster 1 = 2
Cluster 2 = 15
Cluster 3 = 4

O valor do Cluster 2 é muito maior que o dos outros grupos.

Isso significa que comprar com muita frequência é uma característica marcante daquele cluster.

B

Comparação com a média global

Outra estratégia é comparar o centróide de cada cluster com a média de toda a base.

diferença = média do cluster − média global

Quanto maior essa diferença, positiva ou negativa, mais aquela variável ajuda a definir a identidade do grupo.

Em dados padronizados, essa comparação também pode ser interpretada de maneira semelhante a um Z-Score.

3. Exemplo: 3 clusters e várias características

Imagine uma base de clientes com 10 variáveis relacionadas ao comportamento de compra. Depois de executar o K-Means, analisamos os valores dos centróides.

Variável Média global Cluster 1 Cluster 2 Cluster 3
Gasto mensal R$ 150 R$ 450
Muito alto
R$ 50
Baixo
R$ 120
Próximo da média
Idade 35 anos 48 anos
Acima da média
22 anos
Abaixo da média
36 anos
Próximo da média
Uso de cupom 20% 5%
Muito baixo
80%
Muito alto
15%
Próximo da média
Outras 7 variáveis ... ... ... ...

DA MATEMÁTICA PARA A INTERPRETAÇÃO

Agora podemos dar significado aos clusters

Os nomes dos grupos não são produzidos pelo K-Means. Eles são uma interpretação humana construída a partir das características que mais diferenciam cada centróide.

1

VIPs tradicionais

O Cluster 1 possui gasto muito acima da média, idade mais elevada e uso muito baixo de cupons.

Uma interpretação possível é a de clientes de maior valor, menos dependentes de promoções.

2

Jovens caçadores de ofertas

O Cluster 2 apresenta clientes mais jovens, gasto relativamente baixo e uso extremamente alto de cupons.

O desconto aparece como uma das características mais marcantes desse grupo.

3

Clientes padrão

O Cluster 3 permanece próximo da média global em grande parte das variáveis analisadas.

Portanto, ele representa um comportamento mais próximo do perfil médio da base.

Em resumo: em dados com muitas dimensões, interpretar um cluster significa transformar seus centróides em uma tabela de médias e procurar as maiores diferenças entre os grupos e em relação à média geral. São esses valores extremos que ajudam a revelar a identidade de cada perfil.

E quando existem muitas variáveis?

Com 2 atributos, conseguimos visualizar os clusters diretamente em um gráfico. Com 10, 20 ou 100 atributos, essa visualização deixa de ser possível.

Uma estratégia comum é utilizar técnicas de redução de dimensionalidade, como o PCA — Principal Component Analysis, para representar parte dessa estrutura em duas ou três dimensões.

O PCA não substitui a análise dos centróides, mas pode ajudar a visualizar a separação entre os grupos e entender quais combinações de variáveis explicam grande parte da variação dos dados.