Trabajo Práctico 2: Análisis con SVM - Organización de Datos

Alumnos y Padrón

  • Grassano, Bruno - 103855
  • Romero, Adrián - 103371

https://github.com/brunograssano/TP-Organizacion-de-datos

Importamos las bibiliotecas

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
In [2]:
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
from sklearn.model_selection import KFold, StratifiedKFold
In [3]:
from sklearn.svm import SVC
In [4]:
from preprocessing import prepararSetDeDatos
from preprocessing import prepararSetDeHoldout
from preprocessing import prepararSetDeValidacion
from preprocessing import expansionDelDataset
from preprocessing import conversionAVariablesNormalizadas
In [5]:
from funcionesAuxiliares import mostrarAUCScore
from funcionesAuxiliares import mostrarROCCurve
from funcionesAuxiliares import mostrarMatrizDeConfusion
from funcionesAuxiliares import escribirPrediccionesAArchivo
from funcionesAuxiliares import obtenerDatasets
from funcionesAuxiliares import obtenerHoldout

Importamos los datos y los procesamos

In [6]:
X,y = obtenerDatasets()
X = prepararSetDeDatos(X)
y= prepararSetDeValidacion(y)

Funciones Auxiliares

Creamos una función que obtiene mediante grid search y K-Fold cross validation el parametro C y el kernel que maximiza la métrica de AUC_ROC para el modelo de SVM.

In [7]:
def obtenerMejoresParametros(datosPreprocesados):
    mejor_valor = 0
    mejor_c = None
    mejor_kernel = None
    y_array=np.array(y)
    for c in [1,5,10,15,20,25,30,50,75,100,200,300]:
        for kernel in ["poly", "rbf", "linear"]:
            kf = StratifiedKFold(n_splits=8)
            metricas = []
            for fold_idx, (train_index, test_index) in enumerate(kf.split(datosPreprocesados, y_array)):
                svm = SVC(C = c, kernel = kernel, probability = True)
                svm.fit(datosPreprocesados[train_index], y_array[train_index].ravel())
                predicciones = svm.predict_proba(datosPreprocesados[test_index])[:,1]
                score_obtenida = roc_auc_score(y_array[test_index],predicciones)
                metricas.append(score_obtenida)

            if np.mean(metricas) >= mejor_valor:
                mejor_valor = np.mean(metricas)
                mejor_c = c
                mejor_kernel = kernel
            
    return mejor_valor, mejor_c, mejor_kernel
In [8]:
def obtenerMejorGamma(datosPreprocesados, mejor_c, mejor_kernel):
    mejor_gamma = None
    mejor_valor = 0
    y_array=np.array(y)
    for gamma in ['scale',0.00001,0.0001, 0.001,0.005, 0.01, 0.05, 0.1, 1, 10, 100, 1000]:
        kf = StratifiedKFold(n_splits=8)
        metricas = []
        for fold_idx, (train_index, test_index) in enumerate(kf.split(datosPreprocesados, y_array)):
            svm = SVC(C = mejor_c, kernel = mejor_kernel, gamma=gamma,probability = True)
            svm.fit(datosPreprocesados[train_index], y_array[train_index].ravel())
            predicciones = svm.predict_proba(datosPreprocesados[test_index])[:,1]
            score_obtenida = roc_auc_score(y_array[test_index],predicciones)
            metricas.append(score_obtenida)

        if np.mean(metricas) >= mejor_valor:
            mejor_valor = np.mean(metricas)
            mejor_gamma = gamma
            
    return mejor_valor, mejor_gamma

SVM

SVM es un método de aprendizaje supervisado que busca clasificar instancias a partir de un hiperplano separador de clases. Por lo tanto en principio las instancias deben ser linealmente separables.

Lo que se busca es encontrar un hiperplano que separe las clases y maximice la distancia entre las instancias mas cercanas al mismo, generando así un margen.

Se dice que el margen es suave o soft cuando se permite que el hiperplano encontrado no separe perfectamente a las clases y que se permitan clasificaciones erróneas en el set de entrenamiento con el objetivo de generalizar mejor. Suele ser útil tener un margen de este tipo cuando hay ruido o outliers.

