domingo, 13 de marzo de 2016

¿Cómo funciona el programa AlphaGo de Google DeepMind?

"[Las neuronas son] células de formas delicadas y elegantes, las misteriosas mariposas del alma, cuyo batir de alas quién sabe si esclarecerá algún día el secreto de la vida mental." (Ramón y Cajal)

Introducción.

El programa AlphaGo desarrollado por Google DeepMind se ha hecho famoso estos días en los medios de comunicación a partir de su enfrentamiento (y victoria) frente el campeón del mundo y noveno dan, Lee Sedol.

Como digo, todos los medios se han hecho eco de esta proeza (en concreto, conseguir un algoritmo capaz de competir en igualdad de condiciones con un gran maestro de Go), algo que se pensaba que aún tardaría 10 años más en llegar, pero pocos medios no especializados han explicado con cierto detalle cómo funciona realmente AlphaGo. Y esto es precisamente lo que pretendo con esta entrada: explicar técnicamente del modo más sencillo posible qué es lo que han hecho desde Google para conseguir este hito en inteligencia artificial,  y hacerlo además de un modo que pueda ser entendido más o menos por cualquier persona.

Durante toda esta entrada, voy a basarme en el paper que el propio equipo de DeepMind ha publicado en la revista Nature explicando su obra: "Mastering the game of Go with deep neural networks and tree search" (http://www.nature.com/nature/journal/v529/n7587/full/nature16961.html). Pero para que gente no especializada pueda entender este asunto, evidentemente tendré que simplificar en gran parte el contenido de dicho trabajo. No obstante, decir a aquellos lectores con conocimientos más avanzados de computación, que podéis acceder a dicho paper y obtener toda la información técnica completa y detallada en profundidad (si no queréis pagar, aquí tenéis un enlace gratuito al PDF oficial ofrecido por Google Scholar :P).

Precedentes de AlphaGo.

El juego de tablero del Go ha sido desde hace décadas el último gran reto de la inteligencia artificial en cuanto a intentar conseguir un nivel de juego capaz de enfrentarse a grandes maestros humanos. Tal es la complejidad combinacional del juego (¡donde una partida tiene más movimientos posibles que átomos tiene el Universo!), que ni siquiera poniendo miles de ordenadores distribuidos calculando jugadas mediante algoritmos tradicionales de "fuerza bruta" se conseguía nada (algo que sí sirvió, sin embargo, para que el DeepBlue de IBM venciera al gran maestro de ajedrez Gary Kasparov en 1996). Pero como decimos, en el Go esta estrategia digamos "tradicional" de pre-programar algunas reglas de juego e invertir luego cientos de computadores para prever y calcular las mejores jugadas no conseguían ganar ¡ni siquiera a un jugador principiante!

Nuevas técnicas de inteligencia artificial fueron, no obstante, apareciendo en los últimos años, las cuales ya introducían nuevos preceptos y estrategias: siendo de destacar la aparición en el uso de árboles de exploración Monte-Carlo -no entraré en mucho detalle sobre esta técnica denominada MCTS (Monte-Carlo Tree Search), pero tenéis mucha más información en el paper y en internet ;)-. Pues bien, usando estas nuevas técnicas ya se lograron programas comerciales capaces de jugar al nivel de un jugador amateur (con un Elo de sobre 2000), siendo en este sentido de destacar los programas Crazy Stone y Zen.

Comparación del nivel de juego entre AlphaGo, el gran maestro Fan Hui, y otros programas comerciales de Go.

Sin embargo, hasta llegada de AlphaGo en 2015, ningún programa pudo pasar de este nivel amateur de juego, y se pensaba que poder llegar a competir con un gran maestro de Go de dan (p) (con un Elo por encima de 3000), era algo que tardaría en llegar un mínimo de 10 años más...pero finalmente no ha sido así. AlphaGo venció en octubre del 2015 en un torneo al gran maestro humano Fun Hui, de dan 3p (y sobre 2800 de Elo). Y aunque ya eso supuso una revolución y un verdadero logro para la tecnología, ha sido no obstante este año, tras su fenomenal enfrentamiento con el 9p dan Lee Sedol (con un Elo por encima de 3500), cuando se ha confirmado la proeza. Una proeza con un mérito aún mayor dado el método por el que ha conseguido: habiéndose literalmente simulado el modo en que el cerebro humano piensa y aprende, para lograr así generalizar y abstraer lo suficiente la capacidad del algoritmo como para entender y comprender (igual que lo haría una persona), el valor de un movimiento únicamente a partir de la información sensible (visual) del tablero. Veamos ésto con un poco más de detalle.

Redes neuronales artificiales.

Cuando decimos que este logro se ha conseguido simulando cómo funciona el cerebro humano, me estoy refiriendo, por supuesto, a que se han utilizado profusamente redes neuronales artificiales (que no son más que una emulación computacional del modo en que se conforman las redes neuronales biológicas en el hombre -y en el resto de animales- para otorgamos nuestras capacidades cognitivas).

