¡Que siga aprendiendo!


En esta entrada retomaremos el tema de la robótica evolutiva en el punto en el que lo dejamos. Vamos, que antes de seguir leyendo, conviene que os paséis por la entrada anterior (si es que no lo habéis hecho ya):

¡Que aprenda él mismo!

Terminamos aquella entrada con la pregunta de cómo es posible encontrar los valores de los pesos de la red neuronal con tal de conseguir que el robot haga lo que nosotros queramos. Vamos a ponernos manos a la obra.

Votar en los Premios Bitacoras.com

Un problema de optimización

Intentemos ir paso a paso. Nuestro sistema está compuesto por los siguientes elementos:

  1. Tenemos un robot con dos brazos y un detector de luz.
  2. Tenemos una red neuronal feedforward que enlaza las mediciones del detector de luz con los motores de los brazos robóticos. Obviamente, esta red corre en un ordenador al que enchufamos el detector de luz y los brazos del robot.
  3. Tenemos una función objetivo del robot, que es la de maximizar el refuerzo que recibe.
  4. Finalmente, tenemos una función de refuerzo, que evalúa las acciones del robot y envía un refuerzo acorde a la bondad de la acción ejecutada por el robot.

Para poder generar el comportamiento adecuado, la clave está en buscar los valores adecuados de los pesos de cada neurona de nuestra red. Eso se hace inicializando los pesos con valores aleatorios, así sin más. Entonces, cuando el robot realice una acción acorde al valor transmitido por el detector de luz y los pesos aleatorios de su red, nuestra función de refuerzo le asignará una nota. Como se puede intuir, estos valores aleatorios no deberían obtener una buena nota. Más bien lo contrario. Entonces, se volverán a calcular de nuevo los valores de los pesos y se repetirá el proceso. Se recibirá otra nota, en principio algo mejor que la anterior, y seguiremos así con el proceso hasta llegar a notas muy altas.

Seguramente os estéis preguntando: ¿cómo se recalculan esos valores? Y como no, aquí llegan los algoritmos genéticos. El proceso que acabamos de describir es un proceso de optimización. Queremos encontrar los valores óptimos de los pesos para maximizar el refuerzo – la nota -. Es decir, una vez nuestra red haya generado unas acciones, la nota de esas acciones se compara con la nota deseada. La diferencia entre ambas notas es el error de nuestra red. Iremos optimizando los valores de los pesos para ir minimizando la diferencia entre la nota recibida y la deseada. Y como algoritmo de optimización, usaremos los algorittmos genéticos.

Por fin los algoritmos genéticos

Los tan manidos algoritmos genéticos son un algoritmo de optimización como otro cualquiera. Sin embargo, tienen algunas ventajas que los hacen especialmente atractivos para los casos que nos ocupan.

Lo primero de todo es entender cómo funcionan estos algoritmos. Una explicación sencilla y brillante, con el ejemplo del problema del viajante, la podemos leer en Ciencia Xplora (@cienciaxplora) de la mano de Clara Grima (@ClaraGrima):

Algoritmos genéticos by Clara Grima

Como yo no tengo ni la mitad de arte que Clara, no voy a volver a explicar lo que tan bien explicado está. Solo vamos a rescatar lo más importante para nuestro caso. Nuestra población, es decir, las posibles soluciones, está compuesta por diferentes combinaciones de pesos de la red. La función objetivo será minimizar la diferencia entre la nota esperada (un 10, claro está) y la nota asignada por la función de refuerzo a una solución concreta.

Tened en cuenta que nuestro espacio de soluciones es realmente gigantesco. Por hacer unos números: si tenemos 1 neurona de entrada, 4 neuronas en la capa oculta y 2 en la salida, los parámetros a ajustar (pesos + los parámetros libres) ascienden a 4×2 + 2x(4+1) = 18. Esto quiere decir que el espacio de las soluciones posibles es un espacio de 18+1 = 19 dimensiones, donde tenemos que encontrar los valores óptimos (la dimensión adicional es el eje del error). Y os aseguro que incluso para un problema tan simple como el nuestro, una red tan pequeñita no serviría. Cada vez que incorporamos una neurona, la dimensión del espacio asciende mucho, ya que implica muchos pesos nuevos. Por lo tanto, imaginad a lo que nos enfrentamos.

En todo este tinglado hay una pregunta obligada: ¿por qué usamos algoritmos genéticos? Podría ser un capricho, pero no lo es. Nuestro gran espacio de soluciones se compone de los pesos de la red y el error de la red. Si tuviéramos solo un peso, podríamos dibujar ese espacio así:

espacio-soluciones

