SUMÁRIO

🗒️ Resumo

🗒️ 1. Introdução

🗒️ 2. Fundamentação teórica

🗒️ 3. Primeiros passos Hands-on

🗒️ 4. Aplicações com SVM

🗒️ 5. Outros tipos de Modelos de Vetores de Suporte

🗒️ 6. Exercícios

🗒️ Apêndice


4.2 Aplicações com SVM em Linguagem Python

Este capítulo tem o objetivo de reproduzir as mesmas análises feitas em R utilizando a linguagem Python. Dessa forma, serão omitidos maiores detalhes apresentados na seção anterior. Para acessar essas informações, basta retornar ao capítulo passado.

Análise Exploratória:

O primeiro passo é carregar a base de dados utilizando pandas:

import pandas as pd

# Lendo a base de dados
banknote = pd.read_csv("data_banknote_authentication.txt", header=None)
banknote.columns = ['V1', 'V2', 'V3', 'V4', 'V5']

Obtém-se uma descrição geral da base de dados e realiza-se as modificações sobre a base de dados original, a partir do código:

# Obtendo uma descricao geral da base de dados:
summary = banknote.describe()

summary
##                 V1           V2           V3           V4           V5
## count  1372.000000  1372.000000  1372.000000  1372.000000  1372.000000
## mean      0.433735     1.922353     1.397627    -1.191657     0.444606
## std       2.842763     5.869047     4.310030     2.101013     0.497103
## min      -7.042100   -13.773100    -5.286100    -8.548200     0.000000
## 25%      -1.773000    -1.708200    -1.574975    -2.413450     0.000000
## 50%       0.496180     2.319650     0.616630    -0.586650     0.000000
## 75%       2.821475     6.814625     3.179250     0.394810     1.000000
## max       6.824800    12.951600    17.927400     2.449500     1.000000

Ajustando a variável y, tem-se:

# Modificando a última coluna como um fator e renomeando-a.
banknote['V5'] = banknote['V5'].astype('category')
banknote = banknote.rename(columns={'V5': 'y'})

summary = banknote.describe()
print(summary)
##                 V1           V2           V3           V4
## count  1372.000000  1372.000000  1372.000000  1372.000000
## mean      0.433735     1.922353     1.397627    -1.191657
## std       2.842763     5.869047     4.310030     2.101013
## min      -7.042100   -13.773100    -5.286100    -8.548200
## 25%      -1.773000    -1.708200    -1.574975    -2.413450
## 50%       0.496180     2.319650     0.616630    -0.586650
## 75%       2.821475     6.814625     3.179250     0.394810
## max       6.824800    12.951600    17.927400     2.449500

Reproduzindo as análises do pairs e das matrizes de correlações lineares.

# Importando pacotes e funções gráficas.
import seaborn as sns
import matplotlib.pyplot as plt

# Plotando o "pairs()"
sns.pairplot(data=banknote, hue='y', diag_kind='hist')

# Se estiver utilizando JupyterNotebook para exibir o plot utilzar: plt.show()
# Calculando a matriz de correlação.
cor_m = banknote.iloc[:, 0:4].corr()

# Criando uma visualizacao
plt.figure(figsize=(8, 6))
sns.heatmap(cor_m, annot=True, cmap='coolwarm', fmt=".2f", cbar=True, center=0)
plt.title("Matriz de Correlação Linear")
plt.show()

Validação Cruzada (VC):

Para a validação cruzada (também conhecida como cross-validation, CV), será utilizado o holdout-repetido. A estrutura com todas as repetições e todos os conjuntos de dados será criada utilizando um vetor de dicionários.

import numpy as np

# Definindo a semente
np.random.seed(42)

# Definindo parametros da VC 
n_rep = 30
cv = []

for i in range(n_rep):
    train_index = np.random.choice(banknote.index, size=round(0.7 * len(banknote)), replace=False)
    cv.append({'train_set': banknote.loc[train_index], 'test_set': banknote.drop(train_index)})

