Machine Learning: Máquinas de Vetores de Suporte
Última atualização: 2026-08-26
O SVM encontra a "rua mais larga" — empurrando a fronteira de decisão o mais longe possível de ambas as classes, tornando-a naturalmente mais robusta.
1. O que você vai aprender
- Classificador de margem máxima: vetores de suporte, fronteiras de margem, margem rígida vs. margem flexível (parâmetro C)
- Truque do kernel: intuição e estratégias de seleção para kernels linear/RBF/polinomial
- Regressão SVM (SVR): função de perda ε-insensível
- Por que a normalização importa: sensibilidade do SVM às escalas das features
- Segmentação de clientes do Charlie: classificando clientes do mercado europeu em níveis de valor com SVM
2. A história real de um analista de mercado
(1) O problema: fronteiras borradas nos níveis de valor dos clientes
Charlie é responsável pela segmentação de clientes no mercado europeu, dividindo os clientes em níveis de valor Alto/Médio/Baixo para alocar recursos de atendimento. Mas a fronteira entre Alto e Médio é difícil de traçar — usar uma única métrica (por exemplo, valor gasto) para classificar causa 20% dos clientes próximos à fronteira a serem frequentemente mal classificados. Uma fronteira linear não consegue capturar as relações complexas entre múltiplas dimensões do cliente.
(2) A solução com truque do kernel do SVM
O SVM usa o truque do kernel para mapear os dados em um espaço de dimensão superior, encontrando uma fronteira clara onde os dados antes eram inseparáveis.
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# O kernel RBF mapeia para um espaço de alta dimensão
pipe = Pipeline([
("scaler", StandardScaler()),
("svm", SVC(kernel="rbf", C=1.0, gamma="scale")),
])
pipe.fit(X_train, y_train)
print(f"Acurácia: {pipe.score(X_test, y_test):.4f}")
(3) O resultado: a precisão em clientes de fronteira melhorou de 65% para 89%
Depois de substituir a classificação linear por SVM-RBF, Charlie viu a acurácia de classificação para clientes de fronteira saltar de 65% para 89%, reduzindo a alocação incorreta de recursos de atendimento em 40%.
3. Classificador de margem máxima
(1) Ideia central do SVM
O SVM encontra o hiperplano de separação que maximiza a margem. Apenas as amostras que estão nas fronteiras da margem (vetores de suporte) determinam o resultado da classificação.
graph TB
INPUT[Dados de Entrada] --> MAP[Mapeamento por Kernel<br/>Baixa-D → Alta-D]
MAP --> HYPER[Encontrar Hiperplano<br/>de Margem Máxima]
HYPER --> SV[Vetores de Suporte<br/>Definem a fronteira]
SV --> MARGIN[Largura da Margem<br/>Maior = Mais Robusto]
MARGIN --> CLASSIFY[Classificação<br/>Função de Decisão]
▶ Exemplo: Visualização do SVM linear
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
X, y = make_blobs(n_samples=100, centers=2, random_state=42, cluster_std=1.5)
# Treinar SVM com kernel linear
svm = SVC(kernel="linear", C=1.0)
svm.fit(X, y)
fig, ax = plt.subplots(figsize=(8, 6))
# Plotar pontos de dados
ax.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr", s=30, edgecolors="black")
# Plotar fronteira de decisão e margens
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = svm.decision_function(xy).reshape(XX.shape)
ax.contour(XX, YY, Z, colors="k", levels=[-1, 0, 1], alpha=0.5, linestyles=["--", "-", "--"])
# Destacar vetores de suporte
ax.scatter(svm.support_vectors_[:, 0], svm.support_vectors_[:, 1],
s=100, facecolors="none", edgecolors="k", linewidths=2)
ax.set_title(f"SVM Linear (C={svm.C})\nVetores de suporte: {len(svm.support_vectors_)}")
plt.tight_layout()
plt.savefig("svm_linear.png", dpi=150)
Saída:
# Executado com sucesso
(2) Margem rígida vs. margem flexível (parâmetro C)
| Valor de C | Margem | Tolerância a erros | Risco de sobreajuste | Caso de uso |
|---|---|---|---|---|
| Grande (100+) | Estreita | Quase nenhuma | Alto | Dados quase linearmente separáveis |
| Médio (1) | Moderada | Alguma tolerância | Baixo | Casos gerais |
| Pequeno (0,01) | Larga | Alta tolerância | Baixo | Dados ruidosos/com sobreposição |
▶ Exemplo: Comparando o efeito do parâmetro C
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
from sklearn.model_selection import cross_val_score
import numpy as np
X, y = make_blobs(n_samples=200, centers=2, random_state=42, cluster_std=2.5)
for C in [0.01, 0.1, 1.0, 10.0, 100.0]:
svm = SVC(kernel="rbf", C=C, gamma="scale")
scores = cross_val_score(svm, X, y, cv=5, scoring="accuracy")
svm.fit(X, y)
print(f"C={C:6.2f}: Acurácia={scores.mean():.3f} +/- {scores.std():.3f}, "
f"Vetores de suporte={len(svm.support_vectors_)}")
Saída:
# Executado com sucesso
4. O truque do kernel
(1) Escolhendo uma função de kernel
▶ Exemplo: Comparando diferentes funções de kernel
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
# Dados não lineares (luas)
X_moons, y_moons = make_moons(n_samples=500, noise=0.2, random_state=42)
kernels = {
"Linear": SVC(kernel="linear", C=1),
"Polinomial (grau=3)": SVC(kernel="poly", degree=3, C=1),
"RBF": SVC(kernel="rbf", C=1, gamma="scale"),
}
for name, svm in kernels.items():
pipe = Pipeline([("scaler", StandardScaler()), ("svm", svm)])
scores = cross_val_score(pipe, X_moons, y_moons, cv=5, scoring="accuracy")
print(f"{name:20s}: Acurácia={scores.mean():.3f}")
Saída:
# Executado com sucesso
| Kernel | Fórmula | Caso de uso | Parâmetros-chave |
|---|---|---|---|
| Linear | $\langle x, x' \rangle$ | Alta dimensionalidade, separável linearmente | C |
| RBF | $e^{-\gamma|x-x'|^2}$ | Uso geral, não linear | C, gamma |
| Polinomial | $(\langle x, x' \rangle + c)^d$ | Interações entre features | C, degree, coef0 |
| Sigmoid | $\tanh(\gamma\langle x, x' \rangle + c)$ | Semelhante a rede neural | C, gamma, coef0 |
(2) O parâmetro Gamma
▶ Exemplo: Efeito do Gamma no kernel RBF
from sklearn.svm import SVC
from sklearn.datasets import make_moons
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
X, y = make_moons(n_samples=500, noise=0.15, random_state=42)
for gamma in [0.01, 0.1, 1.0, 10.0, "scale", "auto"]:
pipe = Pipeline([
("scaler", StandardScaler()),
("svm", SVC(kernel="rbf", C=1, gamma=gamma)),
])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"gamma={str(gamma):6s}: Acurácia={scores.mean():.3f}")
Saída:
# Executado com sucesso
| Gamma | Fronteira de decisão | Efeito |
|---|---|---|
| Muito pequeno (0,01) | Suave | Subajuste |
| Moderado (1) | Moderadamente curva | Geralmente ótimo |
| Muito grande (10+) | Altamente irregular | Sobreajuste |
| "scale" | 1/(n_features * X.var()) | Padrão do sklearn |
| "auto" | 1/n_features | Padrão legado |
5. Por que a normalização importa e SVR
(1) Sensibilidade do SVM às escalas das features
▶ Exemplo: Impacto da normalização no SVM
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
# Sem normalização
svm_raw = SVC(kernel="rbf", C=1, gamma="scale")
scores_raw = cross_val_score(svm_raw, X, y, cv=5, scoring="accuracy")
# Com normalização
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=1, gamma="scale"))])
scores_scaled = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"Sem normalização: {scores_raw.mean():.4f}")
print(f"Com normalização: {scores_scaled.mean():.4f}")
Saída:
# Executado com sucesso
(2) Regressão SVR
▶ Exemplo: Previsão de vendas com SVR
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np
rng = np.random.default_rng(42)
X = rng.uniform(0, 100, (200, 3))
y = 50 + 0.8 * X[:, 0] + rng.normal(0, 5, 200)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# SVR com kernel RBF
pipe = Pipeline([
("scaler", StandardScaler()),
("svr", SVR(kernel="rbf", C=100, epsilon=5)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"SVR R²: {r2_score(y_test, y_pred):.4f}")
print(f"SVR MAE: {mean_absolute_error(y_test, y_pred):.2f}")
Saída:
# Executado com sucesso
| Parâmetro | Significado | Efeito |
|---|---|---|
| C | Força da regularização | C grande → menor tolerância a erros → possível sobreajuste |
| epsilon (ε) | Largura da banda insensível | ε grande → mais pontos ignorados → ajuste mais suave |
| kernel | Tipo de função de kernel | Mesmo que o SVM de classificação |
6. Segmentação de valor de clientes europeus do Charlie
▶ Exemplo: Projeto completo de classificação com SVM
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import classification_report
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 2000
df = pd.DataFrame({
"annual_spending_eur": rng.exponential(5000, n),
"purchase_frequency": rng.poisson(8, n),
"avg_order_value_eur": rng.exponential(150, n),
"tenure_months": rng.integers(1, 60, n),
"support_tickets": rng.poisson(3, n),
})
# Lógica de nível de valor (baseado em EUR)
score = (df["annual_spending_eur"] / 5000 * 0.3
+ df["purchase_frequency"] / 20 * 0.25
+ df["avg_order_value_eur"] / 200 * 0.2
+ df["tenure_months"] / 60 * 0.15
+ rng.normal(0, 0.1, n))
df["tier"] = pd.cut(score, bins=[0, 0.3, 0.6, 1.5], labels=["Baixo", "Médio", "Alto"])
X = df.drop(columns=["tier"])
y = df["tier"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# Grid search para os melhores parâmetros do SVM
pipe = Pipeline([("scaler", StandardScaler()), ("svm", SVC())])
param_grid = {
"svm__kernel": ["linear", "rbf"],
"svm__C": [0.1, 1, 10],
"svm__gamma": ["scale", 0.1, 1],
}
grid = GridSearchCV(pipe, param_grid, cv=3, scoring="accuracy", n_jobs=-1)
grid.fit(X_train, y_train)
print(f"Melhores parâmetros: {grid.best_params_}")
print(f"Melhor acurácia na CV: {grid.best_score_:.4f}")
print(f"\nRelatório no teste:\n{classification_report(y_test, grid.predict(X_test))}")
Saída:
# Executado com sucesso
❓ Perguntas Frequentes
P: Por que os dados do SVM precisam ser normalizados? R: O SVM calcula margens com base em métricas de distância. Se a feature A variar de 0 a 1 e a feature B variar de 0 a 1.000.000, B dominará completamente o cálculo da margem. O StandardScaler garante que todas as features contribuam igualmente.
P: Para o kernel RBF, o que importa mais — gamma ou C? R: Ambos são importantes e interagem entre si. Use GridSearchCV para pesquisá-los em conjunto. Intervalos de busca típicos: C=[0,1, 1, 10, 100], gamma=[0,001, 0,01, 0,1, 1, "scale"].
P: SVM é adequado para grandes conjuntos de dados? R: Não muito. A complexidade de treino do SVM é O(n²) a O(n³), tornando-o muito lento além de 100 mil amostras. Para grandes conjuntos de dados, use LinearSVC (kernel linear) ou Random Forest/XGBoost.
P: SVM consegue gerar probabilidades? R: Não por padrão. Definir probability=True treina um modelo adicional de Platt Scaling para gerar probabilidades, mas o treino fica mais lento e as probabilidades não são tão bem calibradas quanto as da regressão logística.
P: Qual a diferença entre um kernel linear e a regressão logística? R: Ambos são classificadores lineares. A diferença é que o SVM maximiza a margem (focando apenas nas amostras da fronteira), enquanto a regressão logística maximiza a verossimilhança (considerando todas as amostras). SVM é mais robusto perto da fronteira; a regressão logística gera probabilidades melhor calibradas.
P: Como escolher o parâmetro epsilon para SVR? R: Epsilon é a largura da banda insensível — erros menores que ε não são contabilizados na perda. Um valor típico é uma estimativa do nível de ruído da variável alvo. Por exemplo, se os erros de previsão de vendas estão dentro de ±5k USD, defina ε=5.
📖 Resumo
- O SVM encontra o hiperplano de margem máxima; apenas os vetores de suporte definem a fronteira — todas as outras amostras não têm influência
- O parâmetro C controla a compensação entre largura da margem e erros de classificação: C grande → margem estreita, menos erros; C pequeno → margem larga, mais tolerância
- O truque do kernel resolve a classificação não linear mapeando os dados para um espaço de dimensão superior; o kernel RBF é a primeira escolha padrão
- Gamma controla o alcance da influência do kernel RBF: gamma pequeno → fronteira suave, gamma grande → sobreajuste
- SVM exige normalização das features — as métricas de distância são extremamente sensíveis às escalas
- SVR usa perda ε-insensível para regressão; erros de previsão menores que ε não são penalizados
📝 Exercícios
- Básico (Dificuldade ⭐): Use SVC(kernel="rbf") para classificar o conjunto de dados make_moons. Compare a acurácia com e sem StandardScaler. Dica: Pipeline vs. fit direto.
- Intermediário (Dificuldade ⭐⭐): Use GridSearchCV para procurar os parâmetros ótimos do SVM (kernel, C, gamma) no conjunto de dados Iris e encontre a melhor combinação. Dica: SVC dentro de um Pipeline + param_grid.
- Desafio (Dificuldade ⭐⭐⭐): Reproduza a segmentação de clientes do Charlie — gere dados de clientes com 3 classes, compare os relatórios de classificação de SVM/LogisticRegression/RandomForest, e analise qual modelo funciona melhor para cenários multiclasse com fronteiras borradas. Dica: use classification_report para comparar precisão/revocação/f1.
← Anterior: Árvores de Decisão e Florestas Aleatórias | Próximo: KNN e Agrupamento →