Machine Learning: KNN e Agrupamento
Última atualização: 2026-08-26
Pássaros da mesma plumagem andam juntos — KNN encontra "os vizinhos mais parecidos com você" para classificação, enquanto K-Means encontra "os grupos mais coesos" para segmentação.
1. O que você vai aprender
- Classificação KNN: métricas de distância (Euclidiana/Manhattan/Cosseno), escolha de K, mecanismo de votação
- Agrupamento K-Means: fluxo do algoritmo, escolha de K (método do cotovelo/silhouette), critérios de convergência
- Agrupamento por densidade DBSCAN: pontos centrais/pontos de borda/pontos de ruído, ajuste de eps e min_samples
- Agrupamento hierárquico: AgglomerativeClustering e dendrogramas
- Segmentação de perfil de usuários do Bob: agrupamento RFM para identificar grupos de alto valor/dormentes/em churn
2. A história real de um gerente de operações de usuários
(1) O problema: marketing único para 500 mil usuários é ineficiente
A plataforma do Bob tem 500 mil usuários, e cada usuário recebe os mesmos e-mails promocionais. O resultado: usuários de alto valor acham as promoções muito básicas, usuários dormentes nunca abrem os e-mails, e a taxa de conversão geral é de apenas 2%. Os usuários são diversos — uma estratégia única para todos desperdiça 80% do orçamento de marketing.
(2) A solução com agrupamento
O agrupamento pode dividir automaticamente os usuários em grupos de alto valor/ativos/dormentes/em churn, permitindo marketing de precisão.
from sklearn.cluster import KMeans
# Agrupamento RFM: agrupar usuários por Recência, Frequência, Valor Monetário
rfm = df[["recency", "frequency", "monetary"]]
kmeans = KMeans(n_clusters=4, random_state=42)
df["segment"] = kmeans.fit_predict(rfm)
for i in range(4):
segment = df[df["segment"] == i]
print(f"Segmento {i}: {len(segment)} usuários, "
f"Valor Monetário Médio={segment['monetary'].mean():.0f} USD")
(3) O resultado: marketing de precisão aumenta a conversão 4x
Depois que o Bob usou o agrupamento para dividir os usuários em 4 segmentos, o grupo de alto valor recebeu ofertas premium, o grupo dormente recebeu pacotes de reengajamento, e a taxa de conversão geral saltou de 2% para 8% — uma melhoria de 3x no ROI de marketing.
3. Classificação KNN
(1) Métricas de distância e escolha de K
▶ Exemplo: Classificação KNN + comparando diferentes valores de K
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
X, y = load_iris(return_X_y=True)
# Comparar diferentes valores de K
for k in [1, 3, 5, 7, 11, 21]:
pipe = Pipeline([
("scaler", StandardScaler()),
("knn", KNeighborsClassifier(n_neighbors=k)),
])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"K={k:2d}: Acurácia={scores.mean():.3f} +/- {scores.std():.3f}")
Saída:
# Executado com sucesso
| Métrica de distância | Características da fórmula | Melhor para |
|---|---|---|
| Euclidiana | Distância em linha reta | Features contínuas, todas as dimensões igualmente importantes |
| Manhattan | Distância em quarteirões | Dados de alta dimensionalidade, muitos outliers |
| Cosseno | Distância angular | Vetores de texto, direção importa mais que magnitude |
| Minkowski | Distância generalizada (p=2→Euclidiana, p=1→Manhattan) | Ajuste flexível |
(2) O efeito de K
| Valor de K | Fronteira de decisão | Risco |
|---|---|---|
| K=1 | Extremamente irregular | Sobreajuste (sensível a ruído) |
| K=pequeno (3-7) | Moderadamente curva | Geralmente ótimo |
| K=grande (20+) | Quase linear | Subajuste |
▶ Exemplo: Previsão de compra de usuário com KNN
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import numpy as np
rng = np.random.default_rng(42)
n = 1000
X = np.column_stack([
rng.uniform(0, 100, n), # tempo_navegacao
rng.uniform(0, 50, n), # valor_carrinho_usd
rng.integers(1, 30, n), # paginas_vistas
rng.integers(0, 10, n), # compras_anteriores
])
y = (X[:, 1] * 0.05 + X[:, 3] * 0.3 + rng.normal(0, 0.5, n) > 2).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
pipe = Pipeline([
("scaler", StandardScaler()),
("knn", KNeighborsClassifier(n_neighbors=7, weights="distance")),
])
pipe.fit(X_train, y_train)
print(classification_report(y_test, pipe.predict(X_test)))
Saída:
# Executado com sucesso
4. Agrupamento K-Means
(1) Fluxo do algoritmo
sequenceDiagram
participant Init as Inicializar K Centros
participant Assign as Atribuir Pontos
participant Update as Atualizar Centros
participant Check as Convergiu?
Init->>Assign: Posições aleatórias dos K centros
loop Até convergir
Assign->>Update: Cada ponto → centro mais próximo
Update->>Check: Centros = média dos pontos atribuídos
Check->>Assign: Não convergiu (centros se moveram)
end
Check-->>Done: Convergiu! Retornar clusters
▶ Exemplo: Agrupamento K-Means + método do cotovelo
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.default_rng(42)
# Gerar 4 clusters naturais
X = np.vstack([
rng.normal([20, 500], [3, 50], (200, 2)), # Alto valor
rng.normal([50, 200], [5, 30], (300, 2)), # Médio
rng.normal([80, 50], [8, 20], (350, 2)), # Baixo
rng.normal([10, 800], [2, 40], (150, 2)), # VIP
])
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Método do cotovelo
inertias = []
K_range = range(2, 10)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X_scaled)
inertias.append(km.inertia_)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(K_range, inertias, "bo-", linewidth=2)
ax.set_xlabel("Número de Clusters (K)")
ax.set_ylabel("Inércia (Soma de Quadrados Intra-Cluster)")
ax.set_title("Método do Cotovelo para K Ótimo")
ax.axvline(x=4, color="red", linestyle="--", label="Cotovelo em K=4")
ax.legend()
plt.tight_layout()
plt.savefig("elbow_method.png", dpi=150)
Saída:
# Executado com sucesso
(2) Coeficiente de Silhueta
▶ Exemplo: Escolhendo K com o Coeficiente de Silhueta
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
import numpy as np
# Usando o mesmo X_scaled do exemplo anterior
for k in range(2, 8):
km = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = km.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
print(f"K={k}: Coeficiente de Silhueta={score:.3f}")
Saída:
# Executado com sucesso
| Métrica | Significado | Como determinar o K ótimo |
|---|---|---|
| Inércia (método do cotovelo) | Soma de quadrados intra-cluster | No ponto de inflexão |
| Coeficiente de Silhueta | Coesão vs. separação | No valor máximo |
| Gap Statistic | Comparação contra distribuição aleatória | Primeiro K abaixo do limite superior |
5. Agrupamento por densidade DBSCAN
▶ Exemplo: DBSCAN descobre clusters de formatos arbitrários
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import numpy as np
rng = np.random.default_rng(42)
# Criar clusters não esféricos (dois círculos)
from sklearn.datasets import make_circles
X, _ = make_circles(n_samples=500, factor=0.5, noise=0.05, random_state=42)
X_scaled = StandardScaler().fit_transform(X)
# K-Means vs DBSCAN
km = KMeans(n_clusters=2, random_state=42, n_init=10)
km_labels = km.fit_predict(X_scaled)
db = DBSCAN(eps=0.3, min_samples=10)
db_labels = db.fit_predict(X_scaled)
n_clusters = len(set(db_labels)) - (1 if -1 in db_labels else 0)
n_noise = list(db_labels).count(-1)
print(f"Silhueta K-Means: {silhouette_score(X_scaled, km_labels):.3f}")
print(f"Clusters DBSCAN: {n_clusters}, Pontos de ruído: {n_noise}")
if n_clusters > 1:
db_valid = db_labels != -1
print(f"Silhueta DBSCAN: {silhouette_score(X_scaled[db_valid], db_labels[db_valid]):.3f}")
Saída:
# Executado com sucesso
| Dimensão | K-Means | DBSCAN |
|---|---|---|
| Formato do cluster | Esférico | Formatos arbitrários |
| Valor de K | Deve ser predefinido | Descoberto automaticamente |
| Pontos de ruído | Não tratados | Rotulados como -1 |
| Densidade desigual | Desempenho ruim | Ainda lida bem |
| Velocidade de computação | Rápida | Moderada |
6. Segmentação por perfil RFM do Bob
▶ Exemplo: Projeto completo de agrupamento RFM
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 5000
df = pd.DataFrame({
"user_id": range(10001, 10001 + n),
"recency_days": rng.integers(1, 365, n),
"frequency": rng.integers(1, 50, n),
"monetary_usd": rng.exponential(500, n),
})
# Features RFM
rfm = df[["recency_days", "frequency", "monetary_usd"]]
# Transformação log para features enviesadas
rfm_log = rfm.copy()
rfm_log["frequency"] = np.log1p(rfm_log["frequency"])
rfm_log["monetary_usd"] = np.log1p(rfm_log["monetary_usd"])
# Padronizar
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_log)
# K-Means com K=4
km = KMeans(n_clusters=4, random_state=42, n_init=10)
df["segment"] = km.fit_predict(rfm_scaled)
# Analisar segmentos
segment_summary = df.groupby("segment").agg({
"recency_days": "mean",
"frequency": "mean",
"monetary_usd": "mean",
"user_id": "count",
}).round(1)
segment_summary.columns = ["avg_recency", "avg_frequency", "avg_monetary", "count"]
segment_summary = segment_summary.sort_values("avg_monetary", ascending=False)
# Rotular segmentos
labels = ["Campeões", "Fiéis", "Em Risco", "Perdidos"]
for idx, (_, row) in enumerate(segment_summary.iterrows()):
print(f"{labels[idx]:10s}: {int(row['count']):5d} usuários, "
f"Recência={row['avg_recency']:.0f}d, "
f"Freq={row['avg_frequency']:.1f}, "
f"Monetário={row['avg_monetary']:.0f} USD")
Saída:
# Executado com sucesso
| Segmento | Recência | Frequência | Monetário | Estratégia de marketing |
|---|---|---|---|---|
| Campeões | Baixa (ativos recentemente) | Alta | Alta | Atendimento VIP, recomendações premium |
| Fiéis | Média | Média-alta | Média | Programas de fidelidade, cross-selling |
| Em Risco | Alta (muito tempo sem comprar) | Média | Média | Pacotes de reengajamento, ofertas por tempo limitado |
| Perdidos | Muito alta | Baixa | Baixa | Outreach de baixo custo, pesquisas |
❓ Perguntas Frequentes
P: Qual a diferença entre K no KNN e K no K-Means? R: K no KNN significa "quantos vizinhos votam" — é um algoritmo de classificação. K no K-Means significa "quantos grupos formar" — é um algoritmo de agrupamento. São conceitos completamente diferentes.
P: KNN exige normalização? R: Com certeza. KNN é baseado em distância, então features não normalizadas (por exemplo, renda 0–100.000 vs. idade 0–100) dominarão completamente o cálculo da distância. Sempre use com StandardScaler.
P: Como escolher K para K-Means? R: Combine o método do cotovelo e o coeficiente de silhueta. O método do cotovelo encontra o ponto de inflexão; o coeficiente de silhueta encontra o máximo. Podem discordar — ceda à razoabilidade de negócio.
P: Como escolher eps para DBSCAN? R: Plote o gráfico de K-distance (distâncias dos k vizinhos mais próximos ordenadas) e encontre o cotovelo. Valores típicos de eps vão de 0,1–1,0 (após padronização). min_samples geralmente é definido como 2 × número de dimensões.
P: Como avaliar os resultados do agrupamento? R: O agrupamento não supervisionado não tem "verdade de campo". Use o coeficiente de silhueta (avaliação matemática) + interpretabilidade de negócio (avaliação de domínio) para validação dupla. Apenas clusters que mapeiam para conceitos de negócio significativos são valiosos.
P: Por que aplicar uma transformação log antes do agrupamento RFM? R: Frequência e Monetário normalmente têm distribuições enviesadas à direita — alguns usuários de alto valor esticam as distâncias entre clusters. Uma transformação log torna a distribuição mais simétrica, melhorando o desempenho do K-Means.
📖 Resumo
- Classificação KNN: escolha K vizinhos mais próximos por métrica de distância e vote; K pequeno sobreajusta, K grande subajusta; normalização é obrigatória
- Agrupamento K-Means: atribui iterativamente pontos e atualiza centros; use o método do cotovelo e o coeficiente de silhueta para escolher K; só funciona bem para clusters esféricos
- Agrupamento por densidade DBSCAN: descobre automaticamente o número de clusters, rotula ruído, lida com formatos arbitrários, mas é sensível aos parâmetros
- Segmentação de usuários RFM é uma aplicação clássica de agrupamento em e-commerce: Recência + Frequência + Monetário → perfis de usuário
- Aplique transformações log para lidar com distribuições enviesadas antes do agrupamento; padronize para eliminar diferenças de escala
- Avaliação de clusters = métricas matemáticas (coeficiente de silhueta) + interpretabilidade de negócio — ambos são essenciais
📝 Exercícios
- Básico (Dificuldade ⭐): Use KNN para classificar a Iris. Compare a acurácia com validação cruzada para K=1, 5, 10, 20 e encontre o K ótimo. Dica: Pipeline(StandardScaler + KNN) + cross_val_score.
- Intermediário (Dificuldade ⭐⭐): Gere dados de 4 clusters com make_blobs. Agrupe com K-Means e DBSCAN e compare os coeficientes de silhueta e o tratamento de pontos de ruído. Dica: DBSCAN rotula ruído como -1.
- Desafio (Dificuldade ⭐⭐⭐): Implemente o agrupamento RFM do Bob — gere dados simulados de usuários, aplique transformação log + padronização, escolha K com o método do cotovelo, execute K-Means, nomeie cada segmento de forma significativa (Campeões/Fiéis/Em Risco/Perdidos) e produza os valores médios de RFM por segmento. Dica: consulte o exemplo completo na Seção 6.
← Anterior: SVM Máquinas de Vetores de Suporte | Próximo: Engenharia de Features →