Utilizando o pacote ‘sklearn.svm’

Conforme mencionado na seção três, ao trabalhar com modelos SVM em Python utilizaremos o pacote “sklearn”, que oferece uma ampla gama de modelos de aprendizado estatístico de máquina já implementados.

# Lembre-se sempre caso nao tenha o pacote instalado use
# 'pip install {package-name}'

from sklearn.svm import SVC

# Supondo que 'cv' já esteja definido como uma lista de dicionários contendo 'train_set' e 'test_set'.
train_set = cv[0]['train_set']
y_train = train_set['y']
X_train = train_set.drop(columns=['y'])

# Gerando o modelo SVM
svm_mod = SVC()
svm_mod = svm_mod.fit(X_train, y_train)

Para verificar as especificações utilizadas na versão default do SVC() do sklearn, utilizaremos os atributos .kernel, .C e ._gamma.

svm_mod.kernel # Função de kernel utilizada.
## 'rbf'
svm_mod.C # Parâmetro de custo utilizado.
## 1.0
svm_mod._gamma # Hiperparâmetro calculado na utilização da função de kernel.
## 0.013766170015082227

Para obter mais detalhes sobre como o valor de \(\gamma\) é calculado, basta consultar a documentação oficial do scikit-learn. De forma sucinta, caso esse valor não seja especificado, é utilizado o valor 1/(n_features * X.var()). A variável \(\gamma\) é equivalente ao \(\sigma\) do rbfdot, pertencente ao pacote kernlab. Maiores detalhes sobre as funções de kernel também se encontram na documentação.

Predizendo os valores para novas observações da amostra teste, teríamos:

# Assumindo que a VC (validação cruzada) e o svm_mod (modelo SVM) foram definidos anteriormente:
test_set = cv[0]['test_set']
y_test = test_set['y']
X_test = test_set.drop(columns=['y'])

# Realizando as predições no conjunto de teste (test set).
y_test_hat = svm_mod.predict(X_test)

E verificando o desempenho:

# Criando a matriz de confusão para uma rápida verificação de desempenho.
confusion_matrix = pd.crosstab(y_test, y_test_hat, rownames=['obs'], colnames=['pred'])

print(confusion_matrix)
## pred    0    1
## obs           
## 0     216    1
## 1       0  195

Pode-se ver que o resultado do modelo produziu um resultado satisfatório. No entanto, é necessário verificar para as outras amostras de teste e treinamento e sumarizar o resultado.

from sklearn.metrics import accuracy_score, f1_score

# Criando vetores de ACC e F1-score
acc_default = np.zeros(n_rep)
f1_default = np.zeros(n_rep)
svm_mod = SVC()

for k in range(30):

      # Ajustando o modelo SVM:
      train_set = cv[k]['train_set']
      y_train = train_set['y']
      X_train = train_set.drop(columns=['y'])
      # 
      svm_mod = svm_mod.fit(X_train, y_train)
      
      # Predizendo sobre a amostra test
      test_set = cv[k]['test_set']
      y_test = test_set['y']
      X_test = test_set.drop(columns=['y'])
      
      y_test_hat = svm_mod.predict(X_test)
  
      # Calculando a acurácia e a F1.
      acc_default[k] = (accuracy_score(y_test, y_test_hat))
      f1_default[k] = (f1_score(y_test, y_test_hat))

Observando os valores finais, teríamos:

# Verificando os valores médios.
mean_accuracy = round(np.mean(acc_default), 3)
mean_f1_score = round(np.mean(f1_default), 3)

print(f"Valor médio de acurácia: {mean_accuracy}")
## Valor médio de acurácia: 0.998
print(f"Valor médio de F1-score: {mean_f1_score}")
## Valor médio de F1-score: 0.998

Observa-se o resultado final e, então, temos os valores médios para acurácia e para o F1-score. O objetivo, então, será melhorar o valor obtido através do processo de tuning.

Tuning: explorando hiperparâmetros e funções de kernel.

