viernes, 14 de octubre de 2011

Computación evolutiva: Ejemplo VI

Abstract

Para el ejemplo VI de la serie práctica de algoritmos evolutivos, he implementado un OCR básico, a partir del ejemplo anterior donde realizamos un algoritmo capaz de reconocer los patrones de las vocales mediante aprendizaje evolutivo.

El OCR es bastante básico, pero permite comprobar la potencia de la computación evolutiva en relación al mundo del reconocimiento de patrones.

Ejemplo práctico VI: OCR básico

El programa que os presento a continuación; programado en un applet de Java para que podáis probarlo de primera mano, consigue precisamente eso: aprender a reconocer los patrones de las vocales, en formato Times New Roma, y un tamaño de 8 (8 x 5 = 40 píxeles). Se utiliza una red neuronal cuyos pesos se ajustan mediante una estrategia evolutiva.

El programa actua como un OCR, consiguiendo capturar la información textual dentro de una imagen (en formato GIF), y transcribiendo dicha información en una caja de texto.

Para agilizar la programación necesaria, el OCR sólo reconoce vocales en formato Times New Roman, y un tamaño de letra de 8.

En cualquier momento del entrenamiento, podemos pulsar en el botón Cancelar, y comprobar cómo va mejorando el reconocimiento del patrón.

Una vez entrenada la red neuronal, debemos introducir la ruta url de la imagen a procesar (en formato GIF), y pulsar sobre el botón Pasar OCR.

Las instrucción de uso se pueden esquematizar así:

1) Pulsa sobre el botón Entrenar para que la red neuronal aprenda a reconocer las vocales (espera hasta obtener una media de 120 en la función de evaluación).
2) Abre el Paint de Windows.
3) Crea un nuevo documento, y escribe con la herramienta de texto (con formato Times New Roman, y tamaño 8) todas las vocales que quieras, en cualquier lugar del documento (que pueden no ser consecutivos ni estar alineadas).
4) Guarda la imagen en formato GIF.
5) Sube la imagen a un hosting gratuito de imágenes. Por ejemplo: www.imgur.com.
6) Copia la ruta de la imagen subida (comprueba que la url termina en extensión ".gif").
7) Pega la url en el applet, donde dice URL de la imagen a procesar.
8) Pulsar sobre el botón Pasar OCR.

¡Y listo! Si todo va bien, el applet reconocerá todas las vocales del texto, y las introducirá en orden (y manteniedo los espacios en blanco entre ellas).


Debajo del siguiente applet encontrarás un enlace con el código fuente del ejemplo:

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo VI desde este enlace.


martes, 11 de octubre de 2011

Computación evolutiva: Ejemplo V

Abstract

El siguiente ejemplo práctico, consiste en una revisión del ejemplo anterior donde realizamos un algoritmo capaz de reconocer las vocales mediante aprendizaje evolutivo.

En esta versión, hemos introducido algo de ruido durante el entrenamiento y prueba del reconocimiento conseguido. El ruido consiste en la introducción o borrado de píxeles en la imagen de la vocal a reconocer.

Ejemplo práctico V: Proyecto evolutivo de reconocimiento de patrones con ruido

El programa que os presento a continuación; programado en un applet de Java para que podáis probarlo de primera mano, consigue precisamente eso: aprender a reconocer los patrones de las vocales, en formato Times New Roma, y un tamaño de 8 (8 x 5 = 40 píxeles). No se usa ninguna técnica estratégica mediante heurística, sólo una red neuronal cuyos pesos se ajustan mediante una estrategia evolutiva.

El programa, al ejecutarse inicialmente el applet, sólo no tiene "conocimiento" alguno, y se limita a dar una respuesta aleatoria, cuando se le presenta los píxles de la imagen de una vocal en Times New Roman.

Para conseguir, de manera on-line; que el programa aprenda a reconocer el patrón de las vocales, debemos pulsar sobre el botón Entrenar. En ese momento, el programa comenzará a seleccionar evolutivamente, los individuos que mejor aproximen sus respuestas cuando se le interroga por las vocales.

En cualquier momento del entrenamiento, podemos pulsar en el botón Cancelar, y comprobar cómo va mejorando el reconocimiento del patrón.

Debajo del siguiente applet con el ejemplo V, explicaré más en profundidad la teoría que sigue el proyecto de aprendizaje automático, y encontrarás un enlace con el código fuente del ejemplo:

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo V desde este enlace.


Explicación técnica del ejemplo


El proyecto de ejemplo V, tiene las siguientes características técnicas:

Todo el aprendizaje corre a cuenta de una red neuronal; con conexión hacia delante y una capa oculta (hidden layer).

La capa de entrada contiene 40 nodos; uno por cada posible contenido dentro del array que forma el conjunto de píxles de la imagen de la vocal. Hay 5 nodos de salida, los cuales indican la probabilidad de que la vocal pasada sea la que representa sicho nodo de salida.

Inicialmente, los pesos wij de la red neuronal son marcados aleatoriamente, por lo que la respuesta de la red neuronal ante el problema será aleatorio.

Hay pues que entrenar al programa para que aprenda, lo que vamos a conseguir ajustando evolutivamente los pesos de la red neuronal utilizada. Dicho entrenamiento evolutivo se realizará mediante una estrategia evolutiva.