Estas redes se usaron concretamente para dos tareas bien diferenciadas: para seleccionar (y descartar) estrategias de juego que merecen (o no) la pena estudiar más a fondo de entre las cientos de miles de combinaciones disponibles, y para evaluar lo favorable o desfavorable que es un determinado estado del tablero. Para cada una de estas dos tareas se usaron varias subredes neuronales independientes, aunque todas compartían una estructura muy similar; tratándose de redes con una entrada (input) "visual" el cual recibe directamente los pixels del tablero en un instante dado, tras lo cual 14 capas intermedias u ocultas (hidden layers) se encargan de procesar esta información "sensible" recibida, trasladando el resultado de este procesado de información hacia una capa de salida (output). Este resultado será finalmente el utilizado por AlphaGo para discriminar lo acertado o no de una determinada estrategia de juego, y para intentar prever y entender cómo de bien le iría si finalmente se decidiese por una de ellas.

Este ha sido, por tanto; el verdadero acierto del equipo de desarrollo de Google, hacer un uso muy intensivo de redes neuronales complejas para abstraer y generalizar la estrategia de juego; y ha sido además lo que ha permitido en tan sólo dos años, aumentar el Elo del estado del arte en la IA de este juego en más de 1500 puntos: todo un rotundo éxito.

Imitando al hombre.

Sin duda, la clave detrás del poder del programa se encuentra en haberse logrado imitar computacionalmente, y con mucho acierto, el modo en que se supone que la propia mente humana procede a la hora de jugar una partida de Go; y también al imitar el modo en que procede el cerebro humano a la hora de aprender a jugar primero, y a ganar después con el tiempo maestría. Y para que se comprenda este punto con claridad, creo que lo mejor es hacer un breve símil aproximado entre el modo en que se supone que la mente de un maestro humano procede, y el modo en que AlphaGo hace lo propio:

Ilustración de una red neuronal biológica.
A la hora de jugar una partida, un jugador humano primeramente observa mediante la vista (captando los fotones reflejados por el tablero) el estado actual de juego (número de piezas en cada casilla, color de las mismas, etc,); esa información eléctrica sensible se traslada mediante el nervio óptico hacia el sistema nervioso central (SNC), donde la corteza visual primaria V1 (y posteriormente las áreas visuales corticales extra estriadas: V2, V3, V4, y V5), tratan esta información visual originaria, adecuándola y transformándola de modo que sirvan más eficazmente en los sucesivos procesamientos neuronales, los cuales tomarán como entrada esta información visual tratada (y no la original del nervio óptico). No existen aún detalles exactos y completos del modo en que este procesamiento posterior ocurre, pero sí se sabe que el hipotálamo actúa como coordinador (o controlador) de un proceso asociado recurrente e interconectado donde diferentes subredes neuronales biológicas especializadas procesan toda la información de un modo concurrente (fundamentalmente en los lóbulos temporales y parietales). Durante este procesado continuo y asociado de redes especializadas, se supone que el jugador va mentalmente seleccionando y estudiando los movimientos que considera prometedores (utilizando la memoria), y luego previendo el resultado que ese movimiento conllevaría en sucesivas jugadas, descartando así las jugadas menos favorables y profundizando en aquellas que sí se lo parecen. Este bucle mental, en gran parte inconsciente, se repite durante miles de simulaciones mentales imaginarias (descartando por el camino billones de jugadas no valoradas como prometedoras) para finalmente, tras un intervalo variable de tiempo de dicha "exploración mental", procederse a una salida (es decir; a la toma de una decisión final): "colocar la pieza en la casilla x". Esta salida se transmite posteriormente a los nervios motores de las extremidades, los cuales mueven la mano que cogen la ficha (en Go a las fichas las llaman piedras), y la sueltan en la posición x. En realidad, la neurociencia dispone de detalles mucho más concretos (aunque aún incompletos) sobre este proceso, pero para el símil que vamos a realizar, con lo dicho basta.

¿Y cómo lo hace AlphaGo? Pues de un modo asombrosamente similar. La máquina utiliza una técnica de exploración iterativa muy (muy) similar a la que realiza nuestro cerebro. En concreto, utiliza un árbol de exploración MCTS (Monte-Carlo Tree Search) para emular la "exploración mental" imaginaria que hemos visto que hace el cerebro; pero adapta dicha técnica de manera que se proceda de un modo casi idéntico a cómo explora la mente del jugador humano. El proceso completo es aproximadamente el siguiente: primero la máquina recibe los pixels con el estado actual del tablero (de modo similar a cómo la retina y el nervio óptico llevan esta misma información sensible al SNC), posteriormente esta entrada visual y sensible se convoluciona por entre varias capas intermedias de neuronas (de modo similar a cómo actúa la corteza visual en V1,V2,V3,V4 y V5). Es la salida de esta convolución la que se introduce luego en los nodos (neuronas) de entrada de las 4 subredes neuronales artificiales que se usarán en el resto del proceso (de modo parecido a cómo lo hacen ciertas subredes biológicas asociadas).