De maneira análoga à abordagem anterior, iremos criar um grid de valores. No entanto, o sklearn não tem tantas funções de kernel pré-definidas, portanto, utilizaremos apenas um subconjunto de funções de kernel: linear, polinomial e gaussiano.

from itertools import product

# Definindo os intervalo de valores do grid
c_grid = [ 0.1,1,10]
sigma_grid = [0.1, 0.25, 0.5, 1, 5, 10]
d_grid = [2,3]

# Criando os grids para cada um dos kernels
lin_grid = pd.DataFrame(list(product(c_grid, ["vanilladot"], [np.nan], [np.nan])), columns=["C", "kernel", "sigma_grid", "d_grid"])
pol_grid = pd.DataFrame(list(product(c_grid, ["polydot"], [np.nan], d_grid)), columns=["C", "kernel", "sigma_grid", "d_grid"])
gau_grid = pd.DataFrame(list(product(c_grid, ["rbfdot"], sigma_grid, [np.nan])), columns=["C", "kernel", "sigma_grid", "d_grid"])


# Criando matrizes para armazenar os valores de acurácia e pontuações F1.
lin_acc = np.zeros((n_rep, len(lin_grid)))
lin_f1 = np.zeros((n_rep, len(lin_grid)))

pol_acc = np.zeros((n_rep, len(pol_grid)))
pol_f1 = np.zeros((n_rep, len(pol_grid)))

gau_acc = np.zeros((n_rep, len(gau_grid)))
gau_f1 = np.zeros((n_rep, len(gau_grid)))


# Iterando todos os valores dos grids e todas as repetições do holdout.
for i in range(n_rep):
  for j in range(len(lin_grid)):
    # Ajustando com o SVM com o Kernel Linear
    svm_mod = SVC(kernel="linear", C=lin_grid["C"][j])
    svm_mod = svm_mod.fit(cv[i]["train_set"].drop(columns=['y']), cv[i]["train_set"]["y"])
    # Predizendo novas observações.
    y_test_hat = svm_mod.predict(cv[i]["test_set"].drop(columns=['y']))
    lin_acc[i, j] = accuracy_score(cv[i]["test_set"]["y"], y_test_hat)
    lin_f1[i, j] = f1_score(cv[i]["test_set"]["y"], y_test_hat)
  
  for j in range(len(pol_grid)):
    # Ajustando com o SVM com o Kernel Polinomial
    svm_mod = SVC(kernel='poly', C=pol_grid['C'][j], degree=pol_grid['d_grid'][j],coef0 = 0, gamma = 1)
    svm_mod = svm_mod.fit(cv[i]['train_set'].drop(columns=['y']), cv[i]['train_set']['y'])
    # Predizendo novas observações.
    yhat_test = svm_mod.predict(cv[i]['test_set'].drop(columns=['y']))
    pol_acc[i, j] = accuracy_score(cv[i]['test_set']['y'], yhat_test)
    pol_f1[i, j] = f1_score(cv[i]['test_set']['y'], yhat_test)
  
  for j in range(len(gau_grid)):
    # Gerando o modelo com o Kernel Gaussiano
    svm_mod = SVC(kernel='rbf', C=gau_grid['C'][j], gamma=gau_grid['sigma_grid'][j])
    svm_mod = svm_mod.fit(cv[i]['train_set'].drop(columns=['y']), cv[i]['train_set']['y'])
    # Predizendo novas observações.
    yhat_test = svm_mod.predict(cv[i]['test_set'].drop(columns=['y']))
    gau_acc[i, j] = accuracy_score(cv[i]['test_set']['y'], yhat_test)
    gau_f1[i, j] = f1_score(cv[i]['test_set']['y'], yhat_test)

Armazenando os índices dos valores máximos:

import numpy as np

