🗒️ 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.