Crear una Red Neuronal con Spark MLlib

0
962

Índice de contenidos

1. Introducción

En este tutorial vamos a aprender a crear una red neuronal con Spark MLlib, la librería de Machine Learning de Spark, y la vamos a entrenar con datos de vendedores para que aprenda a clasificar aquellos que tienen un pico de ventas.

Vamos a usar dos bases de datos, una de PostgreSQL, que almacena los datos históricos de los clientes y sus propiedades y otra de ElasticSearch, que almacena las ventas que hacen.

Primero explicaremos cómo cargar los datos en spark y cómo procesarlos para poder pasárselos a nuestra red neuronal y luego veremos el funcionamiento interno de esta red neuronal y evaluaremos su eficacia.

A pesar de que este tutorial está hecho íntegramente en Java, Spark también tiene APIs para Scala y Python.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2 Ghz Intel Core i7, 8GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12.6
  • Software:
    • Entorno de desarrollo: Eclipse Oxygen 4.7.3
    • Postman 6.1.3
    • DBeaver 5.1.1
    • Docker 18.03.1
    • Kitematic 0.17.2
    • PostgreSQL 9.5
    • elasticDump 3.3.18

3. Configuración inicial

Primero vamos a levantar un docker con PostgreSQL y ElasticSearch. Para ello, en la carpeta de nuestro proyecto creamos un docker-compose:

docker-compose.yml

Una vez creado abrimos una terminal en la carpeta del proyecto y ejecutamos

Cuando se hayan cargado los contendores, hacemos Ctrl+C y cerramos la terminal.

Vamos a Kitematic, y actualizamos la lista de contenedores desde view –> Refresh Container List, y los lanzamos.

Si todo ha ido bien, veremos nuestros contenedores en verde:


Vamos a cargar ahora la base de datos de PostgreSQL. Para ello nos descargamos las bases de datos, abrimos DBeaver y conectamos con la base de datos que acabamos de levantar. Cargamos la base de datos desde Tools –> Restore:

Seleccionamos como backup file postgres/postgres.backup y le damos a continuar.

Si nos sale el siguiente error:

Lo corregimos dando click izquierdo en la conexión -> Properties -> Edit coneection –> Connection settings –> Local Client, y añadimos /Applications/Postgres.app/Contents/Versions/9.5/bin. La seleccionamos y guardamos.

Vamos a cargar ahora la base de datos de ElasticSearch. Para ello descomprimimos el archivo elasticsearch/backup.zip, abrimos una terminal en la carpeta elasticsearch y escribimos:

Tardará unos 3-5 minutos en importar la base de datos entera.

Podemos comprobar que la base de datos de ElasticSearch se ha cargado correctamente desde Postman.

Importamos el archivo postman/machine learning tutorial.postman_collection.json y podemos lanzar las consultas status y Query todos, que nos dan las siguientes salidas:

status

Query todos

Hay más consultas preparadas, para poder ejecutarlas es necesario lanzar previamente la consulta Campo idCliente como fielddata.

No vamos a explicarlas en este tutorial, pero nos sirven para comprobar que los datos que cargaremos en Spark se han procesado correctamente.

4. ¡A programar!

El código de este tutorial se puede descargar desde github e importarlo directamente como un proyecto de maven en eclipse o cualquier otro IDE.

En este tutorial explicaremos las partes más importantes del código.

Antes de empezar es necesario añadir las dependencias necesarias a nuestro proyecto, que en nuestro caso son hadoop, las bases de datos y spark y su librería de machine learning.

pom.xml

4.1. Conectar con las bases de datos

Para poder cargar datos desde postgres creamos la clase SparkPostgresConnection y definimos dos métodos, uno que devuelve la base de datos entera y otra que devuelve sólo el resultado de una query.

SparkPostgresConnection.java

Vemos que ambos devuelven Dataset<Row>, que es el tipo de datos con los que trabaja Spark. Podemos considerarlos como una tabla, en la que Spark se encarga de distribuir los datos y todas las optimizaciones, y lo hace transparente para nosotros.

getData() conecta con la base de datos y descarga los datos de la table table.

Primero configuramos las opciones de conexión, cargamos la sesión de Spark y el conexto sql.

Una vez que tenemos el contexto sql podemos leer datos de la base de datos, especificando las opciones de conexión. En este caso cargamos todos los datos.

getDataFromQuery() ejecuta una consulta SQL y devuelve los datos obtenidos.

Aprovechamos que ya tenemos los datos descargados previamente para sólo especificar la tabla en la que se hace la consulta y la query.

Para poder cargar datos desde ElasticSearch creamos la clase SparkElasticsearchConnection y definimos los mismos dos métodos de antes.

SparkElasticsearchConnection.java

En getData() sólo es necesario especificar la ip y puerto de elastic.

Especificamos la configuración set(“es.nodes.wan.only”, “true”) porque la base de datos está en docker o en la nube. Si tu caso te lo permite es mejor no especificarla porque afecta mucho al rendimiento.

4.2. Cargar datos en Spark

Primero cargamos el contexto de Spark con:

Usamos las funciones que hemos creado para cargar los datos. Cargamos los datos de PostgreSQL con:

Los datos de elastic los cargamos filtrando primero con una query que nos da los datos de los 90 días anteriores a la fecha especificada.