La estrategia evolutiva será representada por un vector de 210 elementos de tipo real. Esos elementos o individuos de selección se van a corresponder con los pesos de los nodos que contiene la red neuronal, de manera que serán esos pesos los que irán evolucionando.

Así pues, la población en evolución, consistirá en n individuos (con n = 15), que tendrán n hijos, con variación exclusiva por mutación -sin recombinación- y cuya función de desempeño (fitness fuction) será calculada mediante competición -selección por torneo-.
Para el proceso de mutación, hay que tener en cuenta que cada individuo; además de un vector de pesos, contiene un vector de variables de ajuste, que también irá evolucionando junto con los pesos.

La mutación es de la forma:



Con alpha igual 0.2f, y donde xi indica el peso en la posición i del vector de pesos, y N(0,1) indica un valor tomado aleatoriamente de una distribución normal de desviación típica igual a 1, y media igual a 0. La otra variable que interviene en el proceso se corresponde con la variable de ajuste del elemento i, que; como se puede ver, muta antes de que lo haga el peso xi.

La evaluación de un individuo se realiza mediante el recuento de los aciertos conseguidos cuando se le pasa las 5 vocales. Se sumará uno cuando se acierta, y se restará uno si falla en el reconocimiento.

Finalmente, el proceso de selección consistirá en tomar los 15 mejores individuos de entre los 30 (15 padres + 15 hijos) individuos de la generación en curso. En caso de empate en la función de desempeño, se favorecerá a los individuos más longevos.

El paso de generaciones tendrá como resultado un ajuste en los pesos de la red neuronal, lo que dará lugar a un entrenamiento de la misma, que será lo que permitirá; a su vez, al programa a reconocer los patrones de las vocales dadas en una imagen GIF.


lunes, 10 de octubre de 2011

Computación evolutiva: Ejemplo IV

Abstract

Siguiendo con la serie de ejemplos prácticos -pulsa aquí para ver el ejemplo III: Aprendizaje automático en el juego 4 en raya- y disponibles con licencia GPL, de algoritmos evolutivos, voy a mostrar ahora otro ejemplo práctico. En esta ocasión se trata de diseñar un algoritmo capaz de aprender a reconocer las letras de las vocales dadas en un archivo GIF.

El esquema principal seguido es el mismo del ejemplo II y III de la serie de ejemplos evolutivos prácticos que estoy desarrollando. Es decir; haciendo uso de estrategias evolutivas y una red neuronal se consigue un apndizaje automático por parte de un programa.

Ejemplo práctico IV: Proyecto evolutivo de reconocimiento de patrones

El programa que os presento a continuación; programado en un applet de Java para que podáis probarlo de primera mano, consigue precisamente eso: aprender a reconocer los patrones de las vocales, en formato Times New Roma, y un tamaño de 8 (8 x 5 = 40 píxeles). No se usa ninguna técnica estratégica mediante heurística, sólo una red neuronal cuyos pesos se ajustan mediante una estrategia evolutiva.

El programa, al ejecutarse inicialmente el applet, sólo no tiene "conocimiento" alguno, y se limita a dar una respuesta aleatoria, cuando se le presenta los píxles de la imagen de una vocal en Times New Roman.

Para conseguir, de manera on-line; que el programa aprenda a reconocer el patrón de las vocales, debemos pulsar sobre el botón Entrenar. En ese momento, el programa comenzará a seleccionar evolutivamente, los individuos que mejor aproximen sus respuestas cuando se le interroga por las vocales.

En cualquier momento del entrenamiento, podemos pulsar en el botón Cancelar, y comprobar cómo va mejorando el reconocimiento del patrón.

Debajo del siguiente applet con el ejemplo IV, explicaré más en profundidad la teoría que sigue el proyecto de aprendizaje automático, y encontrarás un enlace con el código fuente del ejemplo:

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo IV desde este enlace.


Explicación técnica del ejemplo


El proyecto de ejemplo IV, tiene las siguientes características técnicas:

Todo el aprendizaje corre a cuenta de una red neuronal; con conexión hacia delante y una capa oculta (hidden layer).

La capa de entrada contiene 40 nodos; uno por cada posible contenido dentro del array que forma el conjunto de píxles de la imagen de la vocal. Hay 5 nodos de salida, los cuales indican la probabilidad de que la vocal pasada sea la que representa sicho nodo de salida.

Inicialmente, los pesos wij de la red neuronal son marcados aleatoriamente, por lo que la respuesta de la red neuronal ante el problema será aleatorio.

Hay pues que entrenar al programa para que aprenda, lo que vamos a conseguir ajustando evolutivamente los pesos de la red neuronal utilizada. Dicho entrenamiento evolutivo se realizará mediante una estrategia evolutiva.

La estrategia evolutiva será representada por un vector de 210 elementos de tipo real. Esos elementos o individuos de selección se van a corresponder con los pesos de los nodos que contiene la red neuronal, de manera que serán esos pesos los que irán evolucionando.

Así pues, la población en evolución, consistirá en n individuos (con n = 15), que tendrán n hijos, con variación exclusiva por mutación -sin recombinación- y cuya función de desempeño (fitness fuction) será calculada mediante competición -selección por torneo-.
Para el proceso de mutación, hay que tener en cuenta que cada individuo; además de un vector de pesos, contiene un vector de variables de ajuste, que también irá evolucionando junto con los pesos.

La mutación es de la forma:



Con alpha igual 0.2f, y donde xi indica el peso en la posición i del vector de pesos, y N(0,1) indica un valor tomado aleatoriamente de una distribución normal de desviación típica igual a 1, y media igual a 0. La otra variable que interviene en el proceso se corresponde con la variable de ajuste del elemento i, que; como se puede ver, muta antes de que lo haga el peso xi.

La evaluación de un individuo se realiza mediante el recuento de los aciertos conseguidos cuando se le pasa las 5 vocales. Se sumará uno cuando se acierta, y se restará uno si falla en el reconocimiento.

Finalmente, el proceso de selección consistirá en tomar los 15 mejores individuos de entre los 30 (15 padres + 15 hijos) individuos de la generación en curso. En caso de empate en la función de desempeño, se favorecerá a los individuos más longevos.

El paso de generaciones tendrá como resultado un ajuste en los pesos de la red neuronal, lo que dará lugar a un entrenamiento de la misma, que será lo que permitirá; a su vez, al programa a reconocer los patrones de las vocales dadas en una imagen GIF.


lunes, 3 de octubre de 2011

Cómo desaparecer completamente


Tristeza. Es lo único que desde hace años veo en sus ojos.

Esa que está ahí no es ella (That there, that's not she), porque ella siempre va donde quiere (She goes, where she pleases). Ella ya no está aquí (She’s not here), aunque parezca mentira (This isn't happening). Y en poco tiempo se irá (In a little while she'll be gone). Su tiempo ya ha pasado (The moment's already passed). Ya no está aquí (She’s not here), y no me lo puedo creer (This isn't happening).

La vida es dura, y cruel. Eso lo sabemos todos. Y sin embargo, tenemos motivaciones para luchar. Pero llega un momento; y, por supuesto, con suerte, en el que éstas desaparecen. Lo que nos hacía levantarnos cada mañana, aquello que nos hacía soportar la vida tal y como es.

Debe ser tan sencillo como levantarse un día, y darse cuenta de que ya no eres el que solías ser. Que ese que está ahí, no eres tú (That there, that’s not me). Dejas de ser útil a la sociedad, y te conviertes en una carga, en un ser inválido (The moment's already passed Yeah, it's gone).

Casi sin darte cuenta, ya no puedes hacer nada por ti mismo, no puedes cruzar la puerta
(I go where I please. I walk through walls).

Tormento. No creo que exista otra palabra que describa mejor vivir una vida sin motivaciones. Tormento que podemos ver en los ojos de cualquier anciano, en los ojos de cualquier persona no válida. Desesperación. Tristeza. Se puede casi palpar su constante lucha por querer vivir. Y no hay posible consuelo. Sólo podemos besarles, abrazarles, y; por supuesto, en cuanto podamos mirar hacia otro lado.

No es sólo una muestra más, sino la muestra más clara de la insoportable levedad del ser.

Nota:

La canción que has podido escuchar de fondo, es “How to disappear completely” del grupo Radiohead. A continuación tenéis la letra completa:

That there, that's not me
I go where I please
I walk through walls
I float down the Liffey

I'm not here
This isn't happening
I'm not here, I'm not here

In a little while
I'll be gone
The moment's already passed
Yeah, it's gone

I'm not here
This isn't happening
I'm not here, I'm not here

Strobe lights and blown speakers
Fireworks and hurricanes

I'm not here
This isn't happening
I'm not here, I'm not here....



sábado, 1 de octubre de 2011

Computación evolutiva: Ejemplo III

Abstract

Siguiendo con la serie de ejemplos prácticos -pulsa aquí para ver el ejemplo II: Aprendizaje de estrategias no-loss en el juego 3 en raya o Tic-tac-toe- y disponibles con licencia GPL, de algoritmos evolutivos, voy a mostrar ahora otro ejemplo práctico. En esta ocasión se trata de diseñar un algoritmo capaz de aprender por si solo a jugar bien al famoso juego Conecta 4 -o cuatro en línea-.

El esquema principal seguido es el mismo del ejemplo II de la serie de ejemplos evolutivos prácticos que estoy desarrollando. Es decir; se sigue la idea detrás de Blondie24; un juego de damas, implementado por David B. Fogel, que; haciendo uso de Estrategias Evolutivas y una red neuronal, consiguió que el programa aprendiera, tras 8 meses de entrenamiento, a jugar bien a las damas. Y tan bien aprendió, que consiguió un rating de 2048 –un 99,6% mejor que cualquier jugador humano-.

Ejemplo práctico III: Proyecto Evolutivo para el juego Cuatro en Línea -o Conecta 4-

El programa que os presento a continuación; programado en un applet de Java para que podáis probarlo de primera mano, consigue precisamente eso: aprender a jugar al Cuatro en Línea, sin enseñarle a priori ninguna técnica estratégica mediante heurística, sólo usando una red neuronal cuyos pesos se ajustarán mediante una estrategia evolutiva.

El programa, al ejecutarse inicialmente el applet, sólo "conoce" las reglas básicas del juego, cuándo termina la partida y el resultado de la misma, y sólo sabe prever si tu próximo movimiento la hará perder. Pero no entiende de estrategias, ni es capaz de jugar demasiado bien.

Si pulsamos en el botón Estadísticas, podremos ver cuántas partidas empata o pierde en este momento el programa tras jugar 150 veces contra un jugador que puede ver 6 jugadas hacia delante (ply =6).

Si pulsamos sobre el botón Jugar, veremos que, en cuanto pensamos un poco y aplicamos una estrategia correcta, comenzamos a ganar partidas.

Para conseguir, de manera on-line; que el programa aprenda a jugar, debemos pulsar sobre el botón Entrenar. En ese momento, el programa comenzará a competir consigo mismo una y otra vez, mejorando con el paso de tiempo –de las generaciones- de manera automática su juego. Irá aprendiendo buenas estrategias de juego.

En cualquier momento del entrenamiento, podemos pulsar en el botón Cancelar, y comprobar cómo va mejorando el juego del programa. Si jugamos contra él, veremos que estrategias con las que antes le ganábamos, ahora ya no son efectivas, o que, si pulsamos sobre el botón Estadísticas, el porcentaje de partidas perdidas va disminuyendo.

Debajo del siguiente applet con el ejemplo III, explicaré más en profundidad la teoría que sigue el proyecto de aprendizaje automático, y encontrarás un enlace con el código fuente del ejemplo:

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo III desde este enlace.


Explicación técnica del ejemplo


El proyecto de ejemplo III, tiene las siguientes características técnicas:

Toda la estrategia de juego, corre a cuenta de una red neuronal; con conexión hacia delante y una capaa oculta (hidden layer).

La capa de entrada contiene 42 nodos; uno por cada posible contenido dentro del array que forma el tablero de juego –con un 1 si la ficha de una casilla es propia, un -1 si la ficha es del adversario, y un 0 si la casilla está vacía-, y un nodo de salida, responsable de devolver el resultado del proceso neuronal: expresando lo bueno o malo que un movimiento concreto es.

Inicialmente, los pesos wij de la red neuronal son marcados aleatoriamente, por lo que la respuesta de la red neuronal ante el problema sobre qué buena o mala es una jugada será también aleatoria.

Hay pues que entrenar al programa para que aprenda a evaluar las jugadas, lo que vamos a conseguir ajustando evolutivamente los pesos de la red neuronal utilizada. Dicho entrenamiento evolutivo se realizará mediante una estrategia evolutiva.

La estrategia evolutiva será representada por un vector de 1848 elementos de tipo real. Esos elementos o individuos de selección se van a corresponder con los pesos de los nodos que contiene la red neuronal, de manera que serán esos pesos los que irán evolucionando.

Así pues, la población en evolución, consistirá en n individuos (con n = 15), que tendrán n hijos, con variación exclusiva por mutación -sin recombinación- y cuya función de desempeño (fitness fuction) será calculada mediante competición -selección por torneo-.
Para el proceso de mutación, hay que tener en cuenta que cada individuo; además de un vector de pesos, contiene un vector de variables de ajuste, que también irá evolucionando junto con los pesos.

La mutación es de la forma:



Con alpha igual 0.2f, y donde xi indica el peso en la posición i del vector de pesos, y N(0,1) indica un valor tomado aleatoriamente de una distribución normal de desviación típica igual a 1, y media igual a 0. La otra variable que interviene en el proceso se corresponde con la variable de ajuste del elemento i, que; como se puede ver, muta antes de que lo haga el peso xi.

La evaluación de un individuo se realiza mediante q partidas (con q = 15), jugadas entre el individuo a evaluar, y otro individuo de la población, tomado aleatoriamente sin reemplazamiento. Cada partida ganada le sumará 1 punto, las perdidas le restará 2, y los empates no suman nada. El valor final será su función de desempeño.

Finalmente, el proceso de selección consistirá en tomar los 15 mejores individuos de entre los 30 (15 padres + 15 hijos) individuos de la generación en curso. En caso de empate en la función de desempeño, se favorecerá a los individuos más longevos.

El paso de generaciones tendrá como resultado un ajuste en los pesos de la red neuronal, lo que dará lugar a un entrenamiento de la misma, que será lo que permitirá; a su vez, al programa a jugar bien, sin intervención heurística alguna.

Detalles adicionales


1º) Para que el aprendizaje automático tenga lugar, es necesario; al menos, prever un movimiento del contrario por adelantado -qué hará él si yo muevo aquí-. Esto se consigue mediante el uso de un árbol min-max de profundidad 2 (ply=2 ), lo que es insuficiente para que la máquina comprenda estrategias, pero que sí permite una base para el ajuste de pesos de la red neuronal. Pero siempre evaluar lo bueno o malo de una jugada será objeto de la red neuronal. Es decir; aunque hay un árbol min-max de ply igual a dos, la evaluación de la tabla de tu movimiento y el mejor movimiento del contrario, la desempeña la red neuronal y no ninguna regla a priori.