Cuando las instancias no son linealmente separables se puede recurrir a una transformación de los datos a un espacio de dimensión mayor en el cual sí lo sean. Dado que esto puede ser muy costoso, se suelen utilizar kernels, funciones que permiten calcular las distancias entre instancias en un espacio de dimensión mayor sin realizar la transformación de los datos.

Buscamos los mejores parámetros

Los parámetros que consideraremos son C y el kernel:

C: Es un parámetro de regularización que indica la suavidad del margen generado por el clasificador SVM. Mientras mayor sea C se tiene un margen mas rígido y pequeño que puede ser bueno clasificando los puntos del set de entrenamiento pero generar overfitting. Mientras menor sea C el margen es mas suave en el sentido de que se permiten clasificaciones erróneas sobre el set de entrenamiento con el propósito de generalizar mejor.

Kernel: el kernel es la función que calcula el producto interno entre las observaciones en un espacio de dimensión mayor, sin tener que convertir los datos a ese espacio, logrando asi definir una distancia en dimensiones mayores. Consideraremos los kernels: lineal, radial y polinomial.

Preprocesado simple

In [9]:
X_svm = conversionAVariablesNormalizadas(X)
In [10]:
mejor_valor, mejor_c, mejor_kernel = obtenerMejoresParametros(X_svm)
In [11]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor,3)}")
print(f"El valor de C encontrado que maximiza el AUC fue: {mejor_c}")
print(f"El kernel encontrado que maximiza el AUC fue: {mejor_kernel}")
El mejor valor fue de AUC fue: 0.86
El valor de C encontrado que maximiza el AUC fue: 10
El kernel encontrado que maximiza el AUC fue: rbf

Dado que el mejor kernel encontrado fue rbf buscamos un optimizar un nuevo hiperparámetro:

$\gamma$, es un parámetro que se utiliza en este kernel y que indica cuánta influencia tiene cada instancia de entrenamiento. Este parámetro influencia en el cálculo de la distancia entre las instancias.

In [12]:
mejor_valor, mejor_gamma = obtenerMejorGamma(X_svm, mejor_c, mejor_kernel)
In [13]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor,3)}")
print(f"El valor de gamma encontrado que maximiza el AUC fue: {mejor_gamma}")
El mejor valor fue de AUC fue: 0.86
El valor de gamma encontrado que maximiza el AUC fue: scale

Preprocesado expandido

Probamos ahora con el preprocesamiento que expande el set de datos.

In [14]:
X = expansionDelDataset(X)
In [15]:
X.head()
Out[15]:
sufijo tipo_de_sala genero edad amigos parientes precio_ticket fila nombre_sede autocompletamos_edad 2_clusters 4_clusters 10_clusters cantidad_total_invitados total_pagado pago_categorizado edades_estratificadas categoria_invitados
0 Señor 4d hombre 73.0 0 0 1 No responde fiumark_quilmes False 1 1 4 0 1 Pago poco mayor Fue solo
1 Señora 4d mujer 35.0 1 1 2 No responde fiumark_quilmes False 0 2 0 2 6 Pago normal adulto Fue en grupo
2 Señor normal hombre 32.0 0 0 3 No responde fiumark_chacarita True 0 2 0 0 3 Pago normal adulto Fue solo
3 Señor 4d hombre 32.0 0 0 1 No responde fiumark_palermo True 0 2 0 0 1 Pago poco adulto Fue solo
4 Señorita 4d mujer 4.0 1 1 2 No responde fiumark_palermo False 0 3 3 2 6 Pago normal ninio Fue en grupo
In [16]:
columnas_codificables_extra = ['pago_categorizado','edades_estratificadas','categoria_invitados']
columnas_numericas_extra = ['2_clusters','4_clusters','10_clusters','cantidad_total_invitados','total_pagado']

X_svm_exp = conversionAVariablesNormalizadas(X,columnas_codificables_extra,columnas_numericas_extra)
In [17]:
mejor_valor_exp, mejor_c_exp, mejor_kernel_exp = obtenerMejoresParametros(X_svm_exp)
In [18]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor_exp,3)}")
print(f"El valor de C encontrado que maximiza el AUC fue: {mejor_c_exp}")
print(f"El kernel encontrado que maximiza el AUC fue: {mejor_kernel_exp}")
El mejor valor fue de AUC fue: 0.856
El valor de C encontrado que maximiza el AUC fue: 300
El kernel encontrado que maximiza el AUC fue: rbf

