Cargamos las bibliotecas correspondientes a este modelo.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
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
from sklearn.model_selection import GridSearchCV
from sklearn.naive_bayes import MultinomialNB
from sklearn.naive_bayes import GaussianNB
from sklearn.naive_bayes import CategoricalNB
from preprocessing import prepararSetDeDatos
from preprocessing import prepararSetDeHoldout
from preprocessing import prepararSetDeValidacion
from preprocessing import conversionAVariablesCodificadas
from preprocessing import conversionAVariablesContinuas
from preprocessing import expansionDelDataset
from funcionesAuxiliares import mostrarAUCScore
from funcionesAuxiliares import mostrarROCCurve
from funcionesAuxiliares import mostrarMatrizDeConfusion
from funcionesAuxiliares import escribirPrediccionesAArchivo
from funcionesAuxiliares import obtenerDatasets
from funcionesAuxiliares import obtenerHoldout
Cargamos ambos set de datos, y se los pasamos a la funciones que realizan el armado hecho para el TP1.
X,y = obtenerDatasets()
X = prepararSetDeDatos(X)
y = prepararSetDeValidacion(y)
def obtenerMejorAlpha(modelo,datosPreprocesados):
mejor_valor = 0
mejor_alpha = None
y_array=np.array(y)
for valor_alpha in [0.001,0.01,0.1,0.3,0.5,0.7,1,2,3,10]:
kf = StratifiedKFold(n_splits=2)
metricas = []
for fold_idx, (train_index, test_index) in enumerate(kf.split(datosPreprocesados, y_array)):
modeloNB = modelo(alpha=valor_alpha)
modeloNB.fit(datosPreprocesados[train_index], y_array[train_index].ravel())
predicciones = modeloNB.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_alpha = valor_alpha
return mejor_valor, mejor_alpha
def obtenerMejorHiperparametroGaussianNB(datosPreprocesados):
mejor_valor = 0
mejor_smoothing = None
y_array=np.array(y)
for valor_smoothing in [0.00000001,0.0000001,0.000001,0.00001,0.0001,0.001]:
kf = StratifiedKFold(n_splits=8)
metricas = []
for fold_idx, (train_index, test_index) in enumerate(kf.split(datosPreprocesados, y_array)):
modeloNB = GaussianNB(var_smoothing=valor_smoothing)
modeloNB.fit(datosPreprocesados[train_index], y_array[train_index].ravel())
predicciones = modeloNB.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_smoothing = valor_smoothing
return mejor_valor, mejor_smoothing
def graficarAUCROC(tipo,modeloNB,X_test,X_train,y_test,y_train):
fpr_nb_test,tpr_nb_test,thresholds_nb_test = roc_curve(y_test,modeloNB.predict_proba(X_test)[:,1])
fpr_nb_train,tpr_nb_train,thresholds_nb_train = roc_curve(y_train,modeloNB.predict_proba(X_train)[:,1])
zero_test = np.argmin(np.abs(thresholds_nb_test))
zero_train = np.argmin(np.abs(thresholds_nb_train))
plt.plot(fpr_nb_train,tpr_nb_train,label="ROC Curve "+tipo+" NB Train")
plt.plot(fpr_nb_test,tpr_nb_test,label="ROC Curve "+tipo+" NB Test")
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.plot(fpr_nb_test[zero_test],tpr_nb_test[zero_test],'o',markersize=10,label="threshold zero test",fillstyle="none",c="k",mew=2)
plt.plot(fpr_nb_train[zero_train],tpr_nb_train[zero_train],'x',markersize=10,label="threshold zero train",fillstyle="none",c="k",mew=2)
plt.legend(loc=4)
plt.show()
Este modelo es utilizado para problemas de clasificación en los que se tienen variables categóricas.
Empezamos realizando el preprocesamiento a los datos. En este caso consiste en deshacernos de las variables con datos numéricos (ej. precio, edad) y codificar ordinalmente el resto de las variables. Probamos primero quedandonos con todas las categoricas.
X.head()
columnasCategoricas = ['sufijo','tipo_de_sala','genero','fila','nombre_sede']
X_categoricalNB = conversionAVariablesCodificadas(X,columnasCategoricas)
En cuanto a hiperparámetros, los modelos de Naive Bayes son simples, ya que solamente tienen un solo hiperparámetro importante, siendo este el alpha. El alpha indica cuanto smoothing se le va a estar aplicando a las diferentes combinaciones, de forma tal de no tener casos con probabilidad 0.
Probamos primero con el preprocesamiento basico aplicado al preparar el set de datos.
mejor_valor, mejor_alpha = obtenerMejorAlpha(CategoricalNB,X_categoricalNB)
print(f"El mejor valor de AUC fue: {round(mejor_valor,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha}")
Ahora veamos que aparece con el preprocesamiento que expande el dataset creando nuevas columnas.
X = expansionDelDataset(X)
X.head()
columnasCategoricas = ['sufijo','tipo_de_sala','genero','fila','nombre_sede','pago_categorizado','edades_estratificadas','categoria_invitados']
X_categoricalNB_exp = conversionAVariablesCodificadas(X,columnasCategoricas)
mejor_valor_exp, mejor_alpha_exp = obtenerMejorAlpha(CategoricalNB,X_categoricalNB_exp)
print(f"El mejor valor de AUC fue: {round(mejor_valor_exp,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha_exp}")
Vemos que se obtuvo un valor similar al anterior.
Probamos sacando las columnas de 'fila' y 'sede', ya que la primera esta en su mayoria compuesta por valores nulos ('No responde'), y la segunda no es un feature muy relevante para saber si alguien volveria o no.
columnasCategoricas = ['sufijo','tipo_de_sala','genero','pago_categorizado','edades_estratificadas','categoria_invitados']
X_categoricalNB_exp = conversionAVariablesCodificadas(X,columnasCategoricas)
mejor_valor_exp2, mejor_alpha_exp2 = obtenerMejorAlpha(CategoricalNB,X_categoricalNB_exp)
print(f"El mejor valor de AUC fue: {round(mejor_valor_exp2,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha_exp2}")
Vemos que no se obtuvieron mejoras.
Dividimos el set de datos y creamos el modelo con el valor de alpha obtenido.
X_train, X_test, y_train, y_test = train_test_split(X_categoricalNB, y, test_size=0.25, random_state=0)
categoricalNB = CategoricalNB(alpha=mejor_alpha)
categoricalNB.fit(X_train, y_train)
Realizamos ahora las predicciones correspondientes con la parte de entrenamiento y la de pruebas, una vez hecho eso, mostramos las métricas obtenidas para la parte de test.
y_pred = categoricalNB.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
mostrarMatrizDeConfusion(y_pred,y_test)
Graficamos ahora la curva ROC para ambos casos.
graficarAUCROC("Categorical",categoricalNB,X_test,X_train,y_test,y_train)
Podemos observar que en este caso la curva de evaluación resulta mejor que la de entrenamiento, indicando que se generalizó mejor. Esto se debe a que se acerca mucho más hacia la esquina superior izquierda del gráfico. (Su área bajo la curva está más cerca de 1)
mostrarAUCScore(categoricalNB,"Categorical NB",X_test,y_test)
Ahora nos guardamos las probabilidades que predice este modelo, ya que las usaremos más adelante.
probabilidades_categorical = categoricalNB.predict_proba(X_train)
probabilidades_x_test_categorical = categoricalNB.predict_proba(X_test)
Procedemos de manera similar para Multinomial Naive Bayes. Este caso solamente difiere en que este modelo se utiliza para features discretos. Por lo que sacamos los atributos que no sirven para este caso y codificamos el resto. Probando los basicos al comienzo.
X.head()
columnasDiscretas = ['sufijo','tipo_de_sala','genero','amigos','parientes','precio_ticket','fila','nombre_sede']
X_multinomialNB = conversionAVariablesCodificadas(X,columnasDiscretas)
mejor_valor, mejor_alpha = obtenerMejorAlpha(MultinomialNB,X_multinomialNB)
print(f"El mejor valor de AUC fue: {round(mejor_valor,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha}")
Habiendo obtenido el mejor alpha para el Multinomial Naive Bayes en el preprocesamiento basico, probamos ahora con el expandido.
columnasDiscretas = ['sufijo','tipo_de_sala','genero','amigos','parientes','fila','nombre_sede','precio_ticket','2_clusters','4_clusters','10_clusters',
'cantidad_total_invitados','pago_categorizado','edades_estratificadas','categoria_invitados']
X_multinomialNB_exp = conversionAVariablesCodificadas(X,columnasDiscretas)
mejor_valor_exp, mejor_alpha_exp = obtenerMejorAlpha(MultinomialNB,X_multinomialNB_exp)
print(f"El mejor valor de AUC fue: {round(mejor_valor_exp,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha_exp}")
Vemos que empeoro el promedio del AUC. Probamos sin algunas columnas.
columnasDiscretas = ['sufijo','tipo_de_sala','genero','amigos','parientes','fila','precio_ticket',
'cantidad_total_invitados','pago_categorizado','edades_estratificadas','categoria_invitados']
X_multinomialNB_exp2 = conversionAVariablesCodificadas(X,columnasDiscretas)
mejor_valor_exp2, mejor_alpha_exp2 = obtenerMejorAlpha(MultinomialNB,X_multinomialNB_exp2)
print(f"El mejor valor de AUC fue: {round(mejor_valor_exp2,3)}")
print(f"El mejor alpha obtenido fue: {mejor_alpha_exp2}")
Se observo una mejora respecto al primero sacando los resultados de los clusters y de la sede. Probamos un ultimo modelo con las caracteristicas que dieron mejor.
X_train, X_test, y_train, y_test = train_test_split(X_multinomialNB, y, test_size=0.25, random_state=0)
multinomialNB = MultinomialNB(alpha=mejor_alpha_exp2)
multinomialNB.fit(X_train, y_train)
Realizamos devuelta las predicciones de la parte de evaluación pero con este modelo.
y_pred = multinomialNB.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
Como podemos observar, se obtuvieron en la mayoría de las métricas valores cercanos al 80% para ambas predicciones hechas. Vemos la matriz de confusión ahora y el área bajo la curva.
mostrarMatrizDeConfusion(y_pred,y_test)
Graficamos ahora la curva ROC para ambos casos.
graficarAUCROC("Multinomial",multinomialNB,X_test,X_train,y_test,y_train)
Observamos que devuelta la curva de evaluación tiene mejor forma que la de entrenamiento.
mostrarAUCScore(multinomialNB,"Multinomial NB",X_test,y_test)
En este caso obtuvimos rendimientos bastante similares al del CategoricalNB, estando ligeramente abajo por unas milésimas.
probabilidades_multinomial = multinomialNB.predict_proba(X_train)
probabilidades_x_test_multinomial = multinomialNB.predict_proba(X_test)
Utilizamos ahora Gaussian Naive Bayes. Este modelo es para features continuos, por lo que en su preprocesamiento nos quedaremos solamente con ese tipo de variables. Al realizar esto, no esperamos que este modelo tenga un gran rendimiento, ya que estaría considerando una parte reducida de los datos.
X_gaussianNB = conversionAVariablesContinuas(X)
Buscamos el hiperparámetro que tiene este modelo. (var_smoothing)
mejor_valor, mejor_var_smoothing = obtenerMejorHiperparametroGaussianNB(X_gaussianNB)
print(f"El mejor valor de AUC fue: {round(mejor_valor,3)}")
print(f"El mejor var_smoothing obtenido fue: {mejor_var_smoothing}")
X_train, X_test, y_train, y_test = train_test_split(X_gaussianNB, y, test_size=0.25, random_state=0)
gaussianNB = GaussianNB(var_smoothing = mejor_var_smoothing)
gaussianNB.fit(X_train, y_train)
Repetimos lo realizado anteriormente.
y_pred = gaussianNB.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
mostrarMatrizDeConfusion(y_pred,y_test)
Graficamos ahora la curva ROC para ambos casos.
graficarAUCROC("Gaussian",gaussianNB,X_test,X_train,y_test,y_train)
Notamos que en este caso se tuvo una curva mucho peor que en los anteriores casos.
mostrarAUCScore(gaussianNB,"Gaussian NB",X_test,y_test)
Observamos que se obtuvo un valor muy bajo. Esto era esperable, ya que se están analizando muy pocas variables al considerar solamente las continuas.
probabilidades_gaussian = gaussianNB.predict_proba(X_train)
probabilidades_x_test_gaussian = gaussianNB.predict_proba(X_test)
Viendo que sklearn tiene la limitación que no permite trabajar a la vez con variables categóricas y variables continuas, decidimos realizar un ensamble al que le pasamos las probabilidades de los 3 modelos hechos anteriormente. De esta forma, logramos obtener un modelo de Naive Bayes que trabaje con ambos tipos de variables.
Comenzamos uniendo las probabilidades obtenidas anteriormente.
probabilidades_x_train = np.hstack((probabilidades_multinomial, probabilidades_categorical , probabilidades_gaussian))
probabilidades_x_test = np.hstack((probabilidades_x_test_multinomial, probabilidades_x_test_categorical , probabilidades_x_test_gaussian))
X_ensamble_gaussiano = np.vstack((probabilidades_x_train,probabilidades_x_test))
mejor_valor, mejor_var_smoothing = obtenerMejorHiperparametroGaussianNB(X_ensamble_gaussiano)
print(f"El mejor valor de AUC fue: {round(mejor_valor,3)}")
print(f"El mejor var_smoothing obtenido fue: {mejor_var_smoothing}")
ensamble_gaussiano = GaussianNB(var_smoothing = mejor_var_smoothing)
ensamble_gaussiano.fit(probabilidades_x_train,y_train)
y_pred = ensamble_gaussiano.predict(probabilidades_x_test)
print(classification_report(y_test, y_pred, target_names=['No vuelve','Vuelve']))
mostrarAUCScore(ensamble_gaussiano,"Ensamble Gaussiano NB",probabilidades_x_test,y_test)
Habiendo hecho el ensamble, observamos que no se tuvieron grandes diferencias en comparacion al Multinomial y Categorical, estando este tipo de ensamble en el medio. Lo que sí mejoró notablemente es su rendimiento en comparación al Gaussian NB.
El algoritmo de Naive Bayes debe calcular, a priori, las probabilidades de que una instancia pertenezca a una clase. Esto lo hace con la variable target, las "etiquetas" del set de entrenamiento. De esta manera si inicialmente tenemos más instancias de una clase que de otra tendremos más probabilidad de clasificarla de esa clase mayoritaria, al menos a priori.
gaussianNB.class_prior_
np.exp(categoricalNB.class_log_prior_)
np.exp(multinomialNB.class_log_prior_)
Claramente, dado que hemos entrenado ambos modelos con la misma información de variable target, las probabilidades a priori son las mismas para los tres modelos. Podemos ver que casi dos tercios de la instancias son de la clase "No volveria"
features_categorical = X.drop(columns=['edad', 'precio_ticket', 'autocompletamos_edad']).columns.to_list()
features_gaussian = ['edad','precio_ticket','autocompletamos_edad']
features_multinomial = X.drop(columns=['edad', 'autocompletamos_edad']).columns.to_list()
Buscamos ahora información acerca de las probabilidades condicionales que se construyeron en el modelo. Analizaremos algunas para poder ver si se corresponden con el análisis realizado en la primer parte del trabajo.
i = 0
j = 0
for array in categoricalNB.feature_log_prob_:
feature = features_categorical[i]
for probabilidad in array:
print(feature)
if(j % 2 == 0):
print("Dado que no volveria")
else:
print("Dado que volveria")
print(np.exp(probabilidad))
print("")
j=j+1
i=i+1
Las probabilidades anteriores son las probabilidades condicionales. Condicionado al subconjunto de las personas que volverían o no volverían se calcula para cada feature la probabilidad de que tome alguno de los valores que puede tomar.
Esto se puede calcular utilizando pandas, por ejemplo para el caso del género:
y_df = pd.DataFrame(columns = ['volveria'], data = y)
X_volveria = X.iloc[y_df.loc[y_df["volveria"] == True].index]
X_volveria["genero"].value_counts(normalize = True).round(2)
X_no_volveria = X.iloc[y_df.loc[y_df["volveria"] == False].index]
X_no_volveria["genero"].value_counts(normalize = True).round(2)
Podemos ver que dentro de las personas que volverían, casi el 70% es mujer y dentro de las personas que no vuelven el 84% es hombre.
Este feature es bastante relevante. Por ejemplo, suponiendo que consideramos solo este feature realizaríamos las siguientes predicciones:
Si es hombre:
P(vuelve) < P(no vuelve) => si es hombre no vuelve
Si es mujer:
$P$(no vuelve) = $P_{a priori}$(no vuelve) $P$(mujer|no vuelve) = 0.62 0.15 = 0.093
$P$(vuelve) > $P$(no vuelve) => si es mujer vuelve
Vemos que relativamente las probabilidades son muy distintas: para los hombres, la probabilidad de que vuelva es aproximadamente 3 veces mayor que de que no vuelva y para las mujeres lo contrario.
Por este motivo consideramos que debe ser un feature relevante a la hora de clasificar a una persona en si esta volvería o no al cine a ver la secuela de la película.
Obtenemos y preparamos el nuevo archivo realizando el mismo preprocesamiento realizado anteriormente.
holdout = obtenerHoldout()
ids_usuarios = np.array(holdout['id_usuario'])
holdout = prepararSetDeHoldout(holdout)
columnasCategoricas = ['sufijo','tipo_de_sala','genero','fila','nombre_sede']
columnasDiscretas = ['sufijo','tipo_de_sala','genero','amigos','parientes','precio_ticket','fila','nombre_sede']
holdout_categorical_NB = conversionAVariablesCodificadas(holdout,columnasCategoricas)
holdout_multinomial_NB = conversionAVariablesCodificadas(holdout,columnasDiscretas)
holdout_gaussian_NB = conversionAVariablesContinuas(holdout)
predicciones_holdout_categorical = categoricalNB.predict(holdout_categorical_NB)
predicciones_holdout_multinomial = multinomialNB.predict(holdout_multinomial_NB)
predicciones_holdout_gaussian = gaussianNB.predict(holdout_gaussian_NB)
escribirPrediccionesAArchivo(predicciones_holdout_categorical,"Categorical NB",ids_usuarios)
escribirPrediccionesAArchivo(predicciones_holdout_multinomial,"Multinomial NB",ids_usuarios)
escribirPrediccionesAArchivo(predicciones_holdout_gaussian,"Gaussian NB",ids_usuarios)
probabilidades_multinomial = multinomialNB.predict_proba(holdout_multinomial_NB)
probabilidades_categorical = categoricalNB.predict_proba(holdout_categorical_NB)
probabilidades_gaussian = gaussianNB.predict_proba(holdout_gaussian_NB)
probabilidades_holdout = np.hstack((probabilidades_multinomial, probabilidades_categorical , probabilidades_gaussian))
predicciones_holdout_ensamble_gaussiano = ensamble_gaussiano.predict(probabilidades_holdout)
escribirPrediccionesAArchivo(predicciones_holdout_ensamble_gaussiano,"Ensamble Gaussiano NB",ids_usuarios)