2º) La red neuronal utilizada va a devolver siempre un valor real en el rango [-1,1]. Un -1 sólo cuando el movimiento a evaluar termina siendo una victoria del adversario, un 1 si la victoria es suya, y un valor entre (-1, 1) indicando lo bueno que es una jugada para el programa (valores cercanos a 1) o para el contrario (valores cercanos a 1).

3º) El campo llamado Info, en el lateral superior derecho del applet, contiene información sobre el estado del entrenamiento. Conforme pasan las generaciones, la pantalla se actualiza, y muestra datos sobre el mejor individuo de la última generación: su función de desempeño, su edad -cuanto tiempo lleva en el pool evolutivo-, y la media de su juego -resultados/edad-. Cuando juegues contra la máquina, te mostrará la misma información, pero sobre jugador contra el que estás jugando -el mejor que se encontró-.


viernes, 2 de septiembre de 2011

Computación evolutiva: Ejemplo II

Abstract

Siguiendo con la serie de ejemplos prácticos -pulsa aquí para ver el ejemplo I- y disponibles con licencia GPL, de algoritmos evolutivos, voy a mostrar ahora algo que, bien entendido; se comprende es un logro importante, que debemos a dos fundamentales ramas de la IA; como son la computación evolutiva y las redes neuronales.