Devuelta como obtuvimos rbf buscamos si hay algun gamma mejor.

In [19]:
mejor_valor_exp, mejor_gamma_exp = obtenerMejorGamma(X_svm_exp, mejor_c_exp, mejor_kernel_exp)
In [20]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor_exp,3)}")
print(f"El valor de gamma encontrado que maximiza el AUC fue: {mejor_gamma_exp}")
El mejor valor fue de AUC fue: 0.856
El valor de gamma encontrado que maximiza el AUC fue: scale

Vemos que empeoro algo el promedio de AUC encontrado respecto al primero probado sin las columnas nuevas. Probamos ahora sacandole algunas columnas de las nuevas creadas.

Preprocesado con menos columnas

In [21]:
columnas_codificables_extra = ['pago_categorizado','edades_estratificadas']
columnas_numericas_extra = ['2_clusters','4_clusters','10_clusters']

X_svm_exp2 = conversionAVariablesNormalizadas(X,columnas_codificables_extra,columnas_numericas_extra)
In [22]:
mejor_valor_exp2, mejor_c_exp2, mejor_kernel_exp2 = obtenerMejoresParametros(X_svm_exp2)
In [23]:
print(f"El mejor valor fue de AUC fue: {round(mejor_valor_exp2,3)}")
print(f"El valor de C encontrado que maximiza el AUC fue: {mejor_c_exp2}")
print(f"El kernel encontrado que maximiza el AUC fue: {mejor_kernel_exp2}")
El mejor valor fue de AUC fue: 0.858
El valor de C encontrado que maximiza el AUC fue: 1
El kernel encontrado que maximiza el AUC fue: linear

Mejoro ligeramente, pero no lo suficiente como para pasarlo.

Dividimos el set de datos en sets de training y test

In [24]:
X_train, X_test, y_train, y_test = train_test_split(X_svm, y, test_size=0.25, random_state=0)

Evaluamos las métricas

In [25]:
svm = SVC(C=mejor_c, kernel=mejor_kernel,gamma = mejor_gamma, probability=True)
In [26]:
svm.fit(X_train, y_train)
Out[26]:
SVC(C=10, probability=True)
In [27]:
y_pred = svm.predict(X_test)
In [28]:
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
              precision    recall  f1-score   support

   No vuelve       0.80      0.86      0.83       121
      Vuelve       0.76      0.68      0.72        80

    accuracy                           0.79       201
   macro avg       0.78      0.77      0.77       201
weighted avg       0.78      0.79      0.78       201

Observamos que tiene un 82% 79% de accuracy lo cual esta en el mismo orden de los otros modelos con esta métrica.

Matriz de confusión

In [29]:
mostrarMatrizDeConfusion(y_pred,y_test)

Respecto de la matriz de confusión corroborar nuevamente que la clasificación tiene un buen accuracy, pues los valores en la diagonal principal son mucho mayores que los de la diagonal invertida.

Por otro lado pareciera tener una cantidad similar de falsos positivos y de falsos negativos

Graficamos la curva ROC

In [30]:
mostrarROCCurve(svm,"SVM",X_test, X_train, y_test, y_train)
In [31]:
mostrarAUCScore(svm,"SVM",X_test,y_test)
AUC para SVM: 0.862

Predicciones sobre el nuevo archivo

Obtenemos y preparamos el nuevo archivo realizando el mismo preprocesamiento realizado anteriormente.

In [32]:
holdout = obtenerHoldout()
ids_usuarios = np.array(holdout['id_usuario'])
holdout = prepararSetDeHoldout(holdout)
holdout_svm = conversionAVariablesNormalizadas(holdout)

Realizamos las predicciones y escribimos al archivo CSV.

In [33]:
predicciones_holdout = svm.predict(holdout_svm)
In [34]:
escribirPrediccionesAArchivo(predicciones_holdout,"SVM",ids_usuarios)