Aprendizaje Con Redes Neuronales Con R
Aprendizaje Con Redes Neuronales Con R
Aprendizaje Con Redes Neuronales Con R
Este es un trabajo sobre redes neuronales y R, sin asumir ningún conocimiento previo del
tema salvo que se tiene R instalado en el sistema y se sabe acceder a su consola para
ejecutar órdenes.
Una red neuronal artificial (ANN, Artificial Neural Network), asumiendo que tenga la
estructura adecuada y cuente con un algoritmo de aprendizaje correcto, es capaz de
aprender a realizar teóricamente cualquier operación, sin necesidad de que le facilitemos su
formulación matemática. Solo necesita que le facilitemos varios ejemplos de la salida que
se produce a partir de ciertas entradas. Si los ejemplos son suficientes y representativos del
problema, tras el aprendizaje la red neuronal será capaz de efectuar dicha operación con
una alta precisión.
Cabe preguntarse la razón por la cual querríamos usar una ANN en lugar de una función
capaz de efectuar la operación con total acierto. Esto último, sin embargo, solo es posible si
conocemos la formulación del problema y, además, somos capaces de trasladarlo a un
lenguaje para implementarla en software. Hay muchas situaciones en las que se conocen las
salidas que producen unas ciertas entradas, pero la función que se aplica a estas últimas
para generar las primeras es desconocida o bien resulta muy compleja de calcular. Es en
estas situaciones en las que una ANN (u otro modelo de minería de datos, pero aquí nos
centramos en las ANN) son considerablemente útiles.
library(neuralnet)
Si la respuesta fuese que el paquete no está disponible, tendremos que instalarlo con el
comando install.packages('neuralnet'), tras lo cual volveríamos a ejecutar el la
orden anterior.
Este paquete de R cuenta con una función, llamada neuralnet(), que a partir de una serie
de ejemplos se encarga de conducir el aprendizaje de la ANN, devolviendo esta como
resultado. A partir de ahí es posible facilitarle solo los datos de entrada para obtener la
salida correspondiente.
Datos de ejemplo
Lo primero que necesitamos son algunos datos de ejemplo que permitan a la ANN aprender
la función. Para ello vamos a preparar un data.frame (un tipo de dato de R similar a una
matriz) con 100 filas, un centenar de ejemplos, cada una de las cuales tendrá tres columnas.
Las dos primeras contendrán las medidas de dos catetos y las obtendremos así:
head(data)
## Cat1 Cat2
## 1 9 7
## 2 9 3
## 3 4 3
## 4 8 5
## 5 7 9
## 6 6 10
La función runif() devuelve valores de una distribución uniforme. En este caso concreto
le solicitamos 100 valores entre 1 y 10 para cada uno de los catetos. Podemos comprobar
cuál es el contenido del data.frame simplemente escribiendo en la consola data y
pulsando Intro. Esto mostraría las 100 filas. Para comprobar la estructura es suficiente con
algunas de ellas, devueltas por la función head().
El data.frame debe tener una columna adicional con la hipotenusa correspondiente a cada
pareja de catetos. La añadimos de la siguiente manera:
head(data)
## Cat1 Cat2 Hyp
## 1 9 7 11.401754
## 2 9 3 9.486833
## 3 4 3 5.000000
## 4 8 5 9.433981
## 5 7 9 11.401754
## 6 6 10 11.661904
Esos índices, un tercio del total, serán los correspondientes a los ejemplos que usaremos
para validación. Cuando se trabaja con un data.frame es posible acceder a cualquier dato
individual mediante la notación variable[nfila, ncolumna]. También es posible
obtener múltiples datos simultáneamente, usando como índices vectores de números. Si el
número de fila o columna es omitido se asume que se tomarán todas las filas o todas las
columnas, según el caso.
head(test)
## Cat1 Cat2 Hyp
## 92 2 2 2.828427
## 93 2 6 6.324555
## 29 7 8 10.630146
## 81 8 4 8.944272
## 62 5 6 7.810250
## 50 9 1 9.055385
head(train)
## Cat1 Cat2 Hyp
## 1 6 4 7.211103
## 2 6 6 8.485281
## 3 8 7 10.630146
## 4 8 4 8.944272
## 5 7 7 9.899495
## 6 8 3 8.544004
Con la expresión data[fold.test, ] estamos tomando del data.frame original las tres
columnas de las filas cuyos índices contiene fold.test. La expresión data[-fold.test,
] es similar, pero tomando las filas cuyos índices no están en el vector fold.test. De esta
forma obtenemos dos conjuntos disjuntos de ejemplos, uno en la variable train y otro en
la variable test. Ambas son objetos data.frame, con las mismas tres columnas que data.
Los dos primeros parámetros son obligatorios. El primero es una fórmula mediante la que
se indica qué variables son predictoras (los catetos) y qué variables se van a predecir (la
hipotenusa). La sintaxis es simple: disponemos los nombres de las columnas del
data.frame que contienen datos a obtener como resultado de la red neuronal, en este caso
es solo una, separados entre sí mediante el operador +. A continuación, tras el símbolo ~, se
facilitan las variables de entrada de la misma manera. El segundo parámetro es el
data.frame que contiene las variables a las que se hace referencia en la anterior fórmula.
ann
## Call: neuralnet(formula = Hyp ~ Cat1 + Cat2, data = train, hidden =
10, rep = 3)
##
## 3 repetitions were calculated.
##
## Error Reached Threshold Steps
## 1 0.006725962 0.009883311 7593
## 3 0.012119691 0.009604500 8799
## 2 0.028969166 0.009210339 28154
Por cada repetición se indica el error cometido, el umbral alcanzado y el número de pasos
que ha demandado el aprendizaje. El número de pasos, también conocido como épocas,
puede limitarse o ampliarse, lo cual afectará a la precisión de la ANN. Durante el proceso
de aprendizaje se usa un algoritmo que determina, para las entradas facilitadas, el error que
ha cometido la ANN en su salida. En función de la magnitud de ese error se ajustarán los
pesos asociados a las conexiones entre las neuronas de las distintas capas. Esto provoca que
para unos valores de entrada se genere una cierta salida que en el futuro, tras efectuar
cambios en dichos pesos como resultado del procesamiento de otros ejemplos, será distinta.
Para estabilizar la red se procesan los mismos ejemplos de manera reiterada y en distinto
orden, hasta que se converge a un cierto umbral de mejora o se alcanza un máximo de
pasos.
Las posibles funciones de activación y su naturaleza, así como la forma en que se ajustan
los pesos que conectan las neuronas, son temas a analizar con más detalle con posterioridad
Además de el diagrama de la red con sus conexiones y pesos, también podemos utilizar la
función gwplot() para obtener una representación de los pesos generalizados (GW,
Generalized Weights) de una variable de entrada respecto a una de salida. Esto nos
permitiría, por ejemplo, determinar qué variable aporta más peso a la predicción de una
salida. En nuestra ANN de ejemplo, tras aprender la función hipotenusa, no es de extrañar
que ambas variables predictoras tengan una aportación muy similar a la única salida
existente, tal y como se aprecia en las siguientes gráficas.
par(mfrow=c(1,2))
gwplot(ann, selected.covariate = 'Cat1', rep = 'best')
gwplot(ann, selected.covariate = 'Cat2', rep = 'best')
En el siguiente ejemplo tomamos de la variable test, que contenía datos que nos hemos
usado para el entrenamiento de la red, las variables Cat1 y Cat2 y se las facilitamos a la
mencionada función. Guardamos el resultado, las predicciones hechas por la ANN, en una
variable. A continuación generamos una tabla de resultados mostrando en la primera
columna el valor real de la hipotenusa, calculado al inicio con la fórmula estándar, en la
segunda el valor predicho por la ANN y en la tercera el error cometido.
Tenemos, por tanto, una ANN que ha aprendido la fórmula de cálculo de la hipotenusa a
partir de un conjunto de ejemplos, con capacidad para calcularla con una precisión bastante
aceptable. De hecho, si nos quedásemos solo con los dos primeros decimales en muchos
casos no habría error.