Estoy hablando de la posibilidad de crear programas que adquieran “conocimiento” estratégico automáticamente sin necesidad de que un programador humano implemente ninguna regla heurística de aprendizaje.

La idea de este ejemplo parte de Blondie24; un juego de damas, implementado por David B. Fogel, que; haciendo uso de Estrategias Evolutivas y una red neuronal, consiguió que el programa aprendiera, tras 8 meses de entrenamiento, a jugar bien a las damas. Y tan bien aprendió, que consiguió un rating de 2048 –un 99,6% mejor que cualquier jugador humano-.

Lo increíble de Blondie24, era que el programador, en ningún momento implementó reglas heurísticas para el correcto funcionamiento del programa. No se escribió qué jugadas son mejores que otras, ni se propuso ningún mecanismo de recompensa por buenas jugadas, ni hizo falta la intervención humana en el aprendizaje en modo alguno. El programador sólo implementó de forma prestablecida las reglas básicas del juego: qué movimientos están permitidos hacer y cuándo alguien ha ganado el juego, y fue Blondie24 la que “aprendió” a jugar, como lo hace cualquier jugador humano –al que nadie le enseñe estrategias sino que aprenda por si mismo-: progresivamente mediante ensayo y error.

Ejemplo práctico II: Proyecto Noelia

Utilizando la teoría tras Blondie24, y algunos de los detalles geniales que David B. Fogel introdujo en su proyecto, me propuse hacer algo similar, pero a menor escala; para poder afrontarlo en poco tiempo, aunque sin perder su esencia.

Para conseguirlo, opté por implementar un programa que “aprenda” por si sólo buenas estrategias de un juego, mucho más elemental que las damas, y bastante utilizado en los ejemplos teóricos de IA: el juego del tres en raya, también conocido como tic-tac-toe. Aunque; tengo que reconocer, finalmente, me propuse algo un poco más ambisioso que eso, consguir no sólo que juegue buenas estrategias, sino que alcance estrategias con las que nunca pierda (no-loss-strategy).

El programa que os presento a continuación; programado en un applet de Java para que podáis probarlo de primera mano, consigue precisamente eso: aprender a jugar al tres en raya, sin enseñarle a priori ninguna técnica estratégica mediante heurística, sólo usando una red neuronal cuyos pesos se ajustarán mediante una estrategia evolutiva.

El programa, al ejecutarse inicialmente el applet, sólo "conoce" las reglas básicas del juego, cuándo termina la partida y el resultado de la misma, y sólo sabe prever si tu próximo movimiento la hará perder. Pero no entiende de estrategias, ni es capaz de jugar bien. Al comienzo, el programa tiene el nivel de una persona muy novata en el juego.

Si pulsamos en el botón Estadísticas, podremos ver cuántas partidas empata o pierde en este momento el programa tras jugar 50 veces contra un jugador experto -simulado mediante heurística, con un árbol min-max y profundidad (ply) de 9 movimientos-. De media, antes de entrenar, el programa perderá un 50% de sus partidas contra dicho jugador experto.

Si pulsamos sobre el botón Jugar, veremos que, en cuanto pensamos un poco y aplicamos una estrategia correcta, comenzamos a ganar partidas.

Para conseguir, de manera on-line; que el programa aprenda a jugar, debemos pulsar sobre el botón Entrenar. En ese momento, el programa comenzará a competir consigo mismo una y otra vez, mejorando con el paso de tiempo –de las generaciones- de manera automática su juego. Irá aprendiendo buenas estrategias de juego.

En cualquier momento del entrenamiento, podemos pulsar en el botón Cancelar, y comprobar cómo va mejorando el juego del programa. Si jugamos contra él, veremos que estrategias con las que antes le ganábamos, ahora ya no son efectivas, o que, si pulsamos sobre el botón Estadísticas, el porcentaje de partidas perdidas va disminuyendo.

Pasadas las generaciones –si se le permite entrenar bastante tiempo-, el programa será igual de efectivo que un jugador humano. Lo que significa que; lo mejor que podrás conseguir contra él serán tablas. El aprendizaje ocurrirá poco a poco, lo que significa que verás una mejora gradual en su calidad de juego. Por poner un ejemplo; es de notar que, tras pasar algunas generaciones de entrenamiento; el programa "comprende" que la mejor estrategia de juego consiste en mover a la casilla central siempre que esté libre.

Hay de tener en cuenta, que sólo notarás una mejora realmente significativa en la calidad de juego a partir de trascurridas algunas generaciones, y que un juego de estrategia no-loss suele llegar aproximadamente en 150 generaciones.

Debajo del siguiente applet con el ejemplo II, explicaré más en profundidad la teoría que sigue el proyecto de aprendizaje automático, y encontrarás un enlace con el código fuente del ejemplo:

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo II desde este enlace.


Explicación técnica del ejemplo


El proyecto de ejemplo II, tiene las siguientes características técnicas:

Toda la estrategia de juego, corre a cuenta de una red neuronal; con conexión hacia delante y dos capas ocultas (hidden layer).

La capa de entrada contiene nueve nodos; uno por cada posible contenido dentro del array que forma el tablero de juego –con un 1 si la ficha de una casilla es propia, un -1 si la ficha es del adversario, y un 0 si la casilla está vacía-, y un nodo de salida, responsable de devolver el resultado del proceso neuronal: expresando lo bueno o malo que un movimiento concreto es.

Tenemos así una red neuronal compuesta de 9 nodos de entrada xi, 21 nodos en las capas ocultas, y un nodo de salida. Más otros 9 nodos de activación (nodos bias).

Inicialmente, los pesos wij de la red neuronal son marcados aleatoriamente, por lo que la respuesta de la red neuronal ante el problema sobre qué buena o mala es una jugada será también aleatoria.

Hay pues que entrenar al programa para que aprenda a evaluar las jugadas, lo que vamos a conseguir ajustando evolutivamente los pesos de la red neuronal utilizada. Dicho entrenamiento evolutivo se realizará mediante una estrategia evolutiva.

La estrategia evolutiva será representada por un vector de 9 elementos de tipo real. Esos elementos o individuos de selección se van a corresponder con los pesos de los nodos que contiene la red neuronal, de manera que serán esos pesos los que irán evolucionando.

Así pues, la población en evolución, consistirá en n individuos (con n = 15), que tendrán n hijos, con variación exclusiva por mutación -sin recombinación- y cuya función de desempeño (fitness fuction) será calculada mediante competición -selección por torneo-.
Para el proceso de mutación, hay que tener en cuenta que cada individuo; además de un vector de pesos, contiene un vector de variables de ajuste, que también irá evolucionando junto con los pesos.

La mutación es de la forma:



Con alpha igual 0.2f, y donde xi indica el peso en la posición i del vector de pesos, y N(0,1) indica un valor tomado aleatoriamente de una distribución normal de desviación típica igual a 1, y media igual a 0. La otra variable que interviene en el proceso se corresponde con la variable de ajuste del elemento i, que; como se puede ver, muta antes de que lo haga el peso xi.

La evaluación de un individuo se realiza mediante q partidas (con q = 15), jugadas entre el individuo a evaluar, y otro individuo de la población, tomado aleatoriamente sin reemplazamiento. Cada partida ganada le sumará 1 punto, las perdidas le restará 2, y los empates no suman nada. El valor final será su función de desempeño.

Finalmente, el proceso de selección consistirá en tomar los 15 mejores individuos de entre los 30 (15 padres + 15 hijos) individuos de la generación en curso. En caso de empate en la función de desempeño, se favorecerá a los individuos más longevos.

El paso de generaciones tendrá como resultado un ajuste en los pesos de la red neuronal, lo que dará lugar a un entrenamiento de la misma, que será lo que permitirá; a su vez, al programa a jugar bien, sin intervención heurística alguna.

Detalles adicionales


1º) Para que el aprendizaje automático tenga lugar, es necesario; al menos, prever un movimiento del contrario por adelantado -qué hará él si yo muevo aquí-. Esto se consigue mediante el uso de un árbol min-max de profundidad 2 (ply=2 ), lo que es insuficiente para que la máquina comprenda estrategias, pero que sí permite una base para el ajuste de pesos de la red neuronal. Pero siempre evaluar lo bueno o malo de una jugada será objeto de la red neuronal. Es decir; aunque hay un árbol min-max de ply igual a dos, la evaluación de la tabla de tu movimiento y el mejor movimiento del contrario, la desempeña la red neuronal y no ninguna regla a priori.

2º) La red neuronal utilizada va a devolver siempre un valor real en el rango [-1,1]. Un -1 sólo cuando el movimiento a evaluar termina siendo una victoria del adversario, un 1 si la victoria es suya, y un valor entre (-1, 1) indicando lo bueno que es una jugada para el programa (valores cercanos a 1) o para el contrario (valores cercanos a 1).

3º) Hay un check box llamado no-loss, que, cuando se activa, indica que el proceso de entrenamiento, se hará jugando contra el jugador experto simulado mediante heurística. De esa manera, el aprendizaje es más rápido y siempre termina (tras unas 100 generaciones aproximadamente) alcanzando una estrategia no-loss. Cuando el check está desactivado, el entrenamiento se realiza contra sí mismo y podría ser que la estrategia no-loss no se alcance -aunque siempre quedará cerca-.

4º) El campo llamado Info, en el lateral superior derecho del applet, contiene información sobre el estado del entrenamiento. Conforme pasan las generaciones, la pantalla se actualiza, y muestra datos sobre el mejor individuo de la última generación: su función de desempeño, su edad -cuanto tiempo lleva en el pool evolutivo-, y la media de su juego -resultados/edad-. Cuando juegues contra la máquina, te mostrará la misma información, pero sobre jugador contra el que estás jugando -el mejor que se encontró-.

5º) La mejor manera de saber si hemos llegado a una estrategia no-loss, es ir mirando la pantalla de información, y esperar a que aparezcan datos en los que el mejor jugador encontrado tiene f = 0.0 y una edad avanzada (más de 20). Si pulsamos sobre el botón cancelar, y luego sobre el botón Estadísticas, deberá devolver siempre un 100% de empates contra el jugador experto.

Conclusión

