Skip to content
import os
import io
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

import numpy as np
import pandas as pd
from scipy import optimize
import matplotlib.pyplot as plt


# ===== CSV loader =====
RAW_URL = "https://raw.githubusercontent.com/marcelademartini/Machine-Learning-1/main/Testing.csv"
LOCAL_CANDIDATES = [
    "Testing.csv",
    os.path.join("docs", "svm", "Testing.csv"),
    os.path.join("docs", "Testing.csv"),
]

def fetch_csv_with_retries(url, local_candidates=None, retries=3, timeout=10):
    session = requests.Session()
    retry = Retry(total=retries, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504])
    session.mount("https://", HTTPAdapter(max_retries=retry))
    headers = {"User-Agent": "Mozilla/5.0"}
    try:
        resp = session.get(url, headers=headers, timeout=timeout)
        resp.raise_for_status()
        return pd.read_csv(io.StringIO(resp.text))
    except Exception:
        if local_candidates:
            for p in local_candidates:
                if os.path.exists(p):
                    return pd.read_csv(p)
        raise RuntimeError(f"Failed to load CSV from '{url}' and no local fallback found. Place 'Testing.csv' in the repo.")

df = fetch_csv_with_retries(RAW_URL, local_candidates=LOCAL_CANDIDATES)

# ===========================
# 1. Selecionar variáveis (features) e alvo (target)
# ===========================

numeric = df.select_dtypes(include=[np.number])
if numeric.shape[1] < 2:
    raise RuntimeError("Need at least two numeric features for this SVM demo. Put Testing.csv with numeric columns in the repo.")

X = numeric.iloc[:, :2].values  
y_raw = df.iloc[:, -1].values  

# Converter rótulos para binário {-1, +1}
unique_values = np.unique(y_raw)
if len(unique_values) != 2:
    raise ValueError("The SVM demo requires exactly two classes in the target column.")
y = np.where(y_raw == unique_values[0], -1, 1).astype(float)

# ===========================
# 2. Kernel RBF
# ===========================
def rbf_kernel(x1, x2, sigma=1.0):
    return np.exp(-np.linalg.norm(x1 - x2)**2 / (2.0 * sigma**2))

def kernel_matrix(X, kernel, sigma=1.0):
    n = X.shape[0]
    K = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            K[i, j] = kernel(X[i], X[j], sigma)
    return K

sigma = 1.0
K = kernel_matrix(X, rbf_kernel, sigma)

# ===========================
# 3. Otimização dual (com restrições de caixa 0 <= alpha <= C)
# ===========================
C = 1.0  # parâmetro soft-margin
P = np.outer(y, y) * K

def objective(alpha):
    return 0.5 * np.dot(alpha, P.dot(alpha)) - np.sum(alpha)

def constraint(alpha):
    return np.dot(alpha, y)

cons = {'type': 'eq', 'fun': constraint}
bounds = [(0.0, C) for _ in range(len(y))]
alpha0 = np.zeros(len(y))

res = optimize.minimize(objective, alpha0, method='SLSQP', bounds=bounds, constraints=cons, options={'maxiter':1000})
if not res.success:
    print("Warning: optimization did not converge:", res.message)
alpha = np.clip(res.x, 0.0, C)

# ===========================
# 4. Support vectors e bias
# ===========================
sv_threshold = 1e-6
sv_idx = alpha > sv_threshold
sv_indices = np.where(sv_idx)[0]
if sv_indices.size == 0:
    raise RuntimeError("No support vectors found. Try increasing C or check data.")

# calcular o viés b como a média sobre os vetores de suporte
b_vals = []
for i in sv_indices:
    b_i = y[i] - np.sum(alpha * y * K[:, i])
    b_vals.append(b_i)
b = float(np.mean(b_vals))

# ===========================
# 5. Função de decisão / predição
# ===========================
def decision_function(x):
    kx = np.array([rbf_kernel(x, xi, sigma=sigma) for xi in X])
    return np.dot(alpha * y, kx) + b

def predict_label(x):
    return np.sign(decision_function(x))

# ===========================
# 6. Plot decision boundary + support vectors
# ===========================
step = 0.05
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, step), np.arange(y_min, y_max, step))

grid_points = np.c_[xx.ravel(), yy.ravel()]
Z = np.array([decision_function(pt) for pt in grid_points])
Z = Z.reshape(xx.shape)

plt.figure(figsize=(8, 6))
plt.contourf(xx, yy, Z, levels=[-np.inf, 0, np.inf], colors=['#FFDDDD', '#DDDDFF'], alpha=0.8)
plt.contour(xx, yy, Z, levels=[0], colors='k', linestyles='--')

