Algoritmos não supervisionados revelam estruturas ocultas em dados brutos, enquanto técnicas de otimização garantem que modelos generalizem bem. Este artigo explora métodos práticos para descobrir padrões sem rótulos e aprimorar robustez preditiva usando scikit-learn.
- Fundamentos da Aprendizagem Não Superviisonada
Essa abordagem extrai conhecimento diretamente de observações não rotuladas — sem dependência de saídas esperadas. Seus dois pilares são agrupamento (clustering), que organiza instâncias por similaridade, e redução de dimensionalidade, que preserva informação essencial em menos eixos.
- Algoritmos de Agrupamento
2.1 K-Means com Inicialização Estratégica
Em vez de escolher centroides aleatoriamente, usamos k-means++ para melhorar convergência e estabilidade. O algoritmo particiona os dados minimizando a soma dos quadradso das distâncias intra-grupo.
from sklearn.cluster import KMeans
from sklearn.datasets import make_moons
import numpy as np
# Dados com estrutura não linear
X_moon, _ = make_moons(n_samples=400, noise=0.08, random_state=47)
# Configuração robusta: inicialização inteligente + múltiplas inicializações
clusterer = KMeans(
n_clusters=2,
init='k-means++',
n_init=15,
max_iter=300,
random_state=47
)
labels = clusterer.fit_predict(X_moon)
# Visualização com cores distintas para cada grupo
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 6))
plt.scatter(X_moon[:, 0], X_moon[:, 1], c=labels, cmap='Set2', s=30, alpha=0.8)
plt.scatter(clusterer.cluster_centers_[:, 0], clusterer.cluster_centers_[:, 1],
c='black', marker='x', s=200, linewidths=3, label='Centroids')
plt.title("K-Means com k-means++ (2 grupos)")
plt.legend()
plt.show()
2.2 Agrupamento Hierárquico com Corte Dinâmico
Ao invés de fixar o número de clusters antecipadamente, construímos um dendrograma e aplicamos um limiar de distância para obter agrupamentos naturais — útil quando a estrutura hierárquica dos dados é relevante.
from scipy.cluster.hierarchy import linkage, fcluster
from sklearn.datasets import make_blobs
X_blob, _ = make_blobs(n_samples=250, centers=3, cluster_std=0.7, random_state=91)
# Ligação completa para maior robustez a outliers
linkage_matrix = linkage(X_blob, method='complete')
# Determinação automática do número ótimo via critério de silhueta
from sklearn.metrics import silhouette_score
best_n = 2
best_score = -1
for n in range(2, 8):
clusters = fcluster(linkage_matrix, n, criterion='maxclust')
score = silhouette_score(X_blob, clusters)
if score > best_score:
best_score = score
best_n = n
final_clusters = fcluster(linkage_matrix, best_n, criterion='maxclust')
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.title("Dendrograma (método completo)")
dendrogram(linkage_matrix, truncate_mode='level', p=4)
plt.subplot(1, 2, 2)
plt.scatter(X_blob[:, 0], X_blob[:, 1], c=final_clusters, cmap='plasma', s=40)
plt.title(f"Agrupamento final (n={best_n})")
plt.show()
2.3 DBSCAN com Ajuste Adaptativo de Parâmetros
O DBSCAN identifica regiões densas e trata ruído como pontos isolados. Em vez de valores fixos para eps e min_samples, estimamos eps com base na distância ao k-ésimo vizinho mais próximo (k=5), tornando-o mais adaptável a variações locais de densidade.
from sklearn.cluster import DBSCAN
from sklearn.neighbors import NearestNeighbors
# Estimativa de eps via gráfico de distância k-vizinha
neighbors = NearestNeighbors(n_neighbors=5).fit(X_blob)
distances, _ = neighbors.kneighbors(X_blob)
distances = np.sort(distances[:, 4], axis=0)
plt.plot(distances)
plt.axhline(y=0.5, color='r', linestyle='--', label='Limiar sugerido')
plt.title("Gráfico de Distância ao 5º Vizinho Mais Próximo")
plt.xlabel("Índice ordenado")
plt.ylabel("Distância")
plt.legend()
plt.show()
# Aplicação com parâmetros refinados
dbscan_model = DBSCAN(eps=0.5, min_samples=5)
outlier_labels = dbscan_model.fit_predict(X_blob)
plt.figure(figsize=(8, 6))
colors = ['lightgray' if x == -1 else plt.cm.viridis(x / (outlier_labels.max() + 1))
for x in outlier_labels]
plt.scatter(X_blob[:, 0], X_blob[:, 1], c=colors, s=40, alpha=0.8)
plt.title("DBSCAN: Núcleos (cores) vs Ruído (cinza)")
plt.show()
- Técnicas de Redução de Dimensionalidade
3.1 PCA com Análise de Variância Acumulada
PCA transforma variáveis correlacionadas em componentes ortogonais. Aqui, calculamos a variância explicada cumulativa para decidir quantos componentes reter — garantindo equilíbrio entre compactação e fidelidade.
from sklearn.decomposition import PCA
from sklearn.datasets import make_classification
X_high, y_high = make_classification(
n_samples=500, n_features=15, n_informative=10,
n_redundant=5, n_classes=3, random_state=123
)
pca_full = PCA()
X_pca_full = pca_full.fit_transform(X_high)
# Plot da variância acumulada
plt.figure(figsize=(8, 5))
plt.plot(np.cumsum(pca_full.explained_variance_ratio_), marker='o')
plt.axhline(y=0.95, color='k', linestyle='--', label='95% de variância')
plt.xlabel('Número de Componentes Principais')
plt.ylabel('Variância Explicada Acumulada')
plt.title('Escolha do Número Ótimo de Componentes (PCA)')
plt.legend()
plt.grid(True)
plt.show()
# Redução para manter ≥95% da informação
n_components_opt = np.argmax(np.cumsum(pca_full.explained_variance_ratio_) >= 0.95) + 1
pca_opt = PCA(n_components=n_components_opt)
X_reduced = pca_opt.fit_transform(X_high)
print(f"Redução de {X_high.shape[1]} → {n_components_opt} atributos (variância retida: {pca_opt.explained_variance_ratio_.sum():.3f})")
3.2 t-SNE com Parâmetros Contextuais
t-SNE é sensível ao parâmetro perplexity, que controla o equilíbrio entre estrutura local e global. Para conjuntos médios (~500 amostras), usamos perplexity=30 e ajustamos learning_rate dinamicamente para evitar colapsos prematuros.
from sklearn.manifold import TSNE
tsne = TSNE(
n_components=2,
perplexity=30,
learning_rate='auto',
init='pca',
random_state=199,
n_iter=1000
)
X_tsne = tsne.fit_transform(X_high)
plt.figure(figsize=(8, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y_high, cmap='Spectral', s=50, alpha=0.8)
plt.colorbar(scatter)
plt.title("Visualização t-SNE (3 classes, 500 amostras)")
plt.xlabel("Eixo t-SNE 1")
plt.ylabel("Eixo t-SNE 2")
plt.show()
- Estratégias Avançadas de Otimização de Modelo
4.1 Validação Cruzada com Estratificação e Temporal Split
Para dados com distribuição desbalanceada ou dependência temporal, susbtituímos a divisão aleatória por StratifiedKFold ou TimeSeriesSplit. Isso garante que cada fold reflita proporcionalmente as classes ou a ordem cronológica.
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import GradientBoostingClassifier
model_gb = GradientBoostingClassifier(n_estimators=80, learning_rate=0.1, random_state=202)
# Validação cruzada estratificada
cv_strategy = StratifiedKFold(n_splits=5, shuffle=True, random_state=202)
scores_gb = cross_val_score(model_gb, X_reduced, y_high, cv=cv_strategy, scoring='f1_weighted')
print(f"F1-poderado médio (5-fold estratificado): {scores_gb.mean():.4f} ± {scores_gb.std():.4f}")
4.2 Busca Bayesiana em vez de Grade ou Aleatória
A busca bayesiana modela a função de desempenho como um processo gaussiano, priorizando regiões promissoras com base em avaliações anteriores — muito mais eficiente que buscas exaustivas ou aleatórias, especialmente em espaços hiperparamétricos amplos.
from skopt import BayesSearchCV
from skopt.space import Real, Integer, Categorical
# Definindo o espaço de busca com distribuições informadas
search_space = {
'n_estimators': Integer(50, 300),
'learning_rate': Real(0.01, 0.3, prior='log-uniform'),
'max_depth': Integer(3, 12),
'subsample': Real(0.7, 1.0)
}
bayes_search = BayesSearchCV(
estimator=GradientBoostingClassifier(random_state=202),
search_spaces=search_space,
n_iter=40,
cv=cv_strategy,
scoring='f1_weighted',
random_state=202,
n_jobs=-1
)
bayes_search.fit(X_reduced, y_high)
print("Melhores hiperparâmetros encontrados:")
for param, value in bayes_search.best_params_.items():
print(f" {param}: {value}")
print(f"F1-poderado otimizado: {bayes_search.best_score_:.4f}")