Lo que pensé iba a ser un ejemplo fácil de conseguir, ha resultado ser una verdadera pesadilla, que me ha llevado un par de semanas de mi tiempo libre.

Mi cabezonería me ha impedido abandonar, y ahora me alegro, porque aprendí muchísimo sobre redes neuronales, y he podido avanzar en este campo de la IA que me parece fundamental, y que creo hace un muy buen equipo con la computación evolutiva. Es más, me atrevería a decir, que el futuro desarrollo de ambas ramas podría llegar a conseguir una verdadera inteligencia artificial. No sé si viviré para verlo, pero aquí queda dicho :).

Gracias por compartir vuestro tiempo conmigo, espero que el proyecto les haya gustado, y el código fuente les pueda servir de ejemplo.

domingo, 21 de agosto de 2011

Computación evolutiva: Ejemplo I

Abstract

Entendemos por un algoritmo evolutivo, a aquel cuya técnica resolutiva se inspira en la evolución biológica de la naturaleza; imitando de esa manera, el proceso de selección natural que, desde Darwin, es aceptado como motor del proceso evolutivo de los seres vivos.
Existe en la red, abundante información teórica al respecto de la computación evolutiva, pero en muy pocos casos, me he encontrado con ejemplos prácticos concretos, donde se pongan en práctica dichos principios.

De manera que me he propuesto intentar poner mi granito de arena práctico en esta rama tan interesante de la inteligencia artificial, iniciando lo que espero sea una serie de artículos con ejemplos funcionales -de código abierto-, para ayudar a cualquiera que quiera adentrarse en la materia. Además, espero que realizar esta propuesta, me ayudará a profundizar en mis conocimientos sobre el tema, y prepararme así para realizar el doctorado en ingeniería, que me gustaría algún día tener tiempo de hacer.

Composición y funcionamiento de un algoritmo evolutivo (AE)

Sin duda, el mejor recurso bibliográfico para iniciarse en el mundo de la CE -compuactión evolutiva- es el magnífico libro Introduction to Evolutionary Computing, de los autores Agoston E. Eiben, y J.E. Smith.

Hemos dicho que la computación evolutiva es una ciencia computacional en la que sus algoritmos imitan el proceso evolutivo de la naturaleza. Veamos de qué partes consta, y cómo funcionan:

Cualquier AE seguirá el siguiente pseudocódigo:



Existe una población de n individuos, los cuales expresan una posible solución. La población es pues, un multiconjunto de genotipos, y forman la unidad de evolución.

Los individuos de la población se van renovando en sucesivas generaciones, que van convergiendo evolutivamente hacia la meta deseada, que no es otra que encontrar una solución a un problema determinado. La evolución se produce durante el paso de las generaciones, y cada generación cumple con el siguiente procedimiento:

La primera generación es especial, y sólo consiste en la generación (y evaluación) de una población inicial de n individuos, normalmente generados aleatoriamente.

El resto de generaciones comienzan con la selección de los individuos que se van a reproducir. Es decir, se seleccionan los padres que conformarán la descendencia.
Y dicha descendencia, será el resultado de un proceso de recombinación y mutación de esos padres previamente seleccionados. Tras la creación de la descendencia, se procede a evaluar su adaptabilidad. Es decir; se calcula, lo bien o mal que se adapta el nuevo individuo a las condiciones del medio ambiente. Este proceso se suele realizar, mediante el uso de una funcion de desempeño (fitness function). Dicha función representará la adaptabilidad del fenotivo expresado por el genotipo en cuestión. Una vez evaluada la progenie, se procede a seleccionar los individuos que finalmente prevalecerán para formar la siguiente generación.

Todo este proceso generacional, se repetirá mientras no se cumpla una condición de parada. La condición de parada normalmente será, o bien que uno o varios genomas expresan un fenotipo que es solución óptima del problema a resolver, o que se alcanzó el máximo de generaciones previstos programáticamente.

Ejemplo I: Proyecto Paula

A continuación, vamos a ver en acción un algoritmo evolutivo, que he escrito en un applet de Java, y que tenéis embebido justo debajo de estas líneas –a continuación del applet tenéis un manual de uso- :

No puede ejecutar applets de Java. Instale la JVM, y vuelva a recargar la página, por favor.

Podéis descargar el código fuente del ejemplo desde este enlace.

El mismo algoritmo escrito completamente en Javascript, utilizando hilos mediante la nueva clase Worker nativa de HTML5 (puedes obtener el código fuente en los recursos del navegador, ya que no hay código ofuscado):


Manual de uso

Mediante el algoritmo del ejemplo, vamos a intentar encontrar la solución óptima -si existe-, o la mejor aproximación posible, al siguiente problema:

Tenemos como entrada (inputs), una serie de puntos definidos para ciertos valores de una variable, y queremos buscar la función de interpolación que sea óptima -que pase por todos los puntos dados como entrada- o lo más aproximada posible.

Para introducir los inputs en el applet, debemos rellenar el campo de texto "Datos de interpolación", añadiendo cuantos puntos deseemos. Una entrada válida sería, por ejemplo:

f0 = 0, f1 = 1, f2 = 4, f3 = 9

Si ahora, pulsamos sobre el botón Ejecutar algoritmo evolutivo, el programa finalizará, encontrando una de las muchas funciones que interpolan esos puntos, por ejemplo:

f(x) = x*x ó f(x) = x*x+x-x

