Alumnos y Padrón
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from numpy.random import seed
seed(42)
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 GridSearchCV
import tensorflow as tf
tf.random.set_seed(42)
from keras.callbacks import EarlyStopping
from tensorflow.keras import regularizers
physical_devices = tf.config.list_physical_devices('GPU')
try:
tf.config.experimental.set_memory_growth(physical_devices[0], True)
except:
pass
import keras
from keras.wrappers.scikit_learn import KerasClassifier
from keras.models import Sequential
from keras.layers import Dense, Dropout
from preprocessing import prepararSetDeDatos
from preprocessing import prepararSetDeHoldout
from preprocessing import prepararSetDeValidacion
from preprocessing import conversionAVariablesNormalizadas
from preprocessing import expansionDelDataset
from funcionesAuxiliares import escribirPrediccionesAArchivo
from funcionesAuxiliares import obtenerDatasets
from funcionesAuxiliares import obtenerHoldout
Cargamos los sets de datos que se usarán para el entrenamiento y validación.
X, y = obtenerDatasets()
X = prepararSetDeDatos(X)
y = prepararSetDeValidacion(y)
def graficarPerdidaDelModelo(historia_modelo):
plt.figure(dpi=125, figsize=(7, 2))
plt.plot(historia_modelo.history['loss'], label="Training loss")
plt.plot(historia_modelo.history['val_loss'], label="Validation loss")
plt.title('Loss del modelo')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()
def entrenarModelo(modelo, epocas, tamanio_entrenamiento):
historia = modelo.fit(X_train, y_train, epochs=epocas, batch_size=tamanio_entrenamiento, verbose=0, validation_split=0.25)
return historia, modelo
def entrenarModeloDatasetExpandido(modelo, epocas, tamanio_entrenamiento):
historia = modelo.fit(X_train_exp, y_train_exp, epochs=epocas, batch_size=tamanio_entrenamiento, verbose=0, validation_split=0.25)
return historia, modelo
# No se utiliza la de funcionesAuxiliares debido a que en este caso se tiene otro array. (en predict_proba [:,1]) (idem en mostrar el AUCScore)
def graficarROCCurve(modelo,nombreModelo,X_test, X_train, y_test, y_train):
fpr_test, tpr_test, thresholds_test = roc_curve(y_test, modelo.predict_proba(X_test))
fpr_train, tpr_train, thresholds_train = roc_curve(y_train, modelo.predict_proba(X_train))
zero_test = np.argmin(np.abs(thresholds_test))
zero_train = np.argmin(np.abs(thresholds_train))
plt.plot(fpr_test, tpr_test, label="ROC Curve "+nombreModelo+" Test")
plt.plot(fpr_train, tpr_train, label="ROC Curve " + nombreModelo + " Train")
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.plot(fpr_test[zero_test], tpr_test[zero_test], 'o', markersize=10, label="threshold zero test",
fillstyle="none", c="k", mew=2)
plt.plot(fpr_train[zero_train], tpr_train[zero_train], 'x', markersize=10, label="threshold zero train",
fillstyle="none", c="k", mew=2)
plt.legend(loc=4)
plt.show()
Las redes neuronales están dentro de lo que se considera modelos más complejos. Este tipo de modelo dispone de una amplia cantdidad de parámetros que se pueden ir modificando hasta obtener los mejores resultados.
Para el preprocesamiento decidimos utilizar el mismo tipo de función que en otros modelos. Este preprocesamiento encodea las variables categóricas mediante OneHotEncoding y normaliza las variables numéricas.
X_redes_neuronales = conversionAVariablesNormalizadas(X)
X_train, X_test, y_train, y_test = train_test_split(X_redes_neuronales, y, test_size=0.25, random_state=0)
Los parámetros que tendremos en cuenta al entrenar las redes neuronales seran:
Función de pérdida a optimizar: Es la función que se busca minimizar. En nuestro caso como deseamos hacer una clasificación binaria, es decir, entre dos clases, utilizaremos siempre la función: binary cross entropy
Optimizador: Es el algoritmo mediante el cual se optimiza el función de perdida anterior. Utilizaremos Stochastic Gradient Descent (SGD) y Adam. La diferencia principal radica en que Adam es un optimizador que considera a la derivada segunda para realizar la optimización mientras que SGD solo la derivada primera. Esto puede llegar a suavizar la pérdida al entrenar la red a lo largo de las épocas.
Tasa de aprendizaje: Es un parámetro que indica la velocidad con la cual el optimizador intenta acercarse el mÃnimo de la función de pérdida. Una tasa pequeña, requerirá más iteraciones para alcanzar el mÃnimo y una muy grande podrÃa nunca encontrarlo, por ejemplo ya que se lo saltea constantemente.
Funcion de activacion de las neuronas: Es la función que se aplica sobre el input de cada neurona, antes de multiplicarla por el peso correspondiente. Hemos probado las siguientes: ReLu, sigmoidea y tanh
Cantidad de capas: Es la cantidad de capas de la red. Consideramos que teniendo una capa de input, una oculta y una de output era suficiente. Esto es porque agregando capas el tiempo de entrenamiento se volvÃa poco razonable y posiblemente más complicado de lo necesario.
Cantidad de neuronas de cada capa: Hemos entrenado con la siguiente configuración de la red: La primera capa tiene 14 neuronas pues tenemos 14 features. La última capa tiene 1, lo cual nos servirá para realizar la clasificación en los 2.
Además, en algunas redes, utilizamos dropout: durante el entrenamiento, algunas de las neuronas no se tienen en cuenta. Esto puede volver más robusto al modelo, al hacer que la salida del mismo no depende únicamente de un camino.
En estas primeras redes vamos a ir probando sin aplicar regularizacion. Luego intentaremos ver si logramos mejorar el resultado agregandole las diferentes opciones.
Empezamos ahora armando una red neuronal sencilla para ver cómo es su desempeño.
red_neuronal1 = Sequential()
red_neuronal1.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal1.add(Dense(6, activation='tanh'))
red_neuronal1.add(Dense(1, activation='sigmoid'))
red_neuronal1.compile(loss='binary_crossentropy', optimizer="SGD", metrics=[tf.keras.metrics.AUC()])
Mostramos el resumen de como queda armada.
red_neuronal1.summary()
Ahora finalmente entrenamos con el set de entrenamiento.
h, red_neuronal1 = entrenarModelo(red_neuronal1, 500, 50)
Observamos cómo se fue desarrollando la funcion de pérdida para el entrenamiento y la validación de la red.
graficarPerdidaDelModelo(h)
Ahora buscamos las métricas que nos interesan sobre el set de evaluación guardado anteriormente.
y_pred = red_neuronal1.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal1,"Red Neuronal 1",X_test, X_train, y_test, y_train)
auc_red = roc_auc_score(y_test,red_neuronal1.predict_proba(X_test))
print("AUC para redes neuronales 1: {:.3f}".format(auc_red))
Vemos que se obtuvieron resultados que estan bien, pero que pueden mejorarse. Una cosa que se destaca es que en el gráfico del entrenamiento aparece como que todavÃa puede seguir aprendiendo.
Probamos aumentando la cantidad de épocas.
red_neuronal2 = Sequential()
red_neuronal2.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal2.add(Dense(6, activation='tanh'))
red_neuronal2.add(Dense(1, activation='sigmoid'))
red_neuronal2.compile(loss='binary_crossentropy', optimizer="SGD", metrics=[tf.keras.metrics.AUC()])
h, red_neuronal2 = entrenarModelo(red_neuronal2, 800, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal2.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal2,"Red Neuronal 2",X_test, X_train, y_test, y_train)
auc_red = roc_auc_score(y_test,red_neuronal2.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
Vemos que aprendió bastante más y mejoró la métrica, pero que ya en el entrenamiento se empiezan a ver picos hacia el final.
Probamos mejorarlo cambiando el optimizador a 'Adam'.
red_neuronal3 = Sequential()
red_neuronal3.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal3.add(Dense(6, activation='tanh'))
red_neuronal3.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal3.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal3 = entrenarModelo(red_neuronal3, 800, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal3.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal3,"Red Neuronal 3",X_test, X_train, y_test, y_train)
auc_red = roc_auc_score(y_test,red_neuronal3.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
Vemos que en este caso disminuyo el valor buscado.
Probamos agregando algunas capas de 'Dropout' y bajamos las epocas a 350, asà no empieza a separarse hacia el final la función de pérdida.
red_neuronal4 = Sequential()
red_neuronal4.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal4.add(Dropout(0.1))
red_neuronal4.add(Dense(6, activation='tanh'))
red_neuronal4.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal4.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal4 = entrenarModelo(red_neuronal4, 350, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal4.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal4,"Red Neuronal 4",X_test, X_train, y_test, y_train)
auc_red = roc_auc_score(y_test,red_neuronal4.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
En este caso el resultado mejoro
Buscamos probar ahora agregando una capa más junto a un dropout.
red_neuronal5 = Sequential()
red_neuronal5.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal5.add(Dropout(0.1))
red_neuronal5.add(Dense(14, activation='relu'))
red_neuronal5.add(Dropout(0.1))
red_neuronal5.add(Dense(6, activation='tanh'))
red_neuronal5.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal5.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal5 = entrenarModelo(red_neuronal5, 350, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal5.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal5,"Red Neuronal 5",X_test, X_train, y_test, y_train)
auc_red = roc_auc_score(y_test,red_neuronal5.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
Habiendo hecho esto, el valor de la métrica AUC incremento respecto a las primeras realizadas, pero no respecto a la anterior (0.877).
Por ultimo buscamos crear un modelo de redes neuronales que utilice el dataset expandido:
X_exp = expansionDelDataset(X)
columnas_codificables_extra = ['pago_categorizado','edades_estratificadas','categoria_invitados']
columnas_numericas_extra = ['2_clusters','4_clusters','10_clusters','cantidad_total_invitados','total_pagado']
X_redes_exp = conversionAVariablesNormalizadas(X_exp,columnas_codificables_extra,columnas_numericas_extra)
X_train_exp, X_test_exp, y_train_exp, y_test_exp = train_test_split(X_redes_exp, y, test_size=0.25, random_state=0)
red_neuronal6 = Sequential()
red_neuronal6.add(Dense(26, input_dim=26, activation='relu'))
red_neuronal6.add(Dropout(0.1))
red_neuronal6.add(Dense(6, activation='tanh'))
red_neuronal6.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal6.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal6 = entrenarModeloDatasetExpandido(red_neuronal6, 350, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal6.predict(X_test_exp)
print(classification_report(y_test_exp, y_pred.round(), target_names=['No vuelve','Vuelve']))
graficarROCCurve(red_neuronal6,"Red Neuronal 6",X_test_exp, X_train_exp, y_test_exp, y_train_exp)
auc_red = roc_auc_score(y_test_exp,red_neuronal6.predict_proba(X_test_exp))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
Vemos que no logramos mejorar la métrica obtenida al utilizar la red neuronal 4, por lo tanto sera esa la que utilizaremos para las predicciones de holdout.
Antes de quedarnos con una red definitiva para las predicciones, probamos utilizar la red 4 que nos dio el mejor resultado hasta ahora con regularizaciones.
red_neuronal4_regularizada = Sequential()
red_neuronal4_regularizada.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal4_regularizada.add(Dropout(0.1))
red_neuronal4_regularizada.add(Dense(6, activation='tanh', kernel_regularizer=tf.keras.regularizers.l1(0.001),
activity_regularizer=tf.keras.regularizers.l2(0.001)))
red_neuronal4_regularizada.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal4_regularizada.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal4_regularizada = entrenarModelo(red_neuronal4_regularizada, 350, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal4_regularizada.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
auc_red = roc_auc_score(y_test,red_neuronal4_regularizada.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
No logro superar a la red sin regularizacion. Probamos devuelta tocando un poco mas los parametros.
red_neuronal4_regularizada = Sequential()
red_neuronal4_regularizada.add(Dense(14, input_dim=14, activation='relu'))
red_neuronal4_regularizada.add(Dropout(0.1))
red_neuronal4_regularizada.add(Dense(6, activation='tanh', kernel_regularizer=tf.keras.regularizers.l1_l2(l1=0.001, l2=0.001),
activity_regularizer=tf.keras.regularizers.l1_l2(l1=0.001, l2=0.01)))
red_neuronal4_regularizada.add(Dense(1, activation='sigmoid'))
optimizador = keras.optimizers.Adam()
red_neuronal4_regularizada.compile(loss='binary_crossentropy', optimizer=optimizador, metrics=[tf.keras.metrics.AUC()])
h, red_neuronal4_regularizada = entrenarModelo(red_neuronal4_regularizada, 350, 50)
graficarPerdidaDelModelo(h)
y_pred = red_neuronal4_regularizada.predict(X_test)
print(classification_report(y_test, y_pred.round(), target_names=['No vuelve','Vuelve']))
auc_red = roc_auc_score(y_test,red_neuronal4_regularizada.predict_proba(X_test))
print("AUC para redes neuronales: {:.3f}".format(auc_red))
Devuelta no logramos superar a las anterior. Aunque hay que destacar que tuvo uno mejor perdida para la validacion, manteniendose las curvas de training y validation separadas.
Obtenemos y preparamos el nuevo archivo realizando el mismo preprocesamiento realizado anteriormente.
holdout = obtenerHoldout()
ids_usuarios = np.array(holdout['id_usuario'])
holdout = prepararSetDeHoldout(holdout)
holdout_redes = conversionAVariablesNormalizadas(holdout)
Realizamos las predicciones y escribimos al archivo CSV. Para realizar las predicciones, utilizamos el modelo que mejor resultado dio.
predicciones_holdout = red_neuronal4.predict(holdout_redes)
escribirPrediccionesAArchivo(predicciones_holdout.round().astype(int).ravel(),"RedesNeuronales",ids_usuarios)