Redes neuronales y algoritmos evolutivos I
Encontrar la topología más adecuada para la red neuronal que pretendemos aplicar a un determinado problema puede resultar un trabajo tedioso de prueba y error, además de acabar produciendo una red poco optimizada. Para automatizar este proceso podemos recurrir a los algoritmos evolutivos, inspirados en la selección natural de los organismos vivos, que nos pueden facilitar enormemente el trabajo.
En este tipo de procedimientos, primeramente definimos de alguna manera unos elementos que generan los objetos cuya estructura y propiedades pretendemos optimizar, a los que llamamos genes. En el caso de una red neuronal, existen muchas propiedades que pueden ser definidas por uno de estos genes, como el número de capas de neuronas, el número de neuronas de cada capa, el tipo de funciones que utilizaremos para activar las neuronas y sus parámetros, o incluso los propios pesos de las entradas de cada neurona. Cuantos más parámetros diferentes determinen los genes, más complicado será el proceso y más tiempo llevará, por lo que no hay que empezar siendo demasiado ambiciosos.
Cada gen producirá, por lo tanto, una red neuronal con una configuración diferente. Esto nos permitirá tener una población de redes neuronales a las que someter a entrenamiento y prueba, para determinar cuáles de las configuraciones producen mejores resultados. Esto significa que necesitaremos aplicar cada una de ellas al conjunto de datos de entrenamiento y test, y determinar de alguna manera su posición en un ranquin; para ello, se define lo que llamamos una función de coste, que devolverá un único valor como resultado, el coste de la red. El objetivo es encontrar una red cuyo coste sea mínimo. El cálculo del coste no tiene reglas fijas, dependerá de nuestras preferencias y del problema que estemos tratando de abordar. Si nos interesa una red con el menor número posible de neuronas, usaremos este valor para penalizar la función de coste, lo mismo podemos hacer con el número de capas ocultas; otros valores que nos puede interesar utilizar en el cálculo son los relacionados con los errores de la red: errores mínimo, mediano y medio, desviación típica de los errores, o porcentaje de fallos sobre la muestra de entrenamiento. Asignaremos un peso a cada uno de los valores utilizados para el cálculo, en función de la importancia que le demos, y devolveremos la suma de todo ello como el coste final de la red.
En la primera iteración de nuestro sistema, se generará una población de genes aleatoriamente, y habrá que evaluar el coste de todos ellos para ordenarlos de menor a mayor coste. Esto es lo que se conoce como la primera generación, pero la cosa no acaba ahí; como en la reproducción de los seres vivos, en la que se inspiran esta clase de procedimientos, debemos seleccionar los mejores individuos y obtener una nueva generación tratando de conservar lo mejor de la actual. Para ello, podemos realizar diferentes operaciones entre los genes actuales para obtener otros nuevos: el cruce y la mutación.
El cruce de genes consiste en la operación de tomar dos genes diferentes y generar otro que contenga parte de los dos. Para ello, decidimos por medio de alguna regla que valores de parámetros del primer y del segundo gen usamos para construir el nuevo. Por ejemplo, si los genes definen el número de neuronas de cada una de las seis capas de una red neuronal, podemos decidir crear un nuevo gen con los valores de las tres primeras capas del primero y las tres últimas del segundo. Esta operación puede seguir reglas fijas o decidirse según algún valor aleatorio. No existe ningún criterio a priori, aunque es conveniente asegurar una cierta diversidad en los genes de las nuevas generaciones.
La mutación consiste en modificar alguno de los parámetros de un gen, bien en una pequeña cantidad o bien a un valor completamente diferente. Normalmente esta operación se realiza solo en un pequeño número de ocasiones, con una probabilidad baja, pero las reglas tampoco son fijas. La mutación ayuda a diversificar la población y probar combinaciones nuevas.
Estas dos operaciones se pueden llevar a cabo entre los mejores genes, entre los mejores y el resto de la población, o entre genes seleccionados aleatoriamente. Conservaremos los mejores individuos de la generación actual y eliminaremos los peores, de manera que, al final, tengamos de nuevo una población igual a la inicial. Con esta nueva población, repetiremos de nuevo el proceso. Como he dicho antes, es importante conseguir la suficiente diversidad genética en la población, ya que el objetivo de este tipo de algoritmos es explorar un espacio de soluciones que suele ser muy grande, por lo que un sistema mal diseñado puede acabar quedando limitado a la exploración de solamente una pequeña parte del mismo. Puede resultar de utilidad crear algunos genes de manera totalmente aleatoria, como en la primera generación, para introducir genes totalmente nuevos en la población.
Es evidente que esto implica bastante tiempo de proceso. Aunque no realicemos un entrenamiento en profundidad de la red, habrá que realizar suficientes iteraciones (en el caso del algoritmo de retro propagación, por ejemplo) como para poder tener unos valores razonables para calcular la función de coste. El tamaño de la población máxima de redes que vamos a mantener y los parámetros de entrenamiento se deben seleccionar con vistas al tiempo del que disponemos para obtener los resultados.
Puesto que probablemente empleemos un tiempo limitado para entrenar cada red, de manera que nos hagamos una idea aproximada de su eficiencia en el menor tiempo posible, es conveniente realizar en cada generación un nuevo entrenamiento de todas las redes, incluso de las que conservamos de generaciones anteriores, para evaluar su coste. La razón es que la eficiencia que alcance la red puede depender mucho de los valores iniciales de los pesos, que se suelen asignar aleatoriamente. De esta manera mejoramos la capacidad del proceso para determinar las mejores topologías y parámetros a costa de solo un poco más de tiempo.
Como lectura para profundizar en el tema, os puedo recomendar el libro Genetic Algorithms, de K.F. Man, K.S. Tang y S. Kwong, que contiene un capítulo dedicado precisamente al entrenamiento de redes neuronales usando algoritmos evolutivos. Yo he preparado un sencillo programa como ejemplo de implementación de este sistema, que explicaré en el segundo artículo de esta serie.