Ingeniería de datos. Como saber qué características o features dar como Inputs a nuestras redes neuronales
Una característica es una propiedad que se puede medir de un fenómeno (según la wikipedia).
Partiendo de esta definición totalmente acertada, os enseñaré cómo debemos preparar nuestros datos para crear un buen dataset, antes de empezar a trabajar con los modelos de redes neuronales para no tener datos inútiles, que el sistema no pueda manejar o que entorpezcan lo que tendría que ser un entrenamiento fluido y natural de nuestra red neuronal optimizado al máximo.
Quizás lo primero que se nos pasa por la cabeza es..... tengo un listado de artículos con precios, características, etc..... con muuuuuuuchos datos. Mientras más datos le de a la red neuronal mejor, no? así que le paso todos mis datos tal cual a mi red neuronal y que se espabile, a ver qué soluciones me da. Se trata de inteligencia artificial y para esto está, porque es inteligente y ya sabrá qué hacer con todos estos datos. No era tan difícil no? Que bueno soy y qué culito tengo!!!!. ERROR!!!!!
Si creías que todo iba a ser un camino de rosas, estás equivocad@. El análisis de datos es todo un arte. Lo primero que hay que hacer es mirar con qué datos contamos, mirar qué datos deseamos obtener de la salida de nuestra red neuronal, decidir si haremos un modelo de red neuronal de predicción o una red neuronal de regresión, limpiar los datos, eliminar o preparar nuevas características (features) 'virtuales' que ayuden al modelo a entender qué buscamos, normalizarlos, etc....
Los pasos suelen los siguientes:
- Seleccionar datos: integrar datos, desnormalizarlos en un conjunto de datos, recopilarlos juntos.
- Preprocesar datos: formatear los datos, limpiarlos, mostrarlos para que pueda trabajar con ellos.
- Transform Data: Feature Engineer (lo que realmente explicamos en este post, o sea, ingeniería de datos).
- Datos del modelo: crear modelos, evaluarlos y ajustarlos.
Así que paciencia, que tenemos un largo camino que recorrer antes de poder empezar a entrenar nuestra red neuronal.
Partiremos de este ejemplo. Imaginad que tenemos una base de datos así:
CÓDIGO:
¿Qué valor puede aportar este dato? No nos indica ningún valor sobre el producto (medida, tamaño, color, .... algo que podamos medir. En sí, el valor Código no nos indica absolutamente nada. No guarda ningún valor medible). Para que lo entendáis un poco más.... ¿qué obtenemos si multiplicamos 4001x5? ¿y si dividimos el 4002/8? Nada. Eso no existe. Este dato es simplemente un número de serie que la tienda se ha inventado para ese artículo (incluso en otra tienda podemos encontrar otro código diferente) y no nos sirve de nada hacer operaciones con él, además..... Imaginad que entra un artículo nuevo, un 'perrito piloto' y el señor le da el código 5607 ¿Qué podrá predecir nuestra red neuronal con este número nuevo? NADA. Entonces, este dato no nos sirve y NUNCA tendría que formar parte de nuestro dataset final.
Si nuestro código indicara algunas características como fabricante, categoria, etc... quizás lo podríamos aprovechar, pero para hacerlo deberíamos dividir el código en cada una de las partes que lo forman, dándole un valor numérico a cada una de ellas, creando nuevos campos en la tabla con estos valores y desechar el código inicial.
¿Entonces lo elimino? En el dataset original lo puedes guardar para localizar el dato y mostrar su código, pero para el dataset de entrenamiento no se podrá utilizar. No puede constar en el dataset de entrenamiento. Entonces, para el dataset de entrenamiento LO ELIMINAMOS.
¿Qué podemos calcular con una descripción? Realmente no es un valor medible, es un dato que sirve a los humanos para saber qué es el artículo, pero hablando en formato de cálculos matemáticos, no podemos calcular ningún valor con este dato, así que.... TAMPOCO tendría que figurar en nuestro dataset de entrenamiento.
NOTA: Veréis en algunos ejemplos siguientes que se utiliza la columna DESCRIPCIÓN. És solo a título identificativo de cada registro. Realmente esa columna no debe de estar, pero la utilizamos en estos ejemplos para explicar, entender y localizar los datos, visualmente lo tendremos que ver, por eso lo veréis a continuación en algunas tablas, per recordad.... en el dataset final de entrenamiento, NO DEBE DE CONSTAR el campo de descripción.
El color nos puede indicar algo. De hecho es concretamente una característica. Podría ayudanos a saber que artículos se venden más por color, pero ¿qué pasa con nuestros datos?. Existen artículos que no tienen color, como por ejemplo un antivirus. El color para ese artículo es irrelevante porque se trata de un software, y esa característica no se le aplica. Entonces. ¿Cómo podemos tratar este dato?
Primero, tendríamos que convertir el texto 'BLANCO', 'ROSA', ... en valores numéricos para poder clasificarlo numéricamente (ya sabéis que las redes neuronales entienden de números, pero no de textos), así que podríamos hacer algo así (los números que he utilizado son inventados):
Además, tendríamos que crear una nueva característica booleana (una nueva columna en nuestro dataset) indicando si se le aplica color (1) o no se le aplica el color (0), así podríamos decir:
Pero se puede mejorar aún nuestros datos de entrada aplicando nuevos campos, uno por cada color. Con esto ayudamos mejor a que nuestra red neuronal entienda cada una de las propiedades. Cualquiera de las dos opciones es válida, pero esta segunda se adapta muchísimo mejor al concepto de la red neuronal, por lo que el aprendizaje se optimiza a su comportamiento natural. Recomiendo esta segunda.
Ahora quedaría así:
STOCK:
El stock nos podría ayudar a preveer el número de ventas, cuando pedir más artículos, ya que lo podemos predecir gracias al número de ventas, el movimiento que tengamos de género, etc.... pero si se trata para saber qué color de artículos se vende más, quizás no lo necesitemos, así que, este campo podría valer o no, según el objetivo de nuestro modelo. Lo tendremos que decidir nosotros, ya que el resultado del aprendizaje podría mejorar o entorpecer con este valor.
¿Como sé si mi modelo de red neuronal necesita este dato o no? Esto con el tiempo y práctica, se puede intuir y se sabe automáticamente. Hay que intentar 'adivinar' si el modelo lo necesita o no. A veces puedes pensar que un dato no influye en el resultado, pero al incorporarlo te das cuenta de que ese dato era muy importante para acertar correctamente las predicciones. Muchas veces ya se intuye, y si dudas, haz pruebas. Recuerda que lo mejor es utilizar únicamente los datos necesarios para el resultado que deseamos. Un ejemplo. ¿Crees que el campo de fecha es importante para la previsión del IBEX35, o quizás es solo un campo que te indica cuando se cerró la borsa y en que estado han quedado los valores?. Pues piensa que quizás tenga relación, y algo (la intuición que hablábamos antes) te dice que ya tienes este dato para mejorar tu predicción, ya que no es lo mismo abrir la borsa un Lunes, que cerrarla en Viernes, empezando el fin de semana. ¿Habías pensado en esto? Ahora empiezas a entender la importancia de los datos y de su manejo antes de incorporar los datos en la red neuronal. Ya te habrás dado cuenta de donde sacamos el día de la semana..... de la fecha.... y hablando de fechas.....
FECHA DE ENTRADA:
Las fechas son muy importantes, así que entraremos en detalle en este campo. Una fecha tal cuál no nos indica mucho. Podemos saber cuando entró el artículo, pero imaginemos tener otras fechas en nuestros campos, como la fecha en que el artículo se vendió más, la última fecha de venta, .... Combinando todas las fechas podríamos tener datos muy, pero que muy valiosos.
Una fecha tal cual nos la da el sistema no la podemos utilizar directamente. En definitiva se trata de una tira de caracteres separados por barras o guiones, pero encierra muchísimos datos que si los desglosamos, sí los podemos utilizar por separado e individualmente, como veremos a continuación:
- Indica un día concreto del año (de 1 a 365). Podría ser que un artículo se venda más a principios de año pero menos a final, por ejemplo.
- Indica un día de la semana (Lunes, martes, ....) No es lo mismo quizás a principios de semana que al final. Y en este caso concreto, recordad, que mejor crear un campo 'virtual' booleano (1 o 0) para cada uno de los días de la semana en lugar de guardar un número de 0 a 6 o de 1 a 7.
- Indica un día del mes (de 1 a 31. Quizás, a final de mes, puede ser que se vendan menos artículos, pero podríamos detectar que casualmente hay algunos artículos en concreto que es a final de mes cuando se venden más, por ejemplo).
- Indica un mes del año (de 1 a 12: enero, febrero, marzo, ....). Al igual que el día de la semana, lo mejor es crear un campo 'virtual' booleano (1 o 0) para cada uno de los meses en lugar de guardar un número de 0 a 11 o de 1 a 12.
- Indica el año, (que no deja de ser otro valor numérico).
- Con el mes, además, podríamos saber si es temporada de primavera, verano, otoño o invierno, lo que podría ayudar a nuestro modelo.
- Podemos saber cuantos días, meses y años han pasado desde la fecha del dato en cuestión hasta HOY() (con un simple cálculo de resta podremos obtener estos 3 datos).
Además, muchas veces nos encontramos con campos de hora ( o que la hora forme también parte del campo de fecha ).
Se podría crear una nueva función numérica llamada Hora_de_dia para la hora pudiera ayudar a un modelo de regresión.
Se puede crear una nueva función ordinal llamada Part_Of_Day con 4 valores Mañana, Mediodía, Tarde, Noche con los límites de hora que se considere relevantes. Esto puede resultar útil para un árbol de decisiones.
Y con taaaaaaantos datos.... ¿y si los combinamos con el tiempo para saber la temperatura, lluvia, sol, etc.... de ese día y sacar más conclusiones? Ya veis que se van abriendo cada vez más posibilidades.
Las fechas y horas son ricas en estructura y si se sospecha que existe una dependencia del tiempo en los datos, tomaos vuestro tiempo y analizadlos.
Así que, cada vez que nos encontremos con una fecha, tendríamos que, para ir bien, desglosarla en todos los apartados anteriores (una característica o feature para cada apartado explicado).
PRECIO:
El precio es un valor matemáticamente válido, así que nos puede servir para muchísimas cosas, pero al igual que en stock, debemos decidir nosotros si lo utilizaremos en nuestro dataset o no. ¿Para qué nos serviría el precio si lo que queremos saber es color que más se venderá este año? a no ser que también deseemos saber lo que ganaremos, entonces sí que deberíamos incluirlo. ¿Entendéis el funcionamiento?
MEDIDAS:
En este caso, por ejemplo las tenemos todas juntas, por lo que no nos serviría. Este campo es una conjunto de valores separados por 'x', en definitiva, todo el campo se trata como un conjunto de texto, no de valores y no se puede tratar este campo directamente como un valor. Para ir bien tendríamos que separarlas en 3, o sea Ancho, Alto y Fondo, o sea, que tendremos que añadir a nuestro dataset estos 3 campos y obviar el original. Con estos 3 valores individuales, además, podríamos calcular el espacio en m3 que ocupa el artículo, añadiendo una característica extra a nuestro dataset con la multiplicación de estos 3 parámetros. Ahora, además de tener las medidas individuales, tenemos el volumen. Con este dato podríamos decidir a qué almacén enviaríamos ciertos artículos por su tamaño, según nos dijera nuestro modelo de red neuronal, por ejemplo. Nuestra red neuronal aprenderá la relación que tienen cada uno de estos campos con el nuevo virtual de volumen que hemos creado nosotros.
Es muy importante tener las medidas estandarizadas en toda la base de datos. Si en algún artículo tenemos centímetros y en otro tenemos metros, las debemos pasar al modelo o bien todas en centímetros o bien todas en metros. Esta norma vale para TODOS los campos de la base de datos. No quiere decir que si en una columna está centímetros y otra en metros, tengamos que pasar todas las columnas a la misma medida, simplemente debemos asegurarnos que todos los datos de una misma columna estén en la misma medida.
Por cierto.... qué medida le damos al software????? Ninguna, así que ya sabéis que hay que hacer. Hay que inventar un nuevo input virtual booleano que indique si tenemos medidas (valor 1) o no tenemos medidas (valor 0).
PESO:
El peso tiene el mismo sentido que las medidas. Todos los pesos deben de tener el valor en la misma medida (kg., gr., ....) No puede ser que un valor esté en Kg. y otro lo hayamos calculado en gramos. Otra cosa que debemos de tener en cuenta son los textos que tenemos en esta columna. Hay que eliminarlos y dejar únicamente el número (el valor). Debemos de entender que nuestra red neuronal sabe de números, pero no sabe nada de textos. O sea, lo de siempre, valores con los que pueda realizar operaciones matemáticas.
Y si decido en dividir este campo en dos, por ejemplo, uno para Kg y otro para Gramos, por ejemplo, si tengo 6,423 Kg, podría tener un campo para el 6 y otro campo con 423. Pues sí. Esto podría ayudar en ciertos modelos.
Aquí pasa lo mismo que con las medidas para el software. Qué pesa un software? Nada, así que ya sabéis que hay que hacer. Hay que inventar un nuevo input virtual booleano que indique si tenemos peso (valor 1) o no tenemos peso (valor 0).
Con todo esto aprendido, ahora sabemos que podemos manejar todos nuestros datos a nuestro antojo, incluso crear campos nuevos que digan bastante más que los datos originales.
Vale. Ya lo he entendido. ¿Ahora ya puedo darle todo esto para que 'coma' mi red neuronal? Tengo ganas de ver los resultados.
Pues no. Hay más cosas que hacer. Hasta ahora nos hemos asegurado de tener las columnas necesarias y los valores correctos, todos en su medida para que no estén mezclados entre ellos. De esta manera ya tenemos datos coherentes para empezar, pero......
¿Has revisado el contenido de los datos? Normalmente nos encontramos con datos erróneos en los contenidos. Hay que revisar que todos estén correctos, ya que si no, cualquier incoherencia encontrada en esos datos hará 'bailar' a nuestra red neuronal y obtener resultados no deseados. Por lo que.....
1) Revisa que no exista algún dato erróneo en alguna casilla (este paso parece tedioso pero de suma importancia. No des a tu red neuronal datos erróneos, o no habrá servido para nada toda la preparación de los datos. Este paso puede ser la diferencia entre el éxito o el fracaso, así que, tómate tu tiempo aquí).
2) Revisa que no te hayas dejado un dato en un campo que no tocaba, que estén todos ordenados en sus correspondientes casillas.
3) Revisa que los datos estén todos en su medida
4) Revisa que no falten datos, que estén incompletos o que estén en blanco
¿Qué hago si me faltan algunos datos en el dataset o me encuentro con datos incompletos?
Respecto a los puntos 1, 2 y 3, solo hay que prestar un poco de atención y rectificarlos, pero, ¿qué pasa con el punto 4? Son datos en definitiva que posiblemente podríamos aprovechar en nuestro dataset, y si además contamos con pocos datos, no estamos para desperdiciarlos, pero todo y así nos falta algún dato concreto. Tenemos que mirar de aprovecharlos. ¿Qué hay que hacer en estos casos?
1) Si tenemos suerte de tener muchísimos datos para realizar el aprendizaje (train), se pueden eliminar directamente. Unos cuantos datos de menos no los encontrará a faltar porque ya tiene suficiente información donde aprender, así que procediríamos a eliminar toda esa fila con datos faltantes. NUNCA elimines datos y dejes alguna columna suelta para que la aproveche el sistema. Esto sería un desastre seguro para nuestra red neuronal.
2) Inventarnos los datos. ¿Esto se puede hacer? En muchas ocasiones sí. Se suele buscar la media en otros datos que tengamos y conseguir un dato 'inventado' aproximado, y en definitiva, válido. Debemos de ser coherentes con los datos resultantes. Si vemos que existe mucho error en estos datos inventados, o bien que no los podemos invetar (o que no deberíamos porque sabemos que serían datos no reales y haría variar mucho al modelo), deberíamos proceder al paso 3.
3) Aplicar la técnica que hemos visto antes de 'inventar' características (features) extras booleanas, para indicar si este dato hay que aplicarlo o no. Así, cuando tengamos ese dato, usaremos un 1 en la nueva feature, y en caso de no disponer del dato, en nuestra nueva feature le aplicaremos un 0.
Vale. Ya tengo mis datos limpios, ordenados y revisados. ¿Puedo empezar ya?
Aún no. Tranquil@. Faltan pequeñas cositas.
Ahora nos toca REGULARIZAR los datos del dataset. ¿Qué significa esto? pues imaginemos que queremos calcular el precio de la vivienda en Madrid Comunidad. ¿Qué pasaría con los pueblecitos que están al lado?, que quizás el precio difiere mucho del valor en el centro de Madrid, además del número de viviendas concentradas, por lo que los datos no estan muy bien repartidos. En estos casos se recomienda normalizar los datos para que no exista tanta diferencia entre ellos. Existen muchos métodos para hacerlo que no explicaré aquí (debemos de ser un poco creativos para encontrar una fórmula que se adecue bien a nuestro modelo), pero debemos de saber que tendríamos que tener unos valores dispersados entre los márgenes y no concentrados todos en un mismo punto, Para que entendamos este concepto (me invento los datos. No son reales, pero es para poner un ejemplo), podríamos tener 10.000 viviendas concentradas en el centro de Madrid. Nuestra red neuronal nos daría un valor próximo a 1. Por otro lado tenemos las 5.000 viviendas restantes concentradas todas casi al valor 0, por lo que tendríamos que realizar una curva de precios más o menos continua. Podríamos agrupar en márgenes de precios, por ejemplo y tendríamos una curva escalonada. Si no lo hacemos, nuestra curva de precios puede ser exponencial y nos puede hacer variar el resultado de salida de nuestra red neuronal.
Vaaaaale. Ya tengo mis datos limpios, ordenados, revisados y regularizados..... ¿ya? ¿Puedo empezar ya?
Casi, pero aún no.
Los modelos aprenden mejor y más rápido si les damos los datos en su margen de operación. Sabemos que si estamos utilizando la función de activación sigmoidal (sigm), los valores posibles son entre 0 y 1, y si utilizamos la función de activación tangente (tanh), los valores irán de -1 a 1. Así que lo que nos toca ahora es NORMALIZAR.
Esto implica pasar de los valores originales de cada campo a un rango de valores entre 0 y 1 (en caso de utilizar la función de activación sigmoidal), o bien entre -1 y 1 (en caso de utilizar la función de activación tangente tanh).
Recordemos que los resultados también serán entre 0 y 1 o bien entre -1 y 1, por lo que para después saber el resultado correcto, tendremos que DESNORMALIZAR estos resultados para encontrar los valores esperados próximos a los datos reales del dataset original.
- FECHAS EN PREDICCIÓN FUTURA
Cuando normalizamos los datos, haremos que los márgenes vayan de 0 a 1, por ejemplo, para el caso de utilizar sigmoide, pero en el caso de fechas futuras, debemos implementar un margen para poder predecir correctamente. Me invento un modelo con la fecha máxima del día 06/08/2021, este será el valor 1, y si situamos la fecha menor en el 01/08/2021, este será el valor 0. ¿Qué pasará entonces cuando le pidamos una fecha futura fuera del alcance de nuestro modelo? Si le pedimos el 10/08/2021, nos hemos sobrepasado del valor 1 (recordemos que trabajamos entre márgenes de números decimales entre 0 y 1), por lo que el modelo podría fallar.
Yo recomiendo guardar un margen para fechas futuras máximas, entonces, los márgenes entre 0 y 1 para estos valores de las fechas quedarían algo así:
Entonces..... mirando todo esto.... le pongo una fecha máxima del 31/12/2100 por ejemplo y santaspascuasplin!!!!! NOOOOO ERROR OTRA VEZ. No nos podemos desplazar mucho de la última fecha del dataset. De hecho, el ejemplo que os he dado no es muy adecuado, porque se pasa un 40% de la última fecha, pero nos sirve perfectamente para explicar esto. Yo recomiendo dejar un margen de un 5%, por lo que una fecha correcta en el caso anterior, hubiese sido el 07/08/2021 como máximo.
Para poder utilizar fechas futuras, lo mejor es ir entrenando continuamente nuestra red neuronal con los datasets anteriores y con nuevos datasets que iremos recopilando con el tiempo, e ir ampliando paulatinamente las fechas futuras. Si, es mucho tiempo de entrenamiento, pero pudiendo dejar un buen margen de fechas se tendrá que realizar en pocas ocasiones. Todo dependerá del número de datos disponibles. Si son pocos, no tenemos margen. Si son muchos, quizás dispondremos de bastante margen. Otra opción sería crear otro nodo de entrada virtual que iremos incrementando cada vez que realizamos nuevos aprendizajes, así no tenemos que volver a realizar todo el aprendizaje completo, pero esto es un poco más complejo de aplicar y la intención no es centrarnos en estas casuísticas. Investigad o probad un poco de vuestra propia invención. Ya veréis que no es imposible y quizás tampoco era tan complicado.
Bueeeeeeeno a ver qué tenemos aquí. Ya tengo mis datos limpios, ordenados, revisados, regularizados y normalizados. ¿Ahora sí?
Bueno.... pues en principio sí. Pero me gustaría realizar alguna recomendación más.
Pensad siempre que se pueden jugar con los datos. No deis por sentado que los datos que tenéis irán directamente al dataset de train. Algunos los eliminaremos. Otros nos los 'inventaremos'. Otros los modificaremos. Otros los combinaremos. Podemos tener por ejemplo la latitud y la longitud como valores.... ¿estáis pensando lo mismo que yo? ¿qué pasaría si los multiplicamos? Que tenemos un valor nuevo totalmente válido y útil para nuestra red neuronal.
Jugad con los datos. Muchas veces tienen relación entre ellos y no nos damos cuenta. La mayoría simplemente hace falta hacer una multiplicación entre ellos (¿recordáis para calcular el volumen que hemos hecho antes? Una multiplicación de tres valores). Tampoco os pongáis a multiplicarlos todos sin ningún motivo aparente. Esta observación hace referencia únicamente a valores que creáis o que puedan estar relacionados entre sí. Si veis que no obtenéis el resultado que esperabais, probad de eliminarlos o modificarlos de otras maneras.... Prueba y error.
Veréis que chulo quedará vuestro dataset listo para entrenar y ya veréis los resultados tan óptimos que podréis tener. Lo único que pasará ahora es que habremos incrementado considerablemente el número de neuronas de entrada, por lo que lo que premiará ahora será el tiempo empleado según el modelo que configuréis. A más datos, más tiempo.... es lo que tiene.
En definitiva, todo se reduce a tener un poco de coherencia con los datos.
Una última recomendación con vuestro dataset. Tened cuidado con los datos que enviáis al sistema en .csv, ya que los formatos numéricos os podrían dar problemas debido a que algunos sistemas utilizan '.' y otros ',' para separar decimales o millares. NUNCA utilicéis la separación de miles, y mi recomendación para los .CSV es:
IMPORTANTE PRÓXIMAMENTE PARA TODOS VOSOTROS:
Estoy preparando una aplicación en PHP para ayudar a realizar todos estos trabajos con datos y conseguir unos datasets correctos, coherentes y de calidad. Os ayudará a preparar los campos de las fechas, formatear, normalizar, regularizar o detectar campos erróneos. Tan pronto como pueda os lo enlazaré.
Por último y ya para terminar, os recomiendo unas lecturas muy entretenidas y super interesantes sobre preparación de nuestros datasets donde podréis adquirir más ideas para conseguir unos datasets excelentes, robustos, coherentes y de calidad:
https://towardsdatascience.com/feature-engineering-for-machine-learning-3a5e293a5114
Foto Wikipedia
Comentarios