🗒️ Resumo
🗒️ 3. Primeiros passos Hands-on
🗒️ 5. Outros tipos de Modelos de Vetores de Suporte
🗒️ Apêndice
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.
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()
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)})
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.
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.