Recordad que el error mide la diferencia entre la nota que obtiene la red de la función de refuerzo y la nota deseada. El objetivo del algoritmo es minimizar ese error. Pero, ¿qué pinta tiene ese error? Normalmente, la superficie del error respecto a los pesos suele tener una pinta bastante horrible, cuando se usan las redes neuronales y el aprendizaje por refuerzo. Pongamos un ejemplo:

espacio-soluciones-1

Claro, tenemos que imaginarnos una superficie así en un espacio de 19 dimensiones (18 pesos + el error). A ver quién es el guapo que encuentre el mínimo global allí. Justamente, los algoritmos genéticos son muy buenos en estos espacios. No aseguran encontrar el mínimo global, pero sí que aseguran converger a una solución cercana al mínimo global. De alguna forma, los algoritmos genéticos son capaces de hacer dos cosas:

  1. Mejorar las soluciones existentes cruzándolas entre ellas: usando el cruce de las mejores soluciones, se logran soluciones mejores. Este proceso se puede visualizar como bajar la pendiente en cualquier punto de la superficie del error.
  2. Explorar todo el espacio de soluciones: si solo cruzáramos las soluciones, bajaríamos la pendiente y tendríamos el peligro de quedar atascados en un mínimo local. Para evitar eso, hay que ser capaz de saltar de una zona del espacio a otra. Los algoritmos genéticos consiguen esto introduciendo mutaciones en las soluciones. Las mutaciones son aleatorias y ayudan a explorar bien todo el espacio de soluciones.

Mejorando y explorando, al final el algoritmo genético es capaz de dar con una solución quasi-óptima.

espacio-soluciones-2

 

¿Por qué es esto tan chulo?

Hombre, hay muchísimas razones para que todo esto que acabamos de ver se pueda calificar como chulo. Por un lado, tanto las redes neuronales artificiales como los algoritmos genéticos, son algoritmos matemáticos que se inspiran en la naturaleza. Los primeros en nuestro propio cerebro, que es el ejemplo más sofisticado y brutal de red neuronal natural. Los segundos, en la teoría de la evolución, que explica como las especies se van adaptando a sus medios y logran reproducirse mejorando la especie. Poder utilizar algoritmos de este calibre para hacer que un robot aprenda por sí solo me parece una auténtica gozada.

Otra de las razones es que somos capaces de replicar un proceso de aprendizaje natural. El aprendizaje por refuerzo se da en la naturaleza. Lo usamos los humanos para aprender, y también los animales. Aplicar esquemas de aprendizaje natural a un robot, usando además algoritmos inspirados en la naturaleza, que seguramente tengan mucho que ver con nuestros propios procesos internos, ya me parece la rehostia.

Pero todo esto también tiene un componente más práctico desde el punto de vista de los robots. Hay ciertos comportamientos muy útiles que son muy díficiles de programar a mano. Por ejemplo, voltear una tortilla en una sartén.

En este ejemplo, para inicializar el proceso de aprendizaje, usan otro tipo de aprendizaje: el aprendizaje por demostración. Pero por ahora eso nos da igual. Si os fijáis, el robot al principio es bastante torpe. Al cabo de probar varias veces, alcanza una gran maestría. Tener que programar a mano los movimientos de un brazo con tantos grados de libertad, con todas las variables externas relacionadas con el movimiento de la tortilla, es un auténtico suplicio. Y probablemente, no se podría conseguir un resultado tan bueno.

Para terminar

Recapacitad un poco sobre lo que hemos visto en estas dos entradas. Hemos cogido un robot con unos sensores, unos actuadores y un ordenador. Le hemos metido una red neuronal con unos pesos aleatorios, que enlazaban los sensores con los actuadores. Le hemos dicho que tiene que maximizar el refuerzo obtenido, y con una función de refuerzo, hemos fijado cual es el comportamiento que nosotros queremos que el robot aprenda. Con procesos de prueba y error, un algoritmo genético ha sido capaz de encontrar la configuración de pesos de la red neuronal para que el robot haga lo que nosotros queríamos.

Ya me permitiréis la licencia, pero yo, cuando veo a un bebé, veo en parte todo esto que acabamos de ver. Sale al mundo con una red neuronal descalibrada, hace pruebas como un loco y al cabo de mucho tiempo aprende a hacer cosas. Ya sé que la distancia entre lo que hace el bebé y hacemos con los robots es todavía enorme, y que hay muchísimos más factores que los que hemos visto aquí, pero no me negaréis el parecido.

En fin, olvidaos del último párrafo y pensad que hemos aprendido algo de robótica evolutiva, redes neuronales y algoritmos genéticos.

Nos seguimos leyendo…

Anuncios

Una respuesta a “¡Que siga aprendiendo!

  1. Pingback: ¡Que siga aprendiendo!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s