Ilustración de una red neuronal artificial.
Es decir; que una vez tratada la información visual sensible, se produce un proceso iterativo de simulación muy similar al que tiene lugar en la mente de una persona por entre diferentes subredes neuronales biológicas en los lóbulos temporales y parietales. En concreto, AlphaGo procede a evaluar y seleccionar (gracias a la actuación de dos de las 4 subredes neuronales utilizadas: value network y policy SL network) las jugadas que considera más prometedores (descartando billones de alternativas del mismo modo que hace una persona), y más tarde se dedica a investigar y profundizar en el estudio de estas jugadas percibidas como buenas candidatas gracias a otra de las subredes neuronales encargadas de realizar esta tarea con gran eficacia y precisión (fast rollout policy). Este proceso de exploración ocurre a un ritmo de miles de simulaciones "imaginarias" por segundo (un ritmo del mismo orden de magnitud que el humano), pero hay que recalcar que esta cantidad es irrisoria en comparación con las decenas de millones de "simulaciones" que utilizó, por ejemplo DeepBlue contra Kasparov. Porque es importante hacer de nuevo hincapié en que aquí el poder de la máquina no recae en explotar la potencia de cálculo en un proceso de fuerza bruta de exploración, sino en usar esta potencia en la ejecución de complejas redes neuronales capaces de abstraer, generalizar y seleccionar las mejores jugadas y estrategias de juego de entre billones de alternativas que son descartadas e ignoradas. Esto es precisamente lo que se cree que el hombre hace al jugar, y lo que ha dado al AlphaGo tanta capacidad de juego al imitarle.

Cómo realizaron el entrenamiento de las redes neuronales de AlphaGo.

Pues de una manera muy similar a cómo se supone que nuestras redes neuronales biológicas se adaptan y modulan para realizar con éxito nuevas tareas requeridas por la persona: mediante un proceso continuo y gradual de ensayo y error empírico.

Cualquier persona neuronalmente sana, posee desde que concluye su desarrollo cerebral, todas las capacidades neurológicas necesarias para poder llegar a aprender a jugar Go tras un proceso de aprendizaje el cual llegue a modular y adaptar las sinapsis de ciertas subredes de neuronas en diferentes zonas del cerebro. Es decir; que las subredes que nos permiten jugar al Go ya están ahí desde el principio, y es un proceso de ensayo y error el que va poco a poco adaptando dichas subredes para ir mejorando la calidad de nuestro juego. En pocas palabras: nuestro cerebro aprender a jugar...jugando.

AlphaGo hace lo mismo, pero a la enorme escala que permite el hecho de poderse simular millones de partidas en muy poco tiempo (mientras que una persona no puede jugar más de una centena de ellas por motivos fisiológicos). En concreto, las subredes neurológicas utilizada por la máquina se entrenaron mediante este proceso empírico jugando contra sí misma durante semanas (usando una subred denominada policy RL network). Para entender este proceso de entrenamiento con más detalle, sin embargo, es necesario disponer de ciertos conocimientos previos de computación teórica, por lo que no voy a entrar en más detalles técnicos (aunque está todo muy bien explicado en el paper publicado por Google). Baste con que nos quedemos con la idea de que la máquina aprende literalmente a jugar de un modo casi idéntico a cómo lo hace el hombre (mediante la observación de partidas que son jugadas por otras personas que saben hacerlo mejor que nosotros -policy SL network-, y mediante un proceso continuo de juego y práctica personal por el que vamos limando gradualmente por ensayo y error nuestros fallos de juego -policy RL network-).

Resultados y discusión.

Los resultados han sido maravillosos. Entrenando de este modo, la misma estructura neuronal artificial ha pasado en pocas semanas de no ser capaz de ganar ni siquiera al más penoso jugador humano, a poder vencer primero al gran maestro 2p dan Fan Hui (por 5 a 0), y luego también al campeón mundial Lee Sedol 9p dan. La (durante décadas) tan prometida inteligencia artificial parece que ahora sí está dando por fin sus primeros (y verdaderos) pasos.

Pero las aplicaciones futuras de esta revolución lograda por DeepMind no se reducen a la mera posibilidad de jugar juegos con un nivel sobrehumano; sino que abre las puertas a la aplicación de estos métodos utilizados sobre muchas otras tareas complejas que hasta ahora sólo un humano era capaz de realizar con precisión. Las posibilidades son ilimitadas, y estamos sin duda ante un hito que la historia se encargará de recordar a su debido tiempo.

Opinión personal.

Pues bien, hasta aquí ya hemos dicho lo fundamental sobre cómo funciona AlphaGo y las consecuencia y resultados de su desarrollo, pero no quiero cerrar este artículo, sin embargo; sin hacer antes un par de anotaciones muy personales y subjetivas mías, que cada cual tendrá que valorar, pero que creo sinceramente que ganan fuerza tras este experimento de Google:

Primero y fundamental, creo que este experimento demuestra que nuestra mente no es ni conforma ningún fenómeno especial o diferenciado en su esencia natural. En otras palabras: creo que este desarrollo aporta mucha más fuerza a la idea que rechaza cualquier tipo de dualismo en la capacidad cognitiva del hombre. El materialismo más reduccionista creo que saca partido ante estos hechos, y que ya poco lugar a dudas queda de que nuestra mente entera, junto a sus capacidades cognitivas, es simplemente el resultado de complejos (y masivos) cálculos ocurridos por entre las neuronas de nuestro cerebro gracias a medios eléctricos...y poco más. Ni alma, ni espíritu, ni siquiera postulados estrambóticos como la supuesta intervención cuántica propuesta por Roger Penrose parecen ya necesarios ni creíbles: únicamente la física y la química a nivel mesoscópico, junto con la dirección de movimiento impuesta por la termodinámica, parecen bastar y sobrar para dar cuenta del origen y desarrollo de nuestra conciencia y capacidades mentales. Y eso sería todo.

Por otra parte, también creo que este desarrollo supone una clara muestra del modo en que nuestras propias capacidades cognitivas pueden ser (y serán) fácilmente sobrepasadas en cualquier ámbito o tarea que nos propongamos (sin importar ya su complejidad): simplemente habremos de usar redes neuronales artificiales con la estructura adecuada para cada tarea dada, y posteriormente proceder a modular sus pesos (el equivalente a adaptar la fuerza de las sinapsis biológicas) de un modo gradual pero masivo y a gran escala (parecido a como han hecho con AlphaGo). Además, no es siquiera necesario que una persona se dedique a investigar qué estructura neuronal artificial es la adecuada para cada tarea, porque dentro de poco estoy seguro que otra rama de la computación llamada computación evolutiva se unirá con éxito a estos avances, y harán posible que incluso la estructura neuronal necesaria se seleccione de manera autónoma con esta técnica (de un modo similar a como la evolución biológica hizo lo mismo en el reino animal).

Y es que yo estoy firmemente convencido de que el uso conjunto del tipo de técnicas de aprendizaje utilizadas en AlphaGo, junto con el uso concurrente de técnicas de computación evolutiva, llevarán durante las próximas tres o cuatro (o cinco :P) décadas a la aparición final de una verdadera conciencia artificial indiferenciable de la humana: esa máquina pensará, sentirá, aprenderá, se emocionará, y actuará en cualquier ámbito como nosotros...e incluso lo hará mejor.

Es más: yo creo que ya hoy día, si un gran país del estilo de EEUU se propusiese esta meta, dedicando gran parte del esfuerzo del país en esta dirección de modo similar a cómo se hizo en el proyecto Manhattan (dando lugar a la bomba atómica), o en el programa Apolo (que llevó el hombre a la luna), igualmente creo que en no más de 10 años se podría conseguir sin duda esta conciencia artificial que os digo. De hecho, tengo "confianza" en que una posible guerra fría renovada gracias al choque entre el futurible presidente americano Donald Trump y el actual presidente ruso Vladímir Putin, lleven a que al menos uno de los dos bandos (posiblemente USA), dedique grandes esfuerzos en esta dirección guiado por intereses militares; lo cual acelere así el asunto. Evidentemente hay que tener en cuenta las posibles consecuencias secundarias de este choque entre países, pero bueno, siempre hay que intentar ver el lado positivo de las cosas (xDD). Y lo positivo sería sin duda la aparición de esta hipotética conciencia artificial 40 ó 50 años antes de lo que ocurrirá con el actual nivel de financiación en estos campos de estudio ;).

Para concluir, y aunque sé que puede parecer una afirmación exagerada, en mi opinión, este logro conseguido por Google DeepMind supone una nueva y definitiva herida a la narcisista humanidad y su gran ego:

Históricamente, la primera herida la propició Nicolás Copérnico al demostrar que la Tierra no es el centro del universo. Una segunda fue obra de Charles Darwin, al demostrar que el hombre es simplmente un animal más. La tercera fue iniciada sin duda por Sigmund Freud, al proclamar que ni siquiera somos dueños de nosotros mismos (con el descubrimiento del inconsciente), herida que fue desarrollada y ampliada más tarde por la moderna neurociencia. Finalmente, creo que se acaba de producir una nueva herida desde el terreno de la inteligencia artificial al demostrarse que cualquiera de nuestras capacidades cognitivas superiores (conscientes, inconscientes, sencillas o complejas) no es sólo que sean reproducibles, sino que incluso son mejorables. El materialismo gana la batalla, y se demuestra que el tan alabado fenómeno mental humano no es especial en ningún sentido: se constata que finalmente es sólo cuestión de poner las partículas necesarias en el lugar adecuado, y dejar que la termodinámica guíe el movimiento.

Y con esto termino :). Espero que os haya interesado el artículo.

¡Un saludo!

Principales referencias utilizadas.

