Imagine que você está em um jogo de estratégia onde precisa tomar decisões baseadas em duas condições: se está chovendo E se tem guarda-chuva, ou se não está chovendo E não tem guarda-chuva – em ambos os casos você fica seco. Mas se apenas uma condição for verdadeira, você se molha. Este padrão “ou um ou outro, mas não ambos” é exatamente o problema XOR – um desafio clássico que modelos lineares simples não conseguem resolver. O GPC brilha nestas situações, mapeando fronteiras de decisão complexas com precisão probabilística.
Como isso funciona na prática?
O problema XOR é famoso por ser não linearmente separável – você não pode traçar uma única linha reta para separar as classes. Enquanto classificadores lineares como regressão logística falham miseravelmente aqui, o GPC usa kernels como o RBF para mapear os dados para um espaço de dimensão superior onde se tornam linearmente separáveis. Diferentemente de redes neurais que também resolvem XOR mas são “caixas pretas”, o GPC fornece probabilidades calibradas que mostram não apenas a decisão, mas o quão confiante ele está em cada região do espaço de características.
Mãos na massa: visualizando o GPC no problema XOR
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
""" Ilustração do Gaussian Process Classifier no problema XOR Demonstra como o GPC resolve problemas não-linearmente separáveis """ import numpy as np import matplotlib.pyplot as plt from sklearn.gaussian_process import GaussianProcessClassifier from sklearn.gaussian_process.kernels import RBF, Matern from sklearn.linear_model import LogisticRegression from sklearn.inspection import DecisionBoundaryDisplay # Criando o dataset XOR clássico def criar_dataset_xor(ruido=0.1): """Cria o problema XOR com quatro clusters bem definidos""" np.random.seed(42) # Pontos para cada quadrante do XOR # Classe 0: (0,0) e (1,1) | Classe 1: (0,1) e (1,0) n_pontos = 50 X00 = np.random.multivariate_normal([0, 0], [[ruido, 0], [0, ruido]], n_pontos) X11 = np.random.multivariate_normal([1, 1], [[ruido, 0], [0, ruido]], n_pontos) X01 = np.random.multivariate_normal([0, 1], [[ruido, 0], [0, ruido]], n_pontos) X10 = np.random.multivariate_normal([1, 0], [[ruido, 0], [0, ruido]], n_pontos) X = np.vstack([X00, X11, X01, X10]) y = np.hstack([np.zeros(n_pontos * 2), np.ones(n_pontos * 2)]) return X, y X, y = criar_dataset_xor(ruido=0.05) print("Dataset XOR criado:") print(f"Forma dos dados: {X.shape}") print(f"Distribuição das classes: {np.sum(y==0)} pontos classe 0, {np.sum(y==1)} pontos classe 1") print("Padrão XOR: pontos (0,0) e (1,1) são classe 0, pontos (0,1) e (1,0) são classe 1") # Criando e treinando os modelos kernel_rbf = 1.0 * RBF(length_scale=1.0) gpc = GaussianProcessClassifier(kernel=kernel_rbf, random_state=42) # Comparando com regressão logística (que deve falhar) logreg = LogisticRegression(random_state=42) print("\nTreinando modelos...") gpc.fit(X, y) logreg.fit(X, y) print(f"Kernel GPC otimizado: {gpc.kernel_}") # Criando grid para visualização xx, yy = np.meshgrid(np.linspace(-0.5, 1.5, 100), np.linspace(-0.5, 1.5, 100)) X_grid = np.c_[xx.ravel(), yy.ravel()] # Previsões dos modelos Z_gpc = gpc.predict_proba(X_grid)[:, 1].reshape(xx.shape) Z_logreg = logreg.predict_proba(X_grid)[:, 1].reshape(xx.shape) # Visualização comparativa fig, axes = plt.subplots(2, 3, figsize=(18, 12)) # Plot 1: Dados originais scatter = axes[0, 0].scatter(X[:, 0], X[:, 1], c=y, cmap='bwr', alpha=0.8, edgecolors='black') axes[0, 0].set_title('Dataset XOR Original\n(Padrão Não-Linearmente Separável)') axes[0, 0].set_xlabel('Característica 1') axes[0, 0].set_ylabel('Característica 2') plt.colorbar(scatter, ax=axes[0, 0]) # Plot 2: Regressão Logística (deve falhar) contour_logreg = axes[0, 1].contourf(xx, yy, Z_logreg, levels=20, alpha=0.6, cmap='bwr') axes[0, 1].scatter(X[:, 0], X[:, 1], c=y, cmap='bwr', alpha=0.8, edgecolors='black') axes[0, 1].set_title('Regressão Logística\n(Falha em capturar padrão XOR)') axes[0, 1].set_xlabel('Característica 1') axes[0, 1].set_ylabel('Característica 2') plt.colorbar(contour_logreg, ax=axes[0, 1]) # Plot 3: GPC - Probabilidades contour_gpc = axes[0, 2].contourf(xx, yy, Z_gpc, levels=20, alpha=0.6, cmap='bwr') axes[0, 2].scatter(X[:, 0], X[:, 1], c=y, cmap='bwr', alpha=0.8, edgecolors='black') axes[0, 2].set_title('GPC - Probabilidades\n(Captura perfeitamente o padrão XOR)') axes[0, 2].set_xlabel('Característica 1') axes[0, 2].set_ylabel('Característica 2') plt.colorbar(contour_gpc, ax=axes[0, 2]) # Plot 4: GPC - Decisão binária Z_gpc_binary = (Z_gpc > 0.5).astype(int) contour_binary = axes[1, 0].contourf(xx, yy, Z_gpc_binary, levels=20, alpha=0.6, cmap='bwr') axes[1, 0].scatter(X[:, 0], X[:, 1], c=y, cmap='bwr', alpha=0.8, edgecolors='black') axes[1, 0].set_title('GPC - Decisão Binária\n(Fronteira de decisão complexa)') axes[1, 0].set_xlabel('Característica 1') axes[1, 0].set_ylabel('Característica 2') # Plot 5: GPC - Incerteza incerteza = 1 - 2 * np.abs(Z_gpc - 0.5) # Máxima quando probabilidade = 0.5 contour_unc = axes[1, 1].contourf(xx, yy, incerteza, levels=20, alpha=0.6, cmap='viridis') axes[1, 1].scatter(X[:, 0], X[:, 1], c=y, cmap='bwr', alpha=0.8, edgecolors='black') axes[1, 1].set_title('GPC - Mapa de Incerteza\n(Alta incerteza nas fronteiras)') axes[1, 1].set_xlabel('Característica 1') axes[1, 1].set_ylabel('Característica 2') plt.colorbar(contour_unc, ax=axes[1, 1]) # Plot 6: Comparação de desempenho models = ['Regressão Logística', 'GPC'] scores = [logreg.score(X, y), gpc.score(X, y)] bars = axes[1, 2].bar(models, scores, color=['red', 'blue'], alpha=0.7) axes[1, 2].set_ylim(0, 1.1) axes[1, 2].set_title('Comparação de Acurácia') axes[1, 2].set_ylabel('Acurácia') # Adicionando valores nas barras for bar, score in zip(bars, scores): height = bar.get_height() axes[1, 2].text(bar.get_x() + bar.get_width()/2., height + 0.01, f'{score:.3f}', ha='center', va='bottom') plt.tight_layout() plt.show() # Análise detalhada das previsões print("\n=== ANÁLISE DETALHADA DAS PREVISÕES ===") print("Pontos de teste e suas probabilidades GPC:") pontos_teste = np.array([[0.1, 0.1], [0.9, 0.9], [0.1, 0.9], [0.9, 0.1], [0.5, 0.5]]) # Ponto central - alta incerteza for i, ponto in enumerate(pontos_teste): prob_gpc = gpc.predict_proba([ponto])[0, 1] prob_logreg = logreg.predict_proba([ponto])[0, 1] print(f"\nPonto {ponto}:") print(f" GPC: probabilidade classe 1 = {prob_gpc:.4f}") print(f" Regressão Logística: probabilidade classe 1 = {prob_logreg:.4f}") print(f" Incerteza GPC: {1 - 2 * abs(prob_gpc - 0.5):.4f}") |
Os detalhes que fazem diferença
O sucesso do GPC no problema XOR demonstra o poder dos kernels para capturar relações não-lineares complexas. Enquanto a regressão logística tenta desesperadamente ajustar um plano linear que nunca funcionará, o kernel RBF do GPC mapeia os pontos para um espaço onde a separação torna-se possível. Contudo, a escolha do parâmetro de length_scale é crucial – valores muito pequenos podem levar a overfitting, enquanto valores muito grandes não capturam a complexidade necessária. Analogamente importante é entender que as áreas de alta incerteza (probabilidades perto de 0.5) correspondem exatamente às regiões entre os clusters, onde o modelo honestamente admite sua falta de informação.
- Kernel RBF: Mapeia dados para espaço de alta dimensão onde se tornam linearmente separáveis
- Length_scale: Controla a suavidade da fronteira de decisão
- Incerteza natural: Áreas entre clusters mostram alta incerteza, não indecisão arbitrária
- Interpretabilidade: Probabilidades refletem verdadeira confiança baseada na distribuição dos dados
Perguntas que os iniciantes fazem
Você deve estar se perguntando: “Por que a regressão logística falha tão feio no XOR?” Excelente questão! A regressão logística é inerentemente linear – ela só pode traçar uma linha reta (ou plano) para separar classes. O padrão XOR requer uma fronteira não-linear como uma cruz ou dois segmentos desconectados. Uma confusão comum é pensar que adicionar features polinomiais resolveria o problema – até funciona, mas requer saber antecipadamente a transformação correta. Outra dúvida frequente: “O GPC sempre resolve problemas não-lineares?” Sim, desde que o kernel apropriado seja usado, mas o custo computacional pode ser proibitivo para datasets muito grandes.
Para onde ir agora?
Experimente o GPC em outros problemas não-linearmente separáveis além do XOR. Tente diferentes kernels como Matern ou kernels compostos para padrões mais complexos. Observe como as probabilidades e incertezas se comportam em diferentes regiões do espaço de features. O momento “aha!” acontece quando você visualiza como o GPC cria fronteiras de decisão complexas que seriam impossíveis para métodos lineares, enquanto mantém uma medida honesta de sua própria incerteza.
Assuntos relacionados
Para entender profundamente esta aplicação, estude:
- Teoria de kernels: o truque do kernel e espaços de características
- Separabilidade linear: quando problemas podem ser resolvidos por métodos lineares
- Funções de base radial: fundamento matemático dos kernels RBF
- Mapeamento não-linear: transformações que tornam dados linearmente separáveis
- Complexidade de modelos: trade-off entre flexibilidade e generalização