# Obtendo os valores máximos com respeito à ACC.
lin_max_acc = np.where(np.apply_along_axis(np.mean, 0, lin_acc) == np.max(np.apply_along_axis(np.mean, 0, lin_acc)))[0]
pol_max_acc = np.where(np.apply_along_axis(np.mean, 0, pol_acc) == np.max(np.apply_along_axis(np.mean, 0, pol_acc)))[0]
gau_max_acc = np.where(np.apply_along_axis(np.mean, 0, gau_acc) == np.max(np.apply_along_axis(np.mean, 0, gau_acc)))[0]

# Obtendo os valores máximos com respeito ao F1.
lin_max_f1 = np.where(np.apply_along_axis(np.mean, 0, lin_f1) == np.max(np.apply_along_axis(np.mean, 0, lin_f1)))[0]
pol_max_f1 = np.where(np.apply_along_axis(np.mean, 0, pol_f1) == np.max(np.apply_along_axis(np.mean, 0, pol_f1)))[0]
gau_max_f1 = np.where(np.apply_along_axis(np.mean, 0, gau_f1) == np.max(np.apply_along_axis(np.mean, 0, gau_f1)))[0]

Para este caso, podemos ainda verificar se os hiperparâmetros que produziram os valores máximos de acurácia são os mesmos para o F1 score.

# Checando se as combinações de hiperparâmetros para cada kernel que produzem o máximo resultam no mesmo valor.
all_equal = np.all(lin_max_acc == lin_max_f1) and np.all(pol_max_acc == pol_max_f1) and np.all(gau_max_acc == gau_max_f1) 
print(all_equal)
## True

Finalmente, pode-se plotar os resultados para verificar se houve uma melhora através do processo de tuning quando comparado à versão default:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# Criando um DataFrame do pandas para armazenar todos os valores das métricas.
plot_data = pd.DataFrame({
    'metric': ['ACC'] * len(acc_default) * 4  + ['F1'] * len(f1_default) * 4,
    'kernel': ['Default'] * len(acc_default) + ['Linear'] * len(lin_acc[:, lin_max_acc[0]]) + \
              ['Polinomial'] * len(pol_acc[:, pol_max_acc[0]]) + ['Gaussian'] * len(gau_acc[:, gau_max_acc[0]]) + \
              ['Default'] * len(f1_default) + ['Linear'] * len(lin_f1[:, lin_max_f1[0]]) + \
              ['Polinomial'] * len(pol_f1[:, pol_max_f1[0]]) + ['Gaussian'] * len(gau_f1[:, gau_max_f1[0]]),
    'val': list(acc_default) + list(lin_acc[:, lin_max_acc[0]]) + \
           list(pol_acc[:, pol_max_acc[0]]) + list(gau_acc[:, gau_max_acc[0]]) + \
           list(f1_default) + list(lin_f1[:, lin_max_f1[0]]) + \
           list(pol_f1[:, pol_max_f1[0]]) + list(gau_f1[:, gau_max_f1[0]])
})

# Convertendo a coluna 'kernel' para categórica para ordenação correta.
plot_data['kernel'] = pd.Categorical(plot_data['kernel'], 
                                      categories=['Default', 'Linear',
                                      'Polinomial', 'Gaussian'])



# Configurando o estilo do gráfico.
sns.set(style="whitegrid")

# Criando o boxplot
plt.figure(figsize=(10, 6))
sns.boxplot(x='kernel', y='val', hue='metric', data=plot_data, palette='Set1')
plt.xlabel('Kernel')
plt.ylabel('')
plt.title('Boxplot of ACC and F1 scores by Kernel')
plt.legend(title='Métrica')
plt.xticks(rotation=0)
## (array([0, 1, 2, 3]), [Text(0, 0, 'Default'), Text(1, 0, 'Linear'), Text(2, 0, 'Polinomial'), Text(3, 0, 'Gaussian')])
plt.show()

A partir da Figura acima, pode-se perceber que, através do processo de tuning, o conjunto de hiperparâmetros que produz o valor médio máximo de acurácia e F1 para o kernel gaussiano supera os resultados obtidos usando o default, ressaltando a importância dessa etapa para obter a melhor performance do SVM.