plt.scatter(X[y == -1, 0], X[y == -1, 1], c='red', label=f'Classe {unique_values[0]} (-1)')
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', label=f'Classe {unique_values[1]} (+1)')

plt.scatter(X[sv_idx, 0], X[sv_idx, 1], s=100, facecolors='none', edgecolors='k', label='Support Vectors')

plt.xlabel(numeric.columns[0])
plt.ylabel(numeric.columns[1])
plt.title('SVM com Kernel RBF (dataset Testing.csv)')
plt.legend()
plt.tight_layout()
plt.show()

alt text

Projeto: SVM com Kernel RBF — Explicação Completa do Código

Este documento apresenta uma explicação organizada do código utilizado para treinar um SVM com Kernel RBF, seguindo os critérios exigidos pelo Projeto Integrador.


1. Exploração dos Dados

O código inicia com o carregamento do arquivo Testing.csv, obtido preferencialmente do GitHub. Caso não seja possível, ele tenta automaticamente carregar versões locais do arquivo. Isso garante resiliência e confiabilidade no processo de leitura.

Após o carregamento, o script:

  • Seleciona apenas colunas numéricas, necessárias para o treino do SVM.
  • Utiliza as duas primeiras variáveis numéricas como features (X).
  • Usa a última coluna como target (y).
  • Verifica se o target possui exatamente duas classes, condição obrigatória para o SVM binário.

Essa etapa permite compreender a composição do dataset e garante que ele esteja estruturado corretamente para os passos seguintes.


2. Pré-processamento

Nesta etapa, o script realiza:

Seleção de variáveis numéricas

Garante que apenas dados contínuos sejam utilizados.

Conversão dos rótulos

Os valores do target são convertidos para {-1, +1}, conforme a formulação matemática do SVM.

Verificações de integridade

Confirma que há pelo menos duas features numéricas e que a classificação é binária.

Normalização

O código não normaliza explicitamente as features (ex.: StandardScaler), mas o Kernel RBF suaviza parcialmente essa necessidade.
Sugestão de melhoria: adicionar normalização.


3. Divisão dos Dados

O código não divide o dataset em treino e teste.
Todo o conjunto é utilizado para ajustar o modelo e gerar a figura da fronteira de decisão.

Embora isso funcione para fins didáticos e visualização, recomenda-se incluir divisão treino/teste em aplicações reais.


4. Treinamento do Modelo — SVM Dual com Kernel RBF

O treinamento é feito manualmente, implementando a forma dual do SVM.

4.1 Kernel RBF

A função de kernel gaussiano é definida como:

\[ K(x_i, x_j) = e^{-\frac{\|x_i - x_j\|^2}{2\sigma^2}} \]

4.2 Construção da Matriz Kernel

A matriz K é calculada comparando cada par de observações, gerando um mapa de similaridade.

4.3 Otimização Dual

O problema dual do SVM é resolvido com restrições:

  • \(0 \le \alpha_i \le C\)
  • \(\sum \alpha_i y_i = 0\)

Utiliza-se scipy.optimize.minimize com o método SLSQP.

4.4 Vetores de Suporte

Os pontos com \(\alpha > 1e^{-6}\) são identificados como support vectors, elementos essenciais para definir a fronteira.

4.5 Cálculo do Bias (b)

O termo b é calculado usando a média dos erros nos vetores de suporte.


5. Avaliação do Modelo

O modelo é avaliado de forma visual através de:

  • Regiões de decisão coloridas
  • Fronteira de decisão pontilhada (linha Z=0)
  • Plot das classes -1 e +1
  • Destacando os vetores de suporte com círculos sem preenchimento

Embora não utilize métricas numéricas, o gráfico demonstra claramente a separação não linear obtida pelo kernel RBF.

Sugestão: incluir acurácia, F1-score e matriz de confusão para avaliação quantitativa (treino vs teste).


6. Relatório Final — Conclusões e Melhorias

O código fornece:

  • A implementação completa do SVM Dual com Kernel RBF.
  • Identificação dos vetores de suporte.
  • Uma visualização clara da fronteira de decisão.
  • Funções de decisão e predição.

Possíveis melhorias para o Projeto Integrador:

  1. Adicionar estatísticas descritivas (média, mediana, histograma).
  2. Realizar normalização das features.
  3. Inserir divisão treino/teste.
  4. Testar diferentes valores de C e sigma.
  5. Implementar métricas numéricas para avaliar performance.
  6. Comparar com o SVM do Scikit-Learn.