Redes Neuronales: Un ejemplo práctico en R
Estos últimos años hemos visto un impulso notorio de la inteligencia artificial (AI) y sus aplicaciones. Es frecuente encontrar conceptos como Machine Learning, Algoritmos de Aprendizaje Automático, Natural Languague, Conversational Computing Platforms, por mencionar unos pocos relacionados con el Big Data y el Data Analysis. Si bien el fenómeno es relativamente reciente en sudamérica (quizá aún más en Chile) lleva varios años en el mercado y se ve revitalizado por los enormes avances en potencial de cómputo que ha logrado la industria. Dentro de estos avances, probablemente uno de los más importantes después de la introducción de los GPU en vez de los CPU es el primer computador cuántico comercial desarollado por IBM permitiendo un creciente acceso a una cada vez más elevada capacidad de procesamiento considerando que un computador tradicional trabaja sobre la base de bits con un valor binario entre 0 y 1 mientras que un computador cuántico trabaja con qubits que puede tomar ambos valores a la vez o mezlcas entre ellos, y ya sabemos de la escuela que -en el conjunto de los Reales al menos- los valores entre un número y otro son infinitos.
Uno de esos conceptos populares que está tomando fuerza son las Redes Neuronales (Neural Network, NN, ANN, etc) Para responder qué son y por qué están volviendo con algo más de detalle sugiero este buenísimo artículo de Xataka sobre el tema. Pero en este breve artículo me gustaría mostrar un ejemplo práctico a modo de comprender su potencial y aplicación a los negocios.
¿Qué son las Redes Neuronales?
En primer lugar, es ya cliché mencionar que las redes neuronales son algoritmos de aprendizaje automático que intentan emular el procesamiento del cerebro humano utlizando incluso conceptos como neuronas, sinapsis, etc. Formalmente, me gusta la definición exacta de Simon Haykin en su tercera edición de Neural Networks and Learning Machines de Pearson (una obra monumental de casi 1000 páginas)
Haykin entonces define las redes neuronales como sigue:
A neural network is a massively parallel distributed processor made up of simple processing units that has a natural propensity for storing experiential knowledge and making it available for use.
Pero dijimos que esto era un artículo con el fin de entender para qué sirven y su aplicación a los negocios así que parafraseando a Haykin debemos decir que una Red Neuronal es un algoritmo de aprendizaje automático capaz de guardar datos provenientes de un experimento para aprender de ellos y calibrar futuras respuestas predictivas (en una de sus muchas aplicaciones). Es en esencia, un modelo probabilístico y no determinístico. Para terminar con las definiciones el concepto mismo de red neuronal tiene sus orígenes en los intentos por encontrar representaciones matemáticas de fenómenos biológicos (véase Widrow y Hoff, 1960; Rosenblat et al., 1968, entre otros) por lo que hablar de redes neuronales es hablar de un concepto amplio para describir varios modelos relacionados con el aprendizaje automático, pero nos concentraremos en uno de sus tipos más básicos y de probada efectividad que es el perceptrón y más formalmente, el perceptrón multicapa. El perceptrón es una forma básica de una red neuronal que contiene los elementos de la foto de portada. Una capa de entrada, una capa oculta de procesamiento y una capa de salida. Entonces, el problema a solucionar se vuelve uno de parametrización, donde el objetivo es encontrar los pesos adecuados para calibrar nuestro modelo y obtener resultados coherentes
Xataka plantea un divertido ejemplo para que quede aún más clara la utilidad del perceptrón. Digamos que dada 2 calificaciones en ciertos exámenes, usted obtiene una nota final y no sabe qué peso/porcentaje se le asignó a cada examen y por consiguiente no puede entender su calificación final. ¿Podemos encontrar esos pesos de manera relativamente simple y eficiente? Pues a través de una red neuronal puede encontrar la respuesta a esa pregunta.
Planificando una plantación
Y de una vez vamos al ejemplo para entender cómo podemos entrenar una red neuronal de manera de tener un modelo útil para el negocio. Considérese que este ejemplo es bastante básico dado que buscamos meramente ilustrar el potencial de una red neuronal y su aplicación.
Se encuentra con la tarea de planificar, por ejemplo, la producción de cierto campo de abetos y dentro de los datos que maneja tiene información de la edad y volumen de los árboles. Considerando que para usos medicinales del Abeto blanco -por ejemplo- se suelen utilizar las yemas, corteza y hojas pues poseen aceite esencial, resinas, taninos y vitamina C, podemos decir que hay un importante valor comercial asociado a una plantación de abetos.
Pues bien, una pregunta válida sería ¿Cómo puedo estimar el volumen de mis árboles dada la edad de los mismos? Una forma de responder a esto sería contratar a un buen estadístico/matemático para que realice un análisis de regresión que no es otra cosa que un método estadístico para el estudio de la interdependencia entre variables. Entonces, a través de este estudio usted podría determinar cómo se relaciona la edad con el volumen y proyectar algunos resultados... Pero ¿Qué tal hacerlo más rápido y fácil? ¿Qué tal si entrenamos una red neuronal con ciertos datos ya conocidos y le pedimos que prediga los valores del volumen de un árbol dada una edad?. Pues bien, manos a la obra de la mano de R y de la librería neuralnet disponible.
Lo que haremos, en concreto, será tomar datos históricos de Edad y Volumen para hacer lo siguiente:
- Entrenar una red neuronal con una porción de los datos (a lo que llamaremos Conjunto de Entrenamiento)
- Explorar el error asociado a la predicción de nuestro modelo para determinar la configuración de neuronas en la(s) capa(s) oculta(s)
- Finalmente, evaluaremos nuestras predicciones contrastando con la porción no utilizada en el conjunto de datos (será nuestro conjunto de prueba)
De lo anterior, tendremos 65 pares de datos de entrenamiento para luego contrastar con 12 pares de datos de testeo (el conjunto de prueba). Para los interesados sigue el código utilizando R (es un código bastante poco elegante y básico dado que no soy un experto en R)
library(neuralnet)
#cargamos manualmente en R los datos de arboles desde arboles
datos_in <-arboles[112:164, 2:3]
datos_out <-arboles[165:176, 2:3]
#la normalizacion es necesaria para no obtener resultados saturados
normalize <- function(x) {
return ((x - min(x)) / (max(x) - min(x)))
}
#Guardamos las contanstes para después volver desde la normalización
maxx_age <- max(datos_in[,1])
minn_age <- min(datos_in[,1])
maxx_vol <- max(datos_in[,2])
minn_vol <- min(datos_in[,2])
back_normalizeVol <- function(x) {
return (x*(maxx_vol - minn_vol) + minn_vol)
}
back_normalizeAge <- function(x) {
return (x*(maxx_age - minn_age) + minn_age)
}
#normalizando datos de entrada y de testeo
maxmindf <- as.data.frame(lapply(datos_in, normalize))
maxmindf_test <- as.data.frame(lapply(datos_out, normalize))
La normalización de los datos es solo un paso estándar para la correcta virtualización del input/output del problema. Ahora, con los datos preparados comenzamos definiendo los conjuntos para la red neuronal.
#generando los datos de entrenamiento para la red neuronal ya normalizados
traininginput <- maxmindf[,1]
trainingoutput <- maxmindf[,2]
#reuniendo los datos en un solo dataset
trainingdata <- cbind(traininginput,trainingoutput)
colnames(trainingdata) <- c("Age","Vol")
A continuación definiremos una matriz para ir guardando el error asociado al número de neuronas en una capa oculta. Para aquellos que quieran profundizar el método que estamos empleando aquí es el conocido como Backpropagation
#está matriz tendrá el error asociado al número de neuronas
error_red <- matrix(0,nrow=50,ncol=2)
for (j in 1:50)
{
red_n <- neuralnet(Vol ~ Age, trainingdata, linear.output=FALSE, hidden=j, threshold=0.01)
#Probando la red contra los datos de testeo
testdata <- maxmindf_test[,1]
net.results <- compute(red_n, testdata)
#Preparando los datos para visualizar de vuelta en formato de volumen y no normalizado
back_datos_out <- matrix(0,nrow=12,ncol=2)
resultados <- matrix(0,nrow=12,ncol=1)
error_red[j,1] <- j
error_red[j,2] <- red_n$result.matrix[1]
}
Desde aquí daremos un salto grande, probando diferentes combinaciones y luego ampliando el parámetro hidden de la siguiente manera: hidden=c(14,j) que significa 14 neuronas en la primera capa oculta y buscando un valor óptimo de neuronas (j) en la segunda capa oculta e incluso probando hidden=c(14,x,j) (3 capas ocultas con 14, x y j neuronas) buscando aún más combinaciones solo con fines de exploración. Finalmente, llegamos a la conclusión de utilizar solo una capa oculta y 14 neuronas (o también datos de entrada). Lo anterior es consistente con la experiencia que nos dice que la mayoría de los problemas requieren a lo más una capa oculta y un número limitado de neuronas en la misma.
Volviendo al ejercicio de ir guardando el error, al gráficarlo con respecto al número de neuronas en la primera capa obtenemos lo siguiente:
Un observador cualquiera sin mayores esfuerzos puede identificar una tendencia a partir de las 13 neuronas en la capa oculta. En particular, se genera una convergencia del error alrededor del 0.042~0.044 a partir de las 14 neuronas. Luego, realizamos el entrenamiento con dicha cantidad, devolvemos los datos a su formato original antes de la normalización y comparamos los resultados de nuestro modelo con aquel conjunto de prueba. En el mismo -por ejemplo- sabemos que un árbol con una edad de 92,06 años tiene un volumen de 2411 cm3.
red_final <- neuralnet(Vol ~ Age, trainingdata, linear.output=FALSE, hidden=14, threshold=0.01)
#Probando la red contra los datos de testeo
testdata <- maxmindf_test[,1]
net.results_final <- compute(red_final, testdata)
#Preparando los datos para visualizar de vuelta en formato de volumen y no normalizado
back_datos_out <- matrix(0,nrow=12,ncol=2)
resultados <- matrix(0,nrow=12,ncol=1)
for (i in 1:12)
{
back_datos_out[i,1] <- back_normalizeAge(maxmindf_test[i,1])
back_datos_out[i,2] <- back_normalizeVol(maxmindf_test[i,2])
resultados[i] <- back_normalizeVol(net.results_final$net.result[i])
}
cleanoutput <- cbind(back_datos_out[,1],back_datos_out[,2],
resultados)
colnames(cleanoutput) <- c("Age","Vol Esperado","Vol proporcionado por la Red Neuronal")
print(cleanoutput)
#preparando gráfico para visualizar los resultados esperados vs los de la red
x <- back_datos_out[,1]
y1 <- back_datos_out[,2]
y2 <- resultados
plot(x, y1, type = "l", col = "red", main="Comparación Valores Esperados vs Valores de la Red", xlab="Edad"
, ylab="Volumen")
lines(x, y2, col = "blue")
#Ploteo y detalles de la red neuronal
plot(red_final)
print(red_final)
Lo que obtenemos es una tabla con los valores de la edad de un árbol, el valor esperado (del conjunto de prueba) y la predicción de nuestro modelo.
Veamos estos resultados gráficamente,
Podemos observar que se comporta bastante bien en términos generales y que se nos escapa un poco en los extremos. Al margen de todos los factores y estrategias que podemos utilizar para mejorar la exactitud de la predicción, lo fundamental es que en un tiempo relativamente corto y con una confiabilidad bastante buena tenemos al menos un rango adecuado para responder a la pregunta inicial de estimar el volumen de un árbol abeto dada su edad. Y lo mejor de todo esto es que podemos mejorar los datos de entrenamiento o incluir otras variables para el mismo y responder toda clase de preguntas.
Finalmente, ¿cómo se ve nuestra red neuronal ahora con 14 neuronas en la capa oculta? Es un lindo perceptrón que se puede visualizar a continuación:
Conclusión
¿Puede reconocer todas las aplicaciones para una red neuronal en su negocio? Por ejemplo, digamos que tiene una flota de transporte y necesita estimar el rendimiento o consumo de combustible de acuerdo al kilometraje considerando los datos históricos como edad del vehículo, número de mantenciones, rendimiento real, etc... O tal vez pensando en invertir en un nuevo local necesita predecir un precio adecuado para ofertar dada la información de precios de las propiedades en el sector relacionando datos como superficie, antigüedad, ubicación, entre otros con precio de la propiedad? o ¿Qué tal predecir los precios adecuados para ciertos productos considerando las ofertas que ha recibido en el pasado de acuerdo a ciertos atributos del mismo? ¿Qué atributos del producto tienen mayor valor para el cliente de acuerdo a las ofertas recibidas?
Hay un sin número de aplicaciones prácticas para esta forma básica de red neuronal y la complejidad está apenas creciendo de la mano de la potencia computacional.
Siendo mi primer artículo en linkedIn aprecio cualquier crítica o retroalimentación de la red y sobre todo si hay expertos en el tema que puedan complementar o corregir las ideas compartidas aquí.