Para recalcar la potencia del proceso evolutivo por selección, se propone la posibilidad de intentar encontrar la solución, sin realizar la recombinación, mutación y selección de los más aptos, lo que resulta en un algoritmo completamente aleatorio que muy poco probablemente llegará a solucionar el problema. Para probar dicho algoritmo aleatorio, hay que pulsar sobre el botón Ejecutar algoritmo aleatorio.

Para probar la potencia del programa, se ha habilitado la posibilidad de calcular automáticamente los puntos a interpolar, a partir de una función de nuestra elección. Con esto, nos aseguramos además, de que existe solución óptima. Sólo hay que pulsar sobre el botón Buscar datos de interpolación, y se nos abrirá una ventana donde deberemos escribir la función generadora de n términos de interpolación.

Es importante señalar, que sólo se permite los operadores + - * /, y que la prioridad de los mismos es la tradicional. Se permiten números con decimales (con el punto como separador, y una precisión máxima de cuatro decimales), pero no el operador unitario -.

Al finalizar la ejecución del algoritmo, se abrirá una nueva ventana emergente con un par de gráficas, la primera representando los puntos de interpolación, y una segunda con los puntos representados por la función encontrada por el programa. Ambas gráficas coincidirán siempre y cuando se encuentre una solución óptima -si es que existe-.

Análisis del algoritmo

La representación del genotipo en nuestro ejemplo, consiste en un array de caracteres, conformando una cadena de texto, con los siguientes símbolos permitidos:{[0-9],x,+,-,/,*}

Además, los símbolos deben respetar un patrón que forme cadenas con sentido aritmético (pero no se permite usar el símbolo - como operador unitario), permitiéndose genotipos como 2*x*x-1/2*x+23, pero no otros como 2+-x/*.

El AE del ejemplo I responde al siguiente pseudocódigo:


t <- 0
Inicialización P(t)
Evaluación P(t)
hacer
S <- Seleccion_padres[P(t)]
Q = Operacion_variación[S]
Evaluación[Q)]
P(t+1) <- selección[P(t) U Q]
t <- t + 1
mientras no condición de parada



donde S, Q, y P son poblaciones de individuos, y t es una variable de tipo entera.

El algoritmo comienza creando aleatoriamente una población P de n individuos, y evaluando la función de desempeño de sus individuos -lo cerca o lejos que están de ser una solución óptima al problema-.

Posteriormente se entra en un bucle del que sólo se saldrá cuando se cumpla que, o bien en la población P(t) hay un individuo que es solución óptima, o bien se ha llegado al máximo de iteraciones previsto en la configuración del programa.

Cada iteración del bucle, se va a corresponder con una nueva generación de individuos, formada por parte de los padres de la generación anterior y por parte de su progenie.

Para realizar una generación se procede como sigue:

1) Se forma una población S con los individuos que van a poder procrear. En el caso concreto de mi ejemplo, todos los miembros de la población P van a tener descendencia. Es decir; S = P(t).

2) Se procede a crear la descendencia mediante recombinación, y mutación. En el ejemplo, se van seleccionando pares de individuos de S y se recombinan dando lugar a cuatro hijos.

Para recombinar, seleccionamos al padre y lo dividimos por un punto aleatorio p (quedando el padre partido en dos partes: padre1 y padre2). Hacemos lo mismo con la madre en un punto p', y se crean los hijos de la siguiente manera:

h1 = padre1 + madre2
h2 = madre1 + padre2
h3 = padre1
h4 = padre2


Posteriormente, cada hijo, sufrirá -con 100% de probabilidad- algún tipo de mutación puntual, que podrá ser más o menos leve, y que podrá modificar algún valor concreto del genotipo del individuo, o añadir o eliminar partes del mismo.

El proceso de reproducción, se repetirá 16 veces por cada par de padres, lo que dará un total de n*16*4 hijos en cada generación -siendo n el número de individuos en la población-.

3) El siguiente paso es evaluar la adaptabilidad de esta población Q que conforman la nueva descendencia.

4) Por último, se procede a seleccionar aquellos individuos que mejor se adaptan al medio de la unión del conjunto de padres de la generación anterior P(t) y el conjunto de sus hijos Q. Como resultado, se obtiene el conjunto P de la generación siguiente, con los supervivientes del proceso de selección.

En nuestro ejemplo, la evaluación de un genotipo se procesa mediante la aplicación de una función de desempeño (fitness function) al mismo. En este caso, la función será el resultado de sumar la diferencia en valor absoluto entre g(x) y el input en dicho punto: abs(y-g(x)).

Un genotipo óptimo, será aquel cuya función de desempeño tenga valor 0.

Para el AE del ejemplo, la selección generacional, se hace ordenando los individuos de [P(t) U Q] según su valor de adaptación -cuanto menos diferencia entre g(x) y los puntos de interpolación mejor-, y seleccionando los s primeros, desechándose de esa manera el resto.

Resumen

El algoritmo evolutivo propuesto, es capaz de encontrar; si existe, una solución óptima al problema con bastante frecuencia, aunque; como en todo AE existe la posibilidad de que la evolución generacional se localice en torno a una solución local no óptima -pero muy cercana de serlo-. Sin embargo, la aleatoriedad de la fase de mutación, permite teóricamente recomenzar un nuevo camino evolutivo en cualquier momento -aunque es menos probable que esto ocurra conforme pasan las generaciones-.