A Fronteira Invisível: O que é Classificação Binária?
Imagine que você tem feijões e grãos de milho espalhados sobre uma mesa. O objetivo é traçar uma linha reta que separe perfeitamente os dois tipos de alimento. Esse é o trabalho da classificação binária: um algoritmo supervisionado que aprende a categorizar dados em dois grupos. A máquina analisa exemplos pré-rotulados para entender as características de cada grupo. Depois, ao receber um novo dado, ela decide em qual lado da “fronteira” ele se encaixa. O grande desafio? No mundo real, os dados raramente são organizados.
|
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 |
import numpy as np import matplotlib.pyplot as plt from sklearn import svm from sklearn.datasets import make_blobs # Gerando dados simulados X, y = make_blobs(n_samples=50, centers=2, random_state=6, cluster_std=1.5) # Criando uma malha para plotar as fronteiras x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # Configurando a figura com 3 subplots plt.figure(figsize=(15, 4)) # Testando diferentes valores de C for i, C in enumerate([0.1, 1, 100]): # Criando e treinando o classificador SVM linear clf = svm.SVC(kernel='linear', C=C) clf.fit(X, y) # Plotando a fronteira de decisão Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) plt.subplot(1, 3, i + 1) plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.Paired) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired, edgecolors='k') # Destacando os vetores de suporte plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=200, facecolors='none', edgecolors='red', linewidth=2) plt.title(f'SVM Linear com C = {C}') plt.xlabel('Característica 1') plt.ylabel('Característica 2') plt.tight_layout() plt.show() |
O SVM e a Dança das Margens: Encontrando a Melhor Reta
O SVM não busca apenas qualquer linha; ele procura a mais inteligente. Ele desenha a reta que não só separa os grupos, mas que fique o mais distante possível dos elementos de cada lado. Essa distância é a “margem”. Os pontos na borda dela são os “vetores de suporte” — os dados críticos para a fronteira. Ao maximizar essa margem, o algoritmo cria uma zona de segurança que aumenta a generalização do modelo. Ele se torna mais robusto ao classificar novos dados. Portanto, o SVM busca a fronteira com maior proteção contra erros futuros.
|
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 |
import numpy as np import matplotlib.pyplot as plt from sklearn import svm from sklearn.datasets import make_circles # Gerando dados em formato de círculos concêntricos X, y = make_circles(n_samples=100, noise=0.1, factor=0.3, random_state=42) # Criando a malha para visualização x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5 y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # Configurando a figura plt.figure(figsize=(15, 4)) # Testando diferentes kernels kernels = ['linear', 'poly', 'rbf'] titles = ['Kernel Linear (Fracasso)', 'Kernel Polinomial (Tentativa)', 'Kernel RBF (Sucesso)'] for i, (kernel, title) in enumerate(zip(kernels, titles)): # Criando e treinando o SVM com o kernel especificado clf = svm.SVC(kernel=kernel, degree=3, gamma='scale', C=1) clf.fit(X, y) # Plotando a fronteira de decisão Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) plt.subplot(1, 3, i + 1) plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.Paired) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired, edgecolors='k') # Destacando os vetores de suporte plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=200, facecolors='none', edgecolors='red', linewidth=2) plt.title(title) plt.xlabel('Característica 1') plt.ylabel('Característica 2') plt.tight_layout() plt.show() |
O “Truque do Kernel”: Quando uma Reta Não é Suficiente
A vida real, porém, apresenta dados que não são linearmente separáveis. Surge então o “kernel trick”, a grande sacada do SVM. Em vez de desenhar uma curva complexa, o kernel faz uma transformação: projeta os dados para uma dimensão superior, adicionando uma nova perspectiva. Pontos em círculo no 2D podem ser separados por um plano no 3D. O “truque” é que essa mágica acontece sem cálculos custosos. A operação é implícita e eficiente. Por fim, ao projetar o plano de volta, ele se torna uma fronteira curva e precisa no espaço original.
|
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 |
import numpy as np import matplotlib.pyplot as plt from sklearn import svm from sklearn.datasets import make_circles from mpl_toolkits.mplot3d import Axes3D # 1. Gerando dados não-linearmente separáveis (círculos concêntricos) X, y = make_circles(n_samples=150, noise=0.1, factor=0.3, random_state=42) # 2. Criando e treinando o SVM com kernel RBF clf = svm.SVC(kernel='rbf', gamma=2, C=1) clf.fit(X, y) # 3. Criando uma malha para visualizar a fronteira de decisão 2D x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5 y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) # 4. Predizendo para cada ponto da malha Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) # 5. Plotando a fronteira de decisão 2D plt.figure(figsize=(15, 5)) plt.subplot(1, 3, 1) plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.Paired) plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired, edgecolors='k') plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=200, facecolors='none', edgecolors='red', linewidth=2, label='Vetores de Suporte') plt.title('SVM com Kernel RBF - Fronteira de Decisão') plt.xlabel('Característica 1') plt.ylabel('Característica 2') plt.legend() # 6. Visualizando o efeito do kernel trick: projeção em 3D # Adicionando uma terceira dimensão artificial para demonstrar o conceito # Na prática, o kernel RBF usa uma transformação mais complexa, mas esta é uma ilustração didática r = np.exp(-(X**2).sum(1)) # Transformação radial simples para visualização ax = plt.subplot(1, 3, 2, projection='3d') ax.scatter(X[:, 0], X[:, 1], r, c=y, s=50, cmap=plt.cm.Paired, edgecolors='k') ax.set_title('Dados Projetados em 3D\n(Visualização Didática)') ax.set_xlabel('X1') ax.set_ylabel('X2') ax.set_zlabel('Dimensão Extra') # 7. Plotando a superfície de decisão em 3D # Criando a malha 3D xx_3d, yy_3d = np.meshgrid(np.linspace(x_min, x_max, 30), np.linspace(y_min, y_max, 30)) zz_3d = np.exp(-(xx_3d**2 + yy_3d**2)) # A mesma transformação radial # Calculando a decisão para cada ponto da malha Z_3d = clf.decision_function(np.c_[xx_3d.ravel(), yy_3d.ravel()]) Z_3d = Z_3d.reshape(xx_3d.shape) ax2 = plt.subplot(1, 3, 3, projection='3d') ax2.plot_surface(xx_3d, yy_3d, zz_3d, facecolors=plt.cm.Paired(0.5 + 0.5 * (Z_3d > 0).astype(float)), alpha=0.5, rstride=1, cstride=1) ax2.scatter(X[:, 0], X[:, 1], np.exp(-(X**2).sum(1)), c=y, s=50, cmap=plt.cm.Paired, edgecolors='k') ax2.set_title('Superfície de Decisão no Espaço Transformado') ax2.set_xlabel('X1') ax2.set_ylabel('X2') ax2.set_zlabel('Dimensão Extra') plt.tight_layout() plt.show() # 8. Exibindo informações sobre o modelo print(f"Número de vetores de suporte: {len(clf.support_vectors_)}") print(f"Acurácia no treinamento: {clf.score(X, y):.2f}") |