Si queremos comprobar el resultado de la query a elastic directamente, podemos verla en postman en Query 90 días

4.3. Procesar los datos

Ahora que tenemos los datos en Spark, vamos a procesarlos para tener en una sola tabla toda la información relevante de las ventas.

De la base de datos de ElasticSearch, que es donde están las ventas, primero obtenemos las ventas que ha hecho cada cliente por hora:

Utilizamos una ventana de 24 horas para añadir a la tabla de ventas una nueva columna que recoge las ventas acumuladas por el cliente en el último día

De las bases de datos SQL obtenemos del histórico lo que cada cliente suele vender por día y los umbrales en los que suele trabajar, tanto en porcentaje como en cantidad.

Vemos que para poder hacer join de las tablas hemos usado la función joinBy(), que es una función creada por nostros por comodidad. Esta función simplemente convierte una lista de String en una secuencia de scala:

Combinamos ahora las dos tablas de datos y tendríamos ya todos los datos de entrada para la red neuronal

Clasificaremos a un cliente como que tiene un pico de ventas si la diferencia entre sus ventas en las últimas 24 horas y lo que suele vender por día se sale de los umbrales que tiene especificados. Para clasificarlo como pico tiene que salirse de los umbrales tanto en cantidad como en porcentaje.

Consideraremos que tiene un gran pico de ventas si esta diferencia se sale de los umbrales que hemos especificado para grandes picos.

Para hacer estas clasificaciones creamos la función:

Añadiendo las alarmas a nuestra tabla de datos ya tendríamos los datos preparados para que los procese la red neuronal.

4.4. Crear la red neuronal

Vamos a crear una clase llamada RedNeuronal que tendrá dos métodos públicos, uno para entrenarla, y otro para clasificar datos una vez finalizado el entrenamiento.

Creamos dos constructores, uno que configura la red neuronal con los valores por defecto y otro que permite especificar estos valores.

Al crear una red neuronal es necesario especificar las neuronas de entrada, que tienen que coicidir con el número de columnas de datos, y las neuronas de salida, que tienen que coincidir con el número de posibles calificaciones de los datos.

Los parámetros que podemos pasar son:

  • neuronasIntermedias: Lista de capas ocultas de la red neuronal.
  • porcentajeTest: Porcentaje de datos que no se pasarán al entrenamiento de la red para luego poder comprobar su eficacia. Suele estar entre un 10% y un 40%.
  • seed: Semilla para las operaciones pseudoaleatorias. Con la que hay especificada debería valer.
  • blockSize: Tamaño de los bloques en que se dividen los datos. El tamaño recomenado es entre 10 y 1000.
  • maxIter: Número máximo de iteraciones para parar si el algoritmo no coverge.

Creamos el método public void entrenar(Dataset<Row> datos, String columnaDeClasificacion, String… columnasDeDatos), que entrena la red con una tabla datos pasada.

La red intentará encontrar una manera de deducir el valor de la columna de clasificación a partir de los datos en las columnas de datos.

Primero dividimos la tabla de datos en datos de entrenamiento y de test.

El clasificador de la red neuronal necesita que los datos estén en una columna de vectores llamada “features” y que clasificación sea numérica y se llame “label”.

Esto lo conseguimos creando una pipeline en la que aplicamos 4 transformaciones a la tabla de datos:

Primero usamos VectorAssembler() para crear la columna features.

Segundo usamos StringIndexer() para indexar la columna de clasificación en la columna label.

Tercero definimos MultilayerPerceptronClassifier(), que es la red neuronal como tal.

Por último desindexamos los datos clasificados por la red neuronal con IndexToString().

Por último entrenamos la red neuronal y comprobamos su eficacia

Para acabar con la clase creamos el método que clasifica los datos una vez que ya se ha entrenado la red:

5. Probar y mejorar la red neuronal

Dividimos los datos que hemos procesado en el apartado 4.3 en entrenamiento y test para poder probar la eficiacia de la red que acabamos de crear.

Entrenamos la red neuronal con los datos que hemos procesado como entradas y con 3 posibles salidas: NO_PICOS, PICO o GRAN_PICO.

Definiendo una sola capa intermedia con 5 neuronas, veamos la eficacia de clasificación de la red.

Vemos que los resultados dejan bastante que desear, pero se pueden mejorar bastante con una configuración diferente de las capas intermedias. Con sólo cambiar:

Vemos que los resultados mejoran bastante.

Podemos mejorar estos resultados aún más, añadiendo un par de columnas de datos de entrada:

Vemos que llegamos a tasas de acierto cercanas al 100%

6. Conclusiones

Hemos visto lo relativamente rápido y sencillo que es crear con Spark una herramienta tan potente como es una red neuronal.

En este tutorial hemos usado una red neuronal, pero Spark nos da la posibilidad de usar otro tipo de clasificadores como árboles de decisión, Naive Bayes, etc, y la base sería la misma que hemos seguido.

7. Apéndice: árboles de decisión

Podemos cambiar nuestro clasificador y en vez de tener una red neuronal, ponemos un árbol de decisión. Para ello nos basta con cambiar la línea:

por:

Vemos la potencia de los árboles de decisión, ya que incluso sin añadir las columnas “resta” y “porcentaje”, tenemos tasas de aciertos mejores que con la red neuronal:

Dejar respuesta

Please enter your comment!
Please enter your name here