"Mastering the game of Go with deep neural networks and tree search" (http://www.nature.com/nature/journal/v529/n7587/full/nature16961.html) (Google DeepMind) (2015).
https://deepmind.com/publications.html (publicaciones del equipo de desarrollo Google DeepMind).

jueves, 3 de marzo de 2016

Aprendizaje automático mediante Deep Q Ntework (DQN + TensorFlow)

"[Las neuronas son] células de formas delicadas y elegantes, las misteriosas mariposas del alma, cuyo batir de alas quién sabe si esclarecerá algún día el secreto de la vida mental." (Ramón y Cajal)

Introducción.

Este artículo es una continuación de mi entrada anterior "Las matemáticas de la mente"[2]. Vimos en ese artículo cómo era posible que un simple algoritmo de computación pudiese imitar el modo en que nuestro cerebro aprende a realizar tareas con éxito, simplemente a partir del equivalente computacional de una red neuronal.

Sin embargo, a pesar de que en dicha entrada os comentaba el caso de cómo se puede programar un algoritmo capaz de conseguir literalmente, aprender a jugar al Conecta4 (4 en raya) sin especificar (pre-programar) en ningún momento las reglas del juego; es posible que muchos notasen que aún así, todavía había que pre-procesar la entrada de la red neuronal para ofrecerle a las neuronas (nodos) de la capa de entrada (inputs) qué fichas había en cada casilla del tablero y qué casillas estaban aún libres. Este hecho podía hacer a algunos sospechar de la autonomía real de este programa para aprender de un modo no supervisado a jugar.

Por lo tanto, y con ánimo de solventar este asunto, os voy a presentar un nuevo ejemplo en esta nueva entrada para intentar reforzar así la validez de las conclusiones a las que llegamos al final del artículo "Las matemáticas de la mente"[2]. Haremos ésto mediante un nuevo experimento de aprendizaje automático mediante redes neuronales, pero esta vez; aprovechando el estado del arte teórico que nos ofrece el equipo del departamento de inteligencia artificial de Google DeepMind[4].

En concreto, he dedicado un par de semanas de mi tiempo libre en implementar un muy prometedor algoritmo de aprendizaje por refuerzo (RL según siglas en inglés). Para aquellos interesados técnicamente en el asunto, pueden leer el siguiente paper[1] del que me hecho referencia: "Asynchronous Methods for Deep Reinforcement Learning", publicado a fecha del 4 de febrero de este año 2016, en colaboración con la Universidad de Montreal.

La cuestión fundamental tratada en este paper de Google es que explica el modo en que es posible entrenar una red neuronal artificial para que realice tareas sin supervisión ni pre-programación alguna. Se trata de un proceso end-to-end, mediante el cual el algoritmo accede directamente a los píxeles de la pantalla (simulando el modo en que nosotros obtenemos la información visual desde nuestra retina), y luego convoluciona y post-procesa dicha información sensible en varias capas de nodos (neuronas) internas (simulando el modo en que nuestro cerebro procesa la información obtenida en la retina posteriormente en sucesivas partes de la corteza visual); para terminar tratando dicha información visual en nuevas capas de neuronas que darán lugar finalmente a una respuesta (una acción).

Esto implica que ya no es necesario que nosotros pre-programemos y ofrezcamos como entrada a la red neuronal el estado del entorno (que en el caso del Conecta4 era estado del tablero), sino que es la propia red neuronal la que observa visualmente el entorno (por ejemplo, el tablero), y luego procesa dicha información visual en otras zonas de la misma red de manera autónoma, hasta ofrecer finalmente como salida una determinada acción a realizar.

Y sobra decir que los chicos de Google lo han conseguido (en realidad lo que han logrado es perfeccionar una técnica ya existente en gran medida); han desarrollado un algoritmo capaz de enseñar a una red neuronal artificial a procesar toda la información sensible disponible sobre el entorno de cualquier tarea a realizar, y; conseguir de un modo totalmente autónomo (end-to-end), ¡conseguir una respuesta con habilidades bastante cercanas a las humanas!

Metodología.

En el paper[1] arriba indicado nos explican teóricamente el modo en que es posible implementar un algoritmo de aprendizaje realmente autónomo como el que estamos buscando en un intento de reforzar las conclusiones obtenidas en el anterior artículo[2]. Y lo hacen mediante una detallada explicación, y ofreciendo además un pseudocódigo de guía para el interesado en realizar algún experimento o prueba práctica de lo que proponen. En realidad se ofrecen varias alternativas y variantes de lo que en DeepMind han denominado Deep Q NetWorks, aunque yo me he decantado por utilizar la variante en principio más sencilla de implementar (aunque también es la relativamente menos eficiente). Esto es, el "Asynchronous one-step Q-learning". 

Os dejo a continuación una copia del pseudocódigo ofrecido en el paper, pero aquellos lectores no técnicos no tenéis que preocuparos, continuad adelante sin prestar más atención al mismo:




Configuración experimental.

Para poner a prueba el pseudocódigo, me decidí a aprovechar la librería para desarrollo numérico computacional que desde hace unos meses ofrece el propio Google de acceso gratuito: TensorFlow[3]. En concreto, hice uso de la interfaz Python de la librería para programar mediante esta técnica de aprendizaje por refuerzo una red neuronal artificial capaz de aprender a jugar de manera totalmente autónoma (a partir únicamente de los pixels de la pantalla del ordenador) al clásico juego del Pong (el mítico juego de Atari de la pelotita rebotando en las paredes ;)).

Revisión utilizada del mítico juego del Pong

Utilicé TensorFlow para diseñar una red neuronal artificial de 6 capas: una para recibir los inputs de la imagen del entorno (el estado del juego) en formato RGB (donde cada pixel se representa mediante un número entero que es una combinación para el tono rojo, verde y azul). Estos píxels (simulando al modo en que la retina recoge los fotones en el ojo), se pasan luego a dos capas internas (o, como se las suelen llamar: ocultas -"hidden"-) que convolucionan y pre-procesan esta información visual para facilitar su uso más tarde por parte de las otras dos capas de neuronas, que finalmente procesan la información y devuelven una acción a realizar gracias a una capa de salida (output). Y esta es la red neuronal que se entrena de manera autónoma mediante el código de RL propuesto en el paper.

Como digo, todo se programó en Python usando varios hilos (threads) como requiere el pseudocódigo (ya que este aprendizaje hace uso de un procesamiento asíncrono en paralelo del entrenamiento).

Os dejo a continuación un par de vídeos donde podréis ver gráficamente en vivo cómo se produce este proceso. En un primer vídeo podréis observar como la red neuronal al inicio no se encuentra entrenada (es decir; sus "sinapsis" no están bien balanceadas), por lo que juega a lo loco (la red neuronal es la barra azul de la izquierda) . También veréis cómo se inicia el proceso de entrenamiento en paralelo mediante ensayo y error, gracias al refuerzo que supone la recompensa de ganar una partida, o el castigo que supone perderla (algo similar a lo que nosotros experimentamos como alegría y frustración cuando nos enfrentamos al aprendizaje de una nueva tarea):

video

Este segundo vídeo muestra la misma red neuronal inicial una vez que sus pesos (sinapsis) han sido bien moduladas por el proceso de aprendizaje. Como se puede observar, el algoritmo ahora es capaz de obtener la imagen (pixels) de la pantalla, "objetivar" lo importante de la misma (la pelota y su posición relativa), y actuar en consecuencia para maximizar el beneficio de sus acciones (ganar partidas):

video

Resultados.

Los resultados son muy favorables, alcanzando el algoritmo por sí mismo tras el entrenamiento autónomo un nivel de juego muy admirable y equiparable al de una persona. Además, es importante señalar una cuestión técnica: el proceso de aprendizaje se lleva a cabo con esta novedosa técnica propuesta por Google en un simple ordenador personal, sin requerir de grandes estaciones de trabajo distribuidas, y ni siquiera de un hardware específico (como tarjetas GPU). De hecho, con un simple procesador Intel Core i5 he conseguido entrenar en menos de 24 horas lo que hasta hace poco requería de grandes estaciones de trabajo distribuidas usando la técnica conocida como "Gorila" (ver el paper[1] de referencia para más información).

Por si hay algún interesado en probar todo el tinglado, os dejo a continuación copia del código fuente Python que he desarrollado:

#!/usr/bin/env python
import threading
import tensorflow as tf
import cv2
import sys
sys.path.append("Wrapped Game Code/")
import pong_fun as game # Whichever is imported "as game" will be used
import random
import numpy as np
import time

#Shared global parameters
TMAX = 5000000
T = 0
It = 10000
Iasync = 5
THREADS = 12
WISHED_SCORE = 10

GAME = 'pong' # The name of the game being played for log files
ACTIONS = 3 # Number of valid actions
GAMMA = 0.99 # Decay rate of past observations
OBSERVE = 5. # Timesteps to observe before training
EXPLORE = 400000. # Frames over which to anneal epsilon
FINAL_EPSILONS = [0.01, 0.01, 0.05] # Final values of epsilon
INITIAL_EPSILONS = [0.4, 0.3, 0.3] # Starting values of epsilon
EPSILONS = 3

def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev = 0.01)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.01, shape = shape)
    return tf.Variable(initial)

def conv2d(x, W, stride):
    return tf.nn.conv2d(x, W, strides = [1, stride, stride, 1], padding = "SAME")

def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = "SAME")

def createNetwork():
    # network weights
    W_conv1 = weight_variable([8, 8, 4, 32])
    b_conv1 = bias_variable([32])

    W_conv2 = weight_variable([4, 4, 32, 64])
    b_conv2 = bias_variable([64])

    W_conv3 = weight_variable([3, 3, 64, 64])
    b_conv3 = bias_variable([64])

    W_fc1 = weight_variable([256, 256])
    b_fc1 = bias_variable([256])

    W_fc2 = weight_variable([256, ACTIONS])
    b_fc2 = bias_variable([ACTIONS])

    # input layer
    s = tf.placeholder("float", [None, 80, 80, 4])

    # hidden layers
    h_conv1 = tf.nn.relu(conv2d(s, W_conv1, 4) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)

    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2, 2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)

    h_conv3 = tf.nn.relu(conv2d(h_pool2, W_conv3, 1) + b_conv3)
    h_pool3 = max_pool_2x2(h_conv3)

    h_pool3_flat = tf.reshape(h_pool3, [-1, 256])

    h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc1) + b_fc1)

    # readout layer
    readout = tf.matmul(h_fc1, W_fc2) + b_fc2

    return s, readout, W_conv1, b_conv1, W_conv2, b_conv2, W_conv3, b_conv3, W_fc1, b_fc1, W_fc2, b_fc2

def copyTargetNetwork(sess):
    sess.run(copy_Otarget)

def actorLearner(num, sess, lock):
    # We use global shared O parameter vector
    # We use global shared Otarget parameter vector
    # We use global shared counter T, and TMAX constant
    global TMAX, T

    # Open up a game state to communicate with emulator
    lock.acquire()
    game_state = game.GameState()
    lock.release()

    # Initialize network gradients
    s_j_batch = []
    a_batch = []
    y_batch = []

    # Get the first state by doing nothing and preprocess the image to 80x80x4
    lock.acquire()
    x_t, r_0, terminal = game_state.frame_step([1, 0, 0])
    lock.release()
    x_t = cv2.cvtColor(cv2.resize(x_t, (80, 80)), cv2.COLOR_BGR2GRAY)
    s_t = np.stack((x_t, x_t, x_t, x_t), axis = 2)
    aux_s = s_t

    time.sleep(3*num)

    # Initialize target network weights
    copyTargetNetwork(sess)

    epsilon_index = random.randrange(EPSILONS)
    INITIAL_EPSILON = INITIAL_EPSILONS[epsilon_index]
    FINAL_EPSILON =  FINAL_EPSILONS[epsilon_index]
    epsilon = INITIAL_EPSILON

    print "THREAD ", num, "STARTING...", "EXPLORATION POLICY => INITIAL_EPSILON:", INITIAL_EPSILON, ", FINAL_EPSILON:", FINAL_EPSILON 

    # Initialize thread step counter
    t = 0
    score = 0
    while T < TMAX and score < WISHED_SCORE:

        # Choose an action epsilon greedily
        readout_t = O_readout.eval(session = sess, feed_dict = {s : [s_t]})
        a_t = np.zeros([ACTIONS])
        action_index = 0
        if random.random() <= epsilon or t <= OBSERVE:
            action_index = random.randrange(ACTIONS)
            a_t[action_index] = 1
        else:
            action_index = np.argmax(readout_t)
            a_t[action_index] = 1

        # Scale down epsilon
        if epsilon > FINAL_EPSILON and t > OBSERVE:
            epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / EXPLORE

        # Run the selected action and observe next state and reward
        lock.acquire()
        x_t1_col, r_t, terminal = game_state.frame_step(a_t)
        lock.release()
        x_t1 = cv2.cvtColor(cv2.resize(x_t1_col, (80, 80)), cv2.COLOR_BGR2GRAY)
        x_t1 = np.reshape(x_t1, (80, 80, 1))
        aux_s = np.delete(s_t, 0, axis = 2)
        s_t1 = np.append(aux_s, x_t1, axis = 2)

        # Accumulate gradients
        readout_j1 = Ot_readout.eval(session = sess, feed_dict = {st : [s_t1]})
        if terminal:
            y_batch.append(r_t)
        else:
            y_batch.append(r_t + GAMMA * np.max(readout_j1))

        a_batch.append(a_t)
        s_j_batch.append(s_t)

        # Update the old values
        s_t = s_t1
        T += 1
        t += 1
        score += r_t

        # Update the Otarget network
        if T % It == 0:
            copyTargetNetwork(sess)

        # Update the O network
        if t % Iasync == 0 or terminal:
            if s_j_batch:
                # Perform asynchronous update of O network
                train_O.run(session = sess, feed_dict = {
                    y : y_batch,
                    a : a_batch,
                    s : s_j_batch})

            #Clear gradients
            s_j_batch = []
            a_batch = []
            y_batch = []

        # Save progress every 5000 iterations
        if t % 5000 == 0:
            saver.save(sess, 'save_networks_asyn/' + GAME + '-dqn', global_step = t)

        # Print info
        state = ""
        if t <= OBSERVE:
            state = "observe"
        elif t > OBSERVE and t <= OBSERVE + EXPLORE:
            state = "explore"
        else:
            state = "train"

        if terminal:
            print "THREAD:", num, "/ TIME", T, "/ TIMESTEP", t, "/ STATE", state, "/ EPSILON", epsilon, "/ ACTION", action_index, "/ REWARD", r_t, "/ Q_MAX %e" % np.max(readout_t), "/ SCORE", score
            score = 0

    # Save the last state of each thread
    saver.save(sess, 'save_networks_asyn/' + GAME + '-final-' + num)


# We create the shared global networks
# O network
s, O_readout, W_conv1, b_conv1, W_conv2, b_conv2, W_conv3, b_conv3, W_fc1, b_fc1, W_fc2, b_fc2 = createNetwork()

# Training node
a = tf.placeholder("float", [None, ACTIONS])
y = tf.placeholder("float", [None])
O_readout_action = tf.reduce_sum(tf.mul(O_readout, a), reduction_indices=1)
cost_O = tf.reduce_mean(tf.square(y - O_readout_action))
train_O = tf.train.RMSPropOptimizer(0.00025, 0.95, 0.95, 0.01).minimize(cost_O)

# Otarget network
st, Ot_readout, W_conv1t, b_conv1t, W_conv2t, b_conv2t, W_conv3t, b_conv3t, W_fc1t, b_fc1t, W_fc2t, b_fc2t = createNetwork()
copy_Otarget = [W_conv1t.assign(W_conv1), b_conv1t.assign(b_conv1), W_conv2t.assign(W_conv2), b_conv2t.assign(b_conv2), W_conv3t.assign(W_conv3), b_conv3t.assign(b_conv3), W_fc1t.assign(W_fc1), b_fc1t.assign(b_fc1), W_fc2t.assign(W_fc2), b_fc2t.assign(b_fc2)]

# Initialize session and variables
sess = tf.InteractiveSession()
saver = tf.train.Saver()
sess.run(tf.initialize_all_variables())
checkpoint = tf.train.get_checkpoint_state("save_networks_asyn")
if checkpoint and checkpoint.model_checkpoint_path:
    saver.restore(sess, checkpoint.model_checkpoint_path)
    print "Successfully loaded:", checkpoint.model_checkpoint_path

if __name__ == "__main__":
    # Start n concurrent actor threads
    lock = threading.Lock()
    threads = list()
    for i in range(THREADS):
        t = threading.Thread(target=actorLearner, args=(i,sess, lock))
        threads.append(t)

    # Start all threads
    for x in threads:
        x.start()

    # Wait for all of them to finish
    for x in threads:
        x.join()

    print "ALL DONE!!"

Por cierto, que no os engañe lo reducido del código. Detrás tenemos la enorme librería TensorFlow trabajando, y también una librería para Python llamada Pygame ;).

Conclusiones y discusión.

La principal conclusión que me gustaría destacar, es la de que la inteligencia artificial está avanzando de un modo casi exponencial estos últimos años (aunque los medios apenas se hagan eco de ello). Muy en particular, el grupo de IA dentro de Google llamado DeepMind[4] tiene gran parte de culpa. Sus papers de los últimos años son espectaculares, y están rebasando constantemente el estado del arte en casi todos los ámbitos. Y, además, señalar que todos y cada uno de los hitos conseguidos en el ámbito de la IA se están logrando sin excepción al imitarse cada vez mejor (en cuanto a eficiencia y escala) el modo en que la neurociencia nos cuenta que funciona nuestro cerebro. Cuestión que nos permite reflexionar sobre lo siguiente:

Las capacidades cognitivas humanas (y del resto del reino animal) parecen ser, como vemos, producto exclusivo del procesado eléctrico por entre trillones de sinapsis en el cerebro. Esto supone que sería precisamente este procesamiento de información eléctrica entre neuronas el que lograría otorgarnos todas y cada una de nuestras capacidades, sin que exista evidencia empírica alguna de que nada más intervenga en el proceso que origina lo que se entiende por mente: englobando aquí conducta, emociones, sensaciones, etc. Esta afirmación basa su fuerza en tres hechos probados: 1º) Que la moderna neurociencia indica experimentalmente que todo apunta a que las redes neuronales biológicas se sobran para acometer la gama completa cognitiva del hombre, 2º) que no se ha observado empíricamente nada más en el cuerpo humano capaz contribuir a tales procesos, y 3º) que además, por otra parte, al emular este comportamiento neuronal biológico de un modo computacional, se observan cada vez resultados más y más parecidos a los observados en los seres vivos.

Por lo tanto, esto nos permite concluir (gracias a todo lo que ya conocemos sobre las redes neuronales biológicas y artificiales) que:
Nuestra mente, en el fondo, no es más que el fruto de una enorme calculadora digital capaz de ejecutar en paralelo cientos de trillones de operaciones por segundo: operaciones que, por cierto, se reducen a meras sumas de potenciales eléctricos.

Referencias.

[1] http://arxiv.org/pdf/1602.01783v1.pdf "Asynchronous Methods for Deep Reinforcement Learning" (Google DeepMind) (2016)
[2] http://quevidaesta2010.blogspot.com.es/2016/02/las-matematicas-de-la-mente.html
[3] https://www.tensorflow.org/ (librería de código abierto ofrecida por Google)
[4] https://deepmind.com/publications.html (web del equipo de desarrollo Google DeepMind)