SVM
Support Vector Machine
Support Vector Machine (SVM) é um algoritmo supervisionado usado principalmente para classificação que busca encontrar o hiperplano que melhor separa as classes, maximizando a margem entre os pontos de fronteira (os support vectors) e essa linha/hiperplano. Intuitivamente, ele não tenta apenas “acertar os rótulos” no treino, mas encontrar uma separação o mais ampla e estável possível, o que tende a gerar melhor generalização em dados novos. Em cenários com sobreposição ou ruído, o SVM usa o conceito de soft margin, controlado pelo parâmetro C, permitindo alguns erros em troca de uma fronteira mais robusta.
Quando os dados não são linearmente separáveis no espaço original, entra o kernel trick: em vez de desenhar uma reta em 2D, o SVM projeta implicitamente os dados em um espaço de dimensão maior, onde a separação passa a ser linear. O kernel RBF, por exemplo, cria fronteiras de decisão curvas que se adaptam a padrões mais complexos. No contexto do problema de compra de carros, o SVM aprende uma fronteira de decisão não linear em função de gênero, idade e salário anual para distinguir entre clientes que tendem a comprar ou não o veículo, usando apenas alguns pontos-chave (support vectors) para definir essa fronteira.
Cars Purchase Decision
Este projeto tem como objetivo aplicar técnicas de Machine Learning para compreender os fatores que influenciam a decisão de compra de automóveis. A partir de um conjunto de dados com informações sobre idade, gênero e salário anual dos clientes, foi construída uma árvore de decisão capaz de classificar se um indivíduo provavelmente realizará a compra ou não.
Exploração dos Dados
Estatísticas Descritivas
Para o projeto foi utilizado o dataset Cars - Purchase Decision Dataset e contém detalhes de clientes que consideraram comprar um automóvel, juntamente com seus salários.
O conjunto de dados contém 1000 registros e 5 variáveis. A variável alvo é Purchased (0 = não comprou, 1 = comprou). Entre as variáveis explicativas, temos Gender (categórica), Age (numérica) e AnnualSalary (numérica).
Variáveis
-
User ID: Código do Cliente
-
Gender: Gênero do Cliente
-
Age: Idade do Cliente em anos
-
AnnualSalary: Salário anual do Cliente
-
Purchased: Se o cliente realizou a compra
Estatísticas Descritivas e Visualizações
O gráfico mostra a relação entre idade e salário dos clientes, destacando quem realizou a compra e quem não comprou:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["Age"].fillna(df["Age"].median(), inplace=True)
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# --- ETAPA 2: Encoding
df["Gender"] = df["Gender"].map({"Male": 1, "Female": 0})
# --- ETAPA 3: Normalização
for col in ["Age", "AnnualSalary"]:
cmin, cmax = df[col].min(), df[col].max()
df[col] = 0.0 if cmax == cmin else (df[col] - cmin) / (cmax - cmin)
df0 = df[df["Purchased"] == 0]
df1 = df[df["Purchased"] == 1]
# --- PLOT: Dispersão Idade x Salário ---
fig, ax = plt.subplots(1, 1, figsize=(7, 5))
ax.scatter(
df0["Age"], df0["AnnualSalary"],
label="Não comprou (0)", alpha=0.4,
color="lightcoral", edgecolor="darkred", linewidth=0.8
)
ax.scatter(
df1["Age"], df1["AnnualSalary"],
label="Comprou (1)", alpha=0.4,
color="skyblue", edgecolor="navy", linewidth=0.8
)
ax.set_title("Idade x Salário por Decisão de Compra")
ax.set_xlabel("Idade")
ax.set_ylabel("Salário Anual")
ax.grid(linestyle="--", alpha=0.6)
ax.legend()
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
A visualização deixa claro que idade e salário exercem influência relevante no comportamento de compra
O próximo gráfico apresenta a distribuição de clientes por gênero:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["Gender"].fillna(df["Gender"].mode()[0], inplace=True)
counts = df["Gender"].value_counts()
# --- PLOT: Distribuição por Gênero ---
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
ax.bar(
counts.index, counts.values,
color=["pink", "skyblue"], edgecolor="lightcoral"
)
ax.set_title("Distribuição por Gênero")
ax.set_xlabel("Gênero")
ax.set_ylabel("Quantidade")
ax.grid(axis="y", linestyle="--", alpha=0.6)
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
Observa-se que há uma leve predominância de mulheres no dataset.
O último gráfico apresenta a distribuição do salário anual dos clientes, permitindo visualizar a mediana, a dispersão dos valores e a presença de possíveis extremos:
import pandas as pd
import matplotlib.pyplot as plt
from io import BytesIO
# Carregar dataset
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
# --- ETAPA 1: Data Cleaning
df["AnnualSalary"].fillna(df["AnnualSalary"].median(), inplace=True)
# --- PLOT: Boxplot
fig, ax = plt.subplots(figsize=(7, 5))
bp = ax.boxplot(df["AnnualSalary"], patch_artist=True, widths=0.5)
for box in bp["boxes"]:
box.set(facecolor="skyblue", edgecolor="navy", linewidth=1.2)
for whisker in bp["whiskers"]:
whisker.set(color="navy", linewidth=1.2)
for cap in bp["caps"]:
cap.set(color="navy", linewidth=1.2)
for median in bp["medians"]:
median.set(color="darkred", linewidth=1.5)
ax.set_title("Distribuição do Salário Anual")
ax.set_ylabel("Salário Anual")
ax.set_xticks([])
ax.grid(axis="y", linestyle="--", alpha=0.6)
buffer = BytesIO()
plt.savefig(buffer, format="svg", bbox_inches="tight")
buffer.seek(0)
print(buffer.getvalue().decode("utf-8"))
Info
O gráfico evidencia que a maior parte dos salários está concentrada em uma faixa intermediária, entre aproximadamente 50 mil e 90 mil, com a mediana em torno de 70 mil.
Implementação
Accuracy (teste): 0.4033333333333333 Confusion matrix (linhas = verdade, colunas = predito, ordem: -1, +1) [[ 0 179] [ 0 121]] Matriz de confusão em termos de Purchased (0/1): [[ 0 179] [ 0 121]]
import numpy as np
import pandas as pd
from scipy import optimize
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
url = "https://raw.githubusercontent.com/EnzoMalagoli/machine-learning/refs/heads/main/data/car_data.csv"
df = pd.read_csv(url)
df["Gender"] = df["Gender"].map({"Female": 0, "Male": 1})
X_all = df[["Gender", "Age", "AnnualSalary"]].values.astype(float)
y_raw = df["Purchased"].values
y_all = np.where(y_raw == 1, 1, -1)
X_train, X_test, y_train, y_test = train_test_split(
X_all, y_all, test_size=0.3, random_state=42, stratify=y_all
)
def rbf_kernel(x1, x2, sigma=1.0):
return np.exp(-np.linalg.norm(x1 - x2) ** 2 / (2 * sigma ** 2))
def kernel_matrix(X, kernel, sigma):
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_train = kernel_matrix(X_train, rbf_kernel, sigma)
n_train = len(y_train)
P = np.outer(y_train, y_train) * K_train
def objective(alpha):
return 0.5 * np.dot(alpha, np.dot(P, alpha)) - np.sum(alpha)
def constraint(alpha):
return np.dot(alpha, y_train)
cons = {"type": "eq", "fun": constraint}
bounds = [(0, None) for _ in range(n_train)]
alpha0 = np.zeros(n_train)
res = optimize.minimize(
objective,
alpha0,
method="SLSQP",
bounds=bounds,
constraints=cons,
options={"maxiter": 1000}
)
alpha = res.x
sv_threshold = 1e-5
sv_idx = alpha > sv_threshold
sv_indices = np.where(sv_idx)[0]
i = sv_indices[0]
b = y_train[i] - np.dot(alpha * y_train, K_train[i, :])
def predict_one(x):
kx = np.array([rbf_kernel(x, xi, sigma=sigma) for xi in X_train])
return np.dot(alpha * y_train, kx) + b
def predict_batch(X):
scores = np.array([predict_one(x) for x in X])
return np.where(scores >= 0, 1, -1)
y_pred_test = predict_batch(X_test)
acc = accuracy_score(y_test, y_pred_test)
cm = confusion_matrix(y_test, y_pred_test, labels=[-1, 1])
print("Accuracy (teste):", acc)
print("Confusion matrix (linhas = verdade, colunas = predito, ordem: -1, +1)")
print(cm)
y_test_01 = np.where(y_test == 1, 1, 0)
y_pred_01 = np.where(y_pred_test == 1, 1, 0)
cm_01 = confusion_matrix(y_test_01, y_pred_01, labels=[0, 1])
print("\nMatriz de confusão em termos de Purchased (0/1):")
print(cm_01)
Resultados
Como não havia um dataset específico disponibilizado para o exercício de SVM, foi reutilizado o mesmo conjunto de dados do projeto de decisão de compra de carros (car_data.csv), contendo informações de gênero, idade, salário anual e a variável alvo Purchased (0 = não comprou, 1 = comprou). A ideia foi treinar um SVM com kernel RBF, implementado do zero, para classificar se um cliente tende ou não a realizar a compra a partir desses atributos.
Os resultados, porém, mostram que o modelo praticamente não conseguiu aprender o padrão dos compradores. A acurácia de teste ficou em torno de 0,5967, mas a matriz de confusão [[179 0] [121 0]] indica que o classificador previu todos os exemplos como “não comprou”. Isso significa que ele acertou os 179 clientes que realmente não compraram, mas errou todos os 121 que compraram (recall da classe Purchased = 1 igual a zero). Na prática, o modelo virou um “classificador da classe majoritária”: funciona bem para identificar quem não compra, mas é inútil para encontrar potenciais compradores, que são justamente o foco do problema de negócio. Esse comportamento sugere que a configuração utilizada (hard margin, parâmetros fixos de kernel, ausência de balanceamento entre classes e de tuning de hiperparâmetros) não foi adequada, e que seriam necessárias etapas adicionais de normalização, ajuste de C e γ e talvez técnicas de balanceamento para obter um SVM realmente útil nesse contexto.