Qué es deep learning: caso práctico en EDICOM (Parte II)

En esta segunda parte, describiremos cómo implementar paso a paso un sistema de deep learning que solucione el problema planteado: calificar de forma automática la complejidad de una nueva tarea técnica de EDICOM.

    Artículo elaborado por:

    Jose Blas Vilata

    Director Técnico y socio fundador de EDICOM

Introducción

Recordemos que el objetivo final de este artículo es programar un algoritmo que califique de forma automática la complejidad de una nueva tarea técnica de EDICOM, simulando de esta forma lo que actualmente hace un humano experto, apoyándonos para ello en el conocimiento de todos los datos históricos de tareas técnicas actualmente almacenados en nuestro sistemas de gestión.

Ya comentamos que aunque se puede intentar abordar el problema desde un punto de vista de algorítmica tradicional, vemos con bastante claridad que la definición de este problema encaja bien dentro de los algoritmos de inteligencia artificial, en concreto en la rama de machine learning e hilando más fino, en su versión más moderna, deep learning.

En la primera parte del artículo hemos dado un repaso a los conceptos básicos teóricos del deep learning. En esta segunda parte, describiremos cómo implementar paso a paso un sistema de deep learning que solucione el problema planteado.

Herramientas a utilizar y preparación del entorno

El lenguaje de programación que vamos a usar es Python, por ello, es lo primero que tenemos que instalar. Todo lo demás que necesitamos son librerías de Python que iremos instalando según necesitemos, aunque recomendamos empezar al menos con las siguientes:

Python es uno de los lenguajes de programación que domina dentro del ámbito de la estadística, data mining y machine learning. Al tratarse de un software libre, innumerables usuarios han podido implementar sus algoritmos, dando lugar a un número muy elevado de librerías donde encontrar prácticamente todas las técnicas de machine learning existentes.

Scikit-learn (sklearn) es una biblioteca de machine learning de código abierto que admite el aprendizaje supervisado y no supervisado. También proporciona varias herramientas para el ajuste de modelos, el preprocesamiento de datos, la selección y evaluación de modelos y muchas otras utilidades.

TensorFlow es el principal framework de deep learning de código abierto, desarrollado y mantenido por Google.

Keras es una biblioteca de deep learning de código abierto escrita en Python. Keras permite diseñar, ajustar, evaluar y usar modelos de deep learning para hacer predicciones en solo unas pocas líneas de código accesibles y entendibles por la mayor parte de los desarrolladores.

Usar TensorFlow directamente puede ser un desafío para los desarrolladores, por ello en 2019, Google lanzó la versión TensorFlow 2 que integró la API de Keras directamente y promovió esta interfaz como la interfaz predeterminada o estándar para el desarrollo de aprendizaje profundo en la plataforma. Por ello, con la versión 2 de TensorFlow no es necesario instalar Keras, ya viene incorporado.

Esta integración se conoce comúnmente como la interfaz o API tf.keras (TensorFlow-Keras).

Podemos instalar Python, Tensorflow y sklearn directamente en nuestra máquina o si no queremos “ensuciar” nuestra computadora con nuevos programas podemos hacer uso de una imagen de docker (jvilata/tensorf-sk:v1) con todo preinstalado y listo para ejecutarse y que pongo a vuestra disposición en el repositorio público Docker Hub. Para instalar esta imagen, lógicamente hay que tener instalado el motor de Docker, que en la mayoría de Linux viene preinstalado o es muy fácil de instalar, y en Windows también lo podemos conseguir mediante el instalador correspondiente de la página: https://docs.docker.com/docker-for-windows/install/

deep learning 01

Para probar que nuestra imagen Docker funciona la ejecutamos y entramos en Bash (Shell de Linux):

deep learning 02

Donde el parámetro “-v d:\docker\:/srv” es opcional y sirve para que podáis mapear un directorio local de vuestra máquina, en este caso “d:\docker\” a un directorio de la máquina virtual de la imagen Docker, en este caso “/srv”. Esto nos facilita las cosas de cara a editar archivos sin necesidad de instalar más programas en el Docker, ya que las ediciones las haremos en local en esta carpeta y los cambios se verán directamente desde la imagen Docker.

La imagen Docker incluye el directorio “/edicom” con los datos y ejemplos que aquí aparecen. En este punto será indistinto estar en Docker o en instalación local ya que todo lo haremos desde dentro de Python.

Vamos a comprobar que está todo bien instalado entrando en Python e imprimiendo la versión de TensorFlow (seguir las líneas marcadas en rojo):

deep learning 03

Si hemos hecho la instalación en local y nos dice que no encuentra el módulo “tensorflow” es porque todavía no lo hemos instalado, al igual que seguramente nos pasará con otras librerías que nos harán falta y que sí están instaladas ya en la imagen de Docker. A modo de ejemplo solo voy a poner un comando de cómo se instalaría una librería de Python, en este caso la de TensorFlow, y lo mismo habría que hacer con cada una de las librerías que nos vayan haciendo falta. Desde el Shell del sistema ejecutamos:

deep learning 04

Vamos a volver a imprimir las versiones de TensorFlow y Keras de una forma un poco distinta, a través de un archivo de programa Python. Para ello, abrimos un archivos de texto que llamaremos “versions.py”. Si estamos utilizando imagen de Docker, este archivo deberá estar situado en el directorio que hayamos mapeado en “/srv” en nuestro caso “d:\docker\”. Escribimos lo siguiente dentro del archivo:

deep learning 05

Y ahora ejecutamos desde la línea de comandos:

deep learning 06

Redirijo la salida de error “2> null” para no ver mensajes no deseados por pantalla. La mayoría son
avisos de hardware no instalado tipo GPUs y TensorFlow nos avisa de ello pero no pasa nada,
simplemente significan que no aprovechará la potencia de dichos aceleradores.

Ciclo de vida de un proyecto de machine learning

Normalmente los siguiente pasos serán comunes a todo proyecto de machine learning:

  • Definir el problema: ¿Qué se pretende predecir? ¿De qué datos se dispone? o ¿Qué datos es necesario conseguir?
  • Explorar, entender y preparar los datos que se van a emplear para crear el modelo.
  • Separar las observaciones en un conjunto de entrenamiento y en un conjunto de validación o test. Es muy importante asegurar que ninguna información del conjunto de test participa en el proceso de entrenamiento del modelo.
  • Definimos el modelo, normalmente de tipo secuencial con al menos 2 capas densas, una de entrada y otra de salida, y sus correspondientes funciones de activación.
  • Compilamos y ajustamos el modelo definiendo una función de pérdida, un algoritmo de optimización y una función de métrica que nos permita conocer la bondad de nuestro modelo respecto a los resultados esperados.
  • Entrenamos el modelo y lo mejoramos incorporando nuevas variables u optimizando los hiperparámetros (“epochs”, “batch_size”) en base al resultado de la evaluación.
  • Evaluamos la capacidad del modelo con el conjunto de datos de test para tener una estimación de la capacidad que tiene el modelo para predecir nuevas observaciones.
  • Guardamos el modelo final para poderlo utilizar en el futuro para predecir resultados a partir de datos nuevos.

Definir el problema

Como ya hemos expuesto en varias ocasiones, en EDICOM cada requerimiento de cliente genera en nuestro sistema gestión una tarea técnica donde se recopilan diferentes datos como la descripción del trabajo a realizar, el cliente solicitante, fechas de trazabilidad, el número de jornadas que se le facturarán al cliente por dicho trabajo, etc. A continuación un responsable experto califica la complejidad de esta tarea en un orden del 0 al 5 de forma manual. En base a esta calificación se le asignará para su ejecución un director de proyecto técnico con las competencias adecuadas. También servirá esta calificación para hacer un mejor ajuste de la capacidad de los técnicos en base al número de proyectos que gestionan según su complejidad.

Nuestro objetivo es programar un algoritmo que califique, de forma automática, la complejidad de una nueva tarea del 0 al 5 simulando de esta forma lo que actualmente hace un humano experto, apoyándonos en el conocimiento de todos los datos de tareas técnicas actualmente almacenadas en nuestro sistema de gestión durante décadas y debidamente calificadas por un humano.

Explorar y entender los datos

Por lo general, la fase de extracción y preparación de datos será la que nos consuma más tiempo en un proyecto de machine learning.

Vamos a extraer los datos de tareas técnicas del sistema de gestión de EDICOM. Para ello, le preguntamos a uno de los expertos que actualmente califica las tareas qué datos de la tarea son los más significativos a la hora de calificar la complejidad de la misma. Tras una larga conversación concluimos los siguientes: importe de la venta (euros), tareas previas de ese mismo cliente por tipo complejidad del 0 al 5, número de mensajes a integrar, jornadas estimadas de trabajo vendidas, volumen de mensajes mensuales estimado, si el cliente pertenece a un grupo empresarial y nivel de soporte que ya tiene asignado el cliente (básico, preferente).

deep learning 07

Programamos una Query para extraer los datos anteriores de nuestra base de datos relacional de los últimos 5 años y el resultado de la misma lo exportamos a formato CSV (texto separado por comas). Cada fila corresponde a una tarea técnica y cada columna es una de las características que pensamos que pueden contribuir a definir mejor la predicción que queremos hacer en nuestro cálculo de la complejidad de dicha tarea.

También tenemos que exportar con la misma Query y en el mismo archivo CSV la columna o columnas que representan el resultado a obtener en nuestra predicción, en nuestro caso la complejidad estimada de la tarea, representada en la columna sombreada. Como estamos hablando de datos históricos sí disponemos de esta información que ha ido introduciendo un humano a lo largo del tiempo.

Vamos a crearnos un archivo “edicom1.py” donde pondremos el código Python para explorar y manipular los datos CSV y conocerlos un poco mejor.

Primero tendremos que importar las librerías de Python que vamos a usar después para cargar y visualizar los datos:

deep leaning 08

A continuación cargamos en la variable “datos” el Dataset o archivo de datos CSV con separador de campos “;” e indicando que el separador decimal será la “,”.

deep leaning 09

Nuestro archivo CSV tiene más o menos este aspecto, donde podemos apreciar que hemos cargado el nombre de las columnas en la primera fila:

deep leaning  10

Ahora vamos a explorar el Dataset cargado en Python en la variable “datos”:

deep learning 11

Con datos.head(4) nos muestra las 4 primeras filas del Dataset, datos.info() muestra las columnas con sus tipos y datos.shape visualiza el número de filas y columnas. La última instrucción nos mostrará por cada columna el número de valores nulos encontrados:

deep leaning 13

Vemos que aparecen las columnas “IDTAREA” y “PAIS_GESTOR” que no las vamos a utilizar en nuestro modelo y, por lo tanto, las podemos borrar del Dataset. También nos llama la atención que en la columna “importeventa”,”complejidad” y “diasVendidos” hay valores nulos, 3689, 99 y 2 respectivamente. Los modelos no trabajan bien con nulos así que debemos tomar una de las siguientes acciones: eliminar las filas con nulos o cambiar los valores nulos por 0 o por algún otro valor como la media. Nosotros optaremos por esta segunda opción y pondremos la “complejidad” nula a 1 y los atributos “importeventa” y “diasVendidos” los rellenaremos con la media de los valores según su “complejidad”.

deep learning 14

Para no extendernos mucho, mostraremos a modo de ejemplo un par de gráficos sobre el Dataset. En el archivo “edicom1.py” de la imagen Docker hay más ejemplos de gráficos.

Esto nos genera un archivo PDF “tmp0.pdf” con la imagen del gráfico de tarta con los tipos de complejidad del Dataset:

Otro gráfico que nos puede ayudar a entender los datos es el de correlaciones entre variables ya que si detectamos que dos columnas están muy correlacionadas podemos eliminar una de las dos. Por contra, nos interesará que las columnas que elijamos tengan una buena correlación con la columna objetivo, querrá decir que aportan bastante a realizar una buena predicción.

A priori ya podemos ver que solo las columnas “mensajesAIntegrar” y “diasVendidos” tienen una correlación relevante respecto a la complejidad. Desgraciadamente los demás atributos no van a contribuir mucho en la predicción pero algo ayudarán. En siguientes versiones del modelo tendremos que encontrar nuevas variables que aporten más valor al conjunto.

La columna “complejidad” con valores del 0 al 5 es de tipo categórico porque realmente está indicando una clasificación, es decir, aunque tengamos números es como si tuviéramos cadenas del tipo “nada complejo”, “poco complejo”, etc. El aprendizaje profundo se basa en algoritmos estadísticos y los algoritmos estadísticos funcionan con números. Por lo tanto, necesitamos convertir la información categórica en columnas numéricas. Hay varios enfoques para hacer eso, pero uno de los enfoques más comunes es la codificación one-hot.

En la codificación one-hot, para cada valor único en la columna categórica, se crea una nueva columna. En nuestro caso crearemos 6 columnas del tipo “comp_0”, “comp_1”, etc. Estas columnas solo tendrán valores 0 o 1. Por ejemplo, si la complejidad era 2 la columna “comp_2” tendrá un 1 y todas las demás un 0.

Programación del modelo

Ahora ya estamos en disposición de definir nuestro modelo. Para ello crearemos un nuevo archivo de Python llamado “edicom2.py” donde ya partiremos del siguiente contenido:

Ahora separamos del Dataset nuestras columnas de datos en la variable X y la columna o columnas de etiquetas o resultados, en nuestro caso la “complejidad” en la variable y:

En la instrucción df.values[:, 1:] le estamos indicando que formarán parte de X todas las filas (:) y solo de la segunda columna en adelante (1:), ya que la primera columna es la 0. A la variable y le asignamos todas las filas y columnas de labels.

Separar observaciones de entrenamiento y test

Es necesario separar nuestro Dataset en un conjunto de datos de entrenamiento y otro de test que sea completamente disjuntos, de ello se encarga la función de sklearn “train_test_split”. En este caso le indicamos que queremos el 33% dedicado a test:

Nos guardamos en la variable “n_features” el número de columnas del conjunto de datos de entrenamiento X_train.shape[1]. Recordemos que shape devuelve un array donde en [0] está el número de filas y en [1] el número de columnas.

Definimos el modelo de la red neuronal

Hemos definido un modelo “secuencial” con sólo 2 capas: la inicial y la final. Hemos dejado comentada una tercera capa “oculta” por si el lector quiere hacer sus pruebas tratando de mejorar la exactitud del modelo.

La capa inicial consta de 128 unidades o neuronas, recibirá los datos de “n_features” columnas y a la salida de cada unidad lineal de esta capa se le aplicará una función de activación llamada rectificadora lo que convertirá a cada neurona en una unidad lineal rectificada o “relu”. El resultado de cada una de estas 128 “relu” se pasará a la siguiente capa que estará formada por 6 neuronas o unidades lineales a las que aplicaremos la función de activación “softmax” especializada en problemas de clasificación. Básicamente lo que hará es dar como resultado para cada una de las 6 neuronas una probabilidad entre el 0 y el 1. La que más probabilidad tenga la daremos como resultado.

Compilamos el modelo

La compilación del modelo requiere que se seleccione una función de pérdida a optimizar, como el error cuadrático medio “mse” para los problemas de regresión o la entropía cruzada “crossentropy” para los problemas de clasificación.

También requiere que se seleccione un algoritmo para realizar el procedimiento de optimización, normalmente el descenso de gradiente estocástico “sgd” o una variación moderna como “Adam”.

Por último, debemos seleccionar una métrica de rendimiento para realizar seguimiento durante el proceso de entrenamiento del modelo. Para problemas de clasificación normalmente “accuracy” (porcentaje de aciertos) y error absoluto medio “mae” en problemas de regresión.

Entrenar el modelo

Para entrenar el modelo primero tenemos que seleccionar la configuración de entrenamiento, como el número de épocas “epochs” y el tamaño del lote “batch”. En la parte I de este artículo, se describía con más detalle estos conceptos.

El entrenamiento aplica el algoritmo de optimización elegido “adam” para minimizar la función de pérdida seleccionada “categorical_crossentropy” y actualiza el modelo utilizando el algoritmo de retropropagación del error. Este proceso se repetirá epochs * (tamañoMuestra / batch_size) iteraciones y en cada iteración se probará con batch_size muestras.

Ajustar el modelo es la parte lenta de todo el proceso y puede llevar de segundos a horas o días, según la complejidad del modelo, el hardware que esté utilizando y el tamaño del conjunto de datos de entrenamiento.

Utilizaremos un 20% de la muestra de entrenamiento para validación de resultado y ajuste del modelo y utilizamos una función de parada temprana “early stopping” para no continuar iterando si la ganancia está por debajo del min_delta estipulado de 0.00001. Aún así seguimos 100 iteraciones más por si cambio dicho valor (patience).

A continuación generamos un gráfico de evolución del entrenamiento del modelo y lo guardamos en el archivo PDF “plot-validacion.pdf”.

Evaluamos el modelo y guardamos en un fichero

Una vez que hemos seleccionado y ajustado el modelo a nuestro conjunto de datos de entrenamiento, podemos usar los datos de test para estimar el rendimiento del modelo en datos nuevos, por lo que podemos hacer una estimación del error de generalización del modelo.

Si estamos satisfechos con el valor de la métrica obtenida, podremos usar el modelo para realizar predicciones con los datos futuros.

Pasamos a producción y hacemos predicciones

Una vez tenemos calculado y guardado el modelo ya podemos hacer predicciones con datos nuevos. A modo de ejemplo tenemos el archivo “edicom3.py” donde cargamos el modelo previamente calculado y hacemos la predicción:

Resultados y conclusiones

La primera conclusión que he sacado desarrollando esta prueba es que la exactitud del modelo no he conseguido que supere el 60%, en el mejor de los casos, ni aún cambiando hiperparámetros de batch y epochs. Incluso tomando un conjunto de datos más grande al original que inicialmente era de 6000 muestras y hemos pasado a 10000 que es el tamaño actual.

Otra conclusión es que he probado con diferentes tamaños de batch y número de epochs y sí afecta bastante el tamaño de batch y no tanto el número de epochs a partir de 200. Respecto al tamaño de batch, he probado con 32, 50 y 100 y los mejores resultados los he obtenido con 50.

Por lo tanto y como conclusión final considero lo siguiente: o la estimación de nuestros expertos no siempre sigue un patrón “racional” y en esos casos el modelo es incapaz de predecir la clasificación decidida por el experto, o lo más probable, nos faltan más variables de entrada que ayuden al sistema a predecir mejor el resultado, información que sí tiene el experto pero que no hemos sido capaces de extraer en esta versión.

En cualquier caso el modelo construido tiene un nivel de aciertos suficiente como para que pueda servir en este momento de referencia al experto que está calificando las tareas actualmente, pero sin llegar a ser un sistema autónomo 100%.

Referencias