domingo, 31 de octubre de 2010

Entrenamiento del perceptron: taller en C++

Llegados a este punto podemos crear un perceptron completo.
Pongo el código de uno que tenia guardado hace tiempo y lo paso a explicar:


#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

#define EPOCA 200  //Numero de iteraciones del entrenamiento
#define K 0.2f     //Taza de aprendizaje del perceptron

float TrainNetwork( float x0, float x1, float target );//Funcion para entrenar el perceptron
float RunNetwork( float x0, float x1 );                //Funcion que entrega las salidas
float sigmoide( float s );                             //Funcion sigmoide
void pesos_init(void);                                 //Inicia los pesos aleatoriamente


float Pesos[2];    // pesos sinapticos
float bias = 1.0f; //El bias lo ajustamos al valor 1

int main()
{
  int i = 0;
  
  pesos_init();
  
  while( i < EPOCA ) // Entrenando la funcion logica AND
  {
    i++;
    
    //Se pasan los patrones para la funcion AND
    cout << "Salida" << endl;
    cout << "1, 1 = " << TrainNetwork( 1, 1, 1 ) << endl;
    cout << "1, 0 = " << TrainNetwork( 1, 0, 0 ) << endl;
    cout << "0, 1 = " << TrainNetwork( 0, 1, 0 ) << endl;
    cout << "0, 0 = " << TrainNetwork( 0, 0, 0 ) << endl;
    
    cout << "\n";
    
    //Se muestran los pesos a cada epoca
    cout << "Peso[0] = " << Pesos[0] << endl;
    cout << "Peso[1] = " << Pesos[1] << endl;
    
    cout << "nbias = " << bias << endl;
    
    cout << "nn" << "epoca = " << i << endl;
    
  }
  
  //Se hace la prueba para ver el estado del entrenamiento
  cout << "Verificar los resultados:" << endl;
  cout << "1, 1 = " << RunNetwork( 1, 1 ) << endl;
  cout << "1, 0 = " << RunNetwork( 1, 0 ) << endl;
  cout << "0, 1 = " << RunNetwork( 0, 1 ) << endl;
  cout << "0, 0 = " << RunNetwork( 0, 0 ) << endl;
  
  cout << "n";
  
  //Se muestran los pesos finales
  cout << "Peso[0] = " << Pesos[0] << endl;
  cout << "Peso[1] = " << Pesos[1] << endl;
  
  //Esto es el bias*W
  cout << "nbias = " << bias << endl;
  
  cout << "nn" << "epoca = " << i << endl;
  return 0;
}

float TrainNetwork( float x0, float x1, float target )
{
  float net = 0;
  float out = 0;
  float delta[2];  //Es la variacion de los pesos sinapticos
  float Error;
  
  net = Pesos[0]*x0 + Pesos[1]*x1-bias;
  net = sigmoide( net );
  
  Error = target - net;
  
  bias -= K*Error;  //Como el bias es siempre 1, pongo que 
                    //el bias incluye ya su peso sinaptico
  
  delta[0] = K*Error * x0;  //la variacion de los pesos sinapticos corresponde 
  delta[1] = K*Error * x1;  //al error cometido, por la entrada correspondiente
  
  Pesos[0] += delta[0];  //Se ajustan los nuevos valores
  Pesos[1] += delta[1];  //de los pesos sinapticos
  
  out=net;
  return out;
}

float RunNetwork( float x0, float x1 )
{
  float net = 0;
  float out = 0;
  
  net = Pesos[0]*x0 + Pesos[1]*x1-bias;
  net=sigmoide( net );
  
  out=net;
  return out;
}


void pesos_init(void)
{
  for( int i = 0; i < 2; i++ )
  {
    Pesos[i] = (float)rand()/RAND_MAX;
  }
}

float sigmoide( float s ){
  return (1/(1+exp(-1*s)));
}

En esta ocasión he dividido cada operación en su respectiva función, de modo que todo quede mas organizado.
Como primera cosa se deben inicializar los pesos sinapticos con valores aleatorios. En este caso no incluyo el peso sinaptico al bias, en cuanto es siempre 1, por lo tanto multiplicado por su peso es siempre igual al peso mismo.
Después la fase de entrenamiento hemos elegido repetirla por 200 veces. El entrenamiento del perceptron suele ser rápido en estos casos, por lo tanto un valor 20 podría estar bien.
Se procede a pasar los parámetros de la función de entrenamiento, este perceptron tiene 2 entradas, por lo tanto los argumento serian la primera entrada, la segunda entrada y la salida respectivamente.
La función hace el calculo del error cometido entre la salida obtenida y la salida deseada ( en la función esta como 'target' ) y en base a las reglas que comentamos en el post anterior, se hace la variación de los pesos sinapticos y del bias ( el cual ya incluye su peso ).
Esta operación debe ser repetida hasta el punto en el que el error sea despreciable, o en todo caso aceptable.
Después se pasa a comprobar el resultado del entrenamiento. Yo obtuve estas salidas:


Verificar los resultados:

1, 1 = 0.854818
1, 0 = 0.0973907
0, 1 = 0.0995641
0, 0 = 0.00202221

Peso[0] = 3.97498
Peso[1] = 3.99946
bias = 6.20154
epoca = 200 

Como se puede observar los valores no son perfectos, aun hay un cierto error, pero podemos decir que es aceptable ya que los valores están bien definidos ( un valor menor de 0.1 seria 0 y un valor mayor de 0.8 seria 1 ).
Esto es debido a que a la salida del perceptron se aplica la función sigmoidal, la cual aunque si esta comprendida entre el 0 y el 1, nunca toca estos valores.
Por lo tanto, nuestro perceptron funciona de manera aceptable.
Podéis cambiar los valores del entrenamiento en el código fuente y verificar que se pueden reproducir en modo muy aproximado muchas funciones lógicas.
Sin embargo el perceptron simple tiene un gran problema, el cual comentaremos en la siguiente entrega.

sábado, 30 de octubre de 2010

Offtopic: repositorio GIT para el codigo fuente

Pensé que seria necesario tener un registro mas ordenado de los códigos que haremos en los distintos talleres en este blog, de modo que usando una cuenta que tenia olvidada en http://github.com, he creado un repositorio donde poner todo el código fuente.
La dirección es:

http://github.com/sauruxum/sauruxum

Hay mucha información acerca el uso de esta herramienta, pero si tenéis dudas especificas, dejar el comentario que responderé lo antes posible.

Saludos

Algoritmo de entrenamiento del perceptron

Como dijimos antes, el perceptron modificando los pesos sinapticos de las entradas es capaz de aprender a dar respuesta según el patrón de entrada que recibe. Para este objetivo existen los algoritmos de entrenamiento.
Básicamente la función de estos algoritmos es el de reducir el error de salida a cada "época" ( una época es un solo ciclo de entrenamiento, ya que en el perceptron el proceso se debe repetir hasta que el error sea mínimo ), de este modo, pasandole ciertas entradas el perceptron da una salida. Sin embargo para el objetivo del entrenamiento esta respuesta puede varía de la respuesta que queremos recibir. De este modo se calcula el error:


Donde:
  • D es la salida deseada
  • S es la salida recibida del perceptron
  • e es el error generado
De este modo la modificacion de los pesos sinapticos sigue la siguiente ley:




De este modo, si la salida obtenida es mas grande que la salida deseada, se tiene que la variación de los pesos de las entradas que dan un valor significativo se hace negativa, de este modo los pesos sinapticos bajan su valor y viceversa. En la formula de la variación de los pesos, k es un coeficiente llamado "taza de aprendizaje". Este coeficiente debe adoptar valores entre 0 y 1, pero se prefieren valores bajos de este coeficiente para evitar oscilaciones.
Este cambio de los pesos debe ser aplicado a todas las entradas del perceptron.

Calculo de las salidas: taller en C++

En este taller nos dedicaremos a mostrar las librerias que utilizaremos en futuro para programar nuestra red neuronal. Ademas de hacer un programa que lea un valor y le aplique la funcion sigmoide, para posteriormente darnos la salida.
Doy por hecho que quien esta interesado en esto sabe compilar un programa escrito en C++, si no es ese el caso dejar un comentario que con gusto respondere segun sea el caso.

Dejo el codigo fuente y paso a explicar una de las funciones que nos interesan:

#include <iostream>
#include <math.h>

float entrada, salida;

using namespace std;

int main(){
cin>>entrada;
salida=1/(1+exp(-1*entrada));
cout<<salida<<endl;
return 0;


Como podreis ver, en el programa se incluye la libreria matematica "math.h" que es una libreria standar en C, y en la linea 10 hacemos una llamada a una funcion de esta libreria, que es la funcion exponencial ( basicamente es el numero de nepper, elevado a la potencia indicada por el argumento de la funcion )


Esta funcion como dijimos antes, da valores de salida que van desde el 0 para numero negativos grandes, y da 1 para numeros positivos grandes. Una curiosidad es que poniendose al centro, pasandole como argumento el 0, obtendremos 0.5 como salida.
Ahora, si queremos obtener un simple perceptron, en el cual le pasamos los datos de ingreso, los pesos sinapticos se inicializan aleatoriamente y da como salida la suma de las entradas por los pesos, aplicandoles la funcion sigmoide, obtendremos un programa de salida como el siguiente:

#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#define NUM_ENTRADAS 2

float entrada[NUM_ENTRADAS], 
      pesos[NUM_ENTRADAS],
      salida;
int i;

using namespace std;

int main(){
  srand48(time(NULL));
  for( i=0; i<NUM_ENTRADAS; i++ ){
  pesos[i]=drand48(); //se aplican valores aleatorios a las entradas
  }
  for( i=0; i<NUM_ENTRADAS; i++ ){
    cout<<"Entrada "<<i+1<<": "; 
    cin>>entrada[i]; //se pide al usuario las entradas
  }
  salida=0.0;      //preventivamente se inicia la salida con 0
  for( i=0; i<NUM_ENTRADAS; i++ ){
    salida+=(entrada[i])*(pesos[i]);//se va a la salida la 
  }                                     //suma de las entradas por los pesos
  salida=1/(1+exp(-1*salida));       //se aplica a la salida la funcion sigmoide
  cout<<"Salida: "<<salida<<endl;               //se imprime la salida
  cout<<"Pesos: "<<pesos[1]<<" "<<pesos[2]<<endl; //muestra los pesos usados
  return 0;
}


En este caso es necesario incluir mas librerias, ya que usamos las funciones para generar numeros aleatorios ( srand48() y drand48() ) que estan en la libreria standar "stdlib.h" y para poder generar numeros aleatorios siempre distintos se usa la libreria "time.h", de modo que muy improbablemente se repetira la misma secuencia de numeros aleatorios ( cosa que sucede con el rand() )

viernes, 29 de octubre de 2010

Funcion de salida del perceptron

Como dijimos antes, el perceptron mostrado en el ejemplo tiene una función de salida lineal. Esto no siempre es lo mejor ya que en este caso puede adoptar valores de todos los rangos, mientras que las neuronas realmente emiten impulsos que no superan los 70 mV.
Por este motivo se elijen dos funciones, con las cuales se pueden obtener un numero finito de estados.
Estas funciones son:

La función sigmoide



La función tangente hiperbólica



Como se ve, en el primer caso los valores adoptan los rangos del 0 al 1 mientras que en el segundo caso los valores de -1 a 1.

Dependiendo del valor obtenido de la sumatoria de las entradas por los pesos sinapticos, se debe aplicar una de estas funciones para obtener una salida mas parecida a la realidad.
Existen mas funciones, pero por el momento nos centraremos en estas dos.

La piedra angular: el perceptron

Digamos que este es el componente mas simple en una ANN ( como antes comentamos, artificial neural network ), casi como los atomos en la materia.
Su funcionamiento trata de emular el comportamiento de la neurona. De este modo tenemos que tiene unas entradas ( similar a las dendritas de otras neuronas ), usa los llamados pesos sinapticos ( como se comento con anterioridad, tienen la funcion de estimular o inhibir el impulso que llega desde las dendritas ), tiene una salida ( como el axon, en las neuronas ) y para decidir si permitir o no una salida de respuesta, se hace uso de una funcion de transferencia, la cual por el momento no me detengo a explicar.

El perceptron puede ser esquematizado asi:

En el que las entradas son x y y ( esta claro que las entradas pueden ser mas ), S es la salida del perceptron y el numero '1' que esta arriba representa un valor al que se denomina bias. El bias determina con que dificultad el perceptron da un impulso de salida.
Como dijimos antes, la neurona es un sumador, lo mismo ocurre en el caso del perceptron:

Llamaremos a las entradas x y y en este caso.
Lamaremos a los pesos sinapticos Wx y Wy respectivamente.

La salida se obtiene haciendo la suma de todas las entradas, por sus respectivos pesos sinapticos y restando el valor del bias ( en este caso 1 )



Esto es en el caso mas simple, en el que la salida es lineal.
Los pesos sinapticos normalmente adoptan valores aleatorios entre 0 y 1 ( pero veremos que en algunas ocaciones seran necesarios valores negativos ).
Siendo este el componente principal de una red neuronal, la cual es capaz de aprender. Es logico pensar que este simple componente puede aprender a dar respuesta segun lo deseemos, y para eso estan los algoritmos de aprendizaje para los distintos tipos de perceptron.

Curiosidades acerca de las neuronas

Cabe destacar algunas características de las neuronas y del sistema nervioso en general:

Los procesos mentales no están precisamente localizados. Esto quiere decir que no existe una neurona la cual, como dicen los estadounidenses en son de broma "la neurona de la abuela", o sea una neurona especifica a reconocer a la abuela. La facultad de reconocer el rostro o la voz de la abuela están distribuidos entre muchas neuronas y en algunas zonas del cerebro.

Las neuronas son las unicas celulas que no se regeneran. Aunque mueren cada día y en grandes cantidades, en la practica diaria no perdemos nuestras facultades. Esto es debido a que aumentan las conexiones sinapticas para compensar la perdida de las neuronas.
Esto da al cerebro una gran robusteza a sufrir daños.

Las neuronas son "lentas". Respecto a los microprocesadores las neuronas son como un caracol respecto un cohete. La velocidad de reacción de las neuronas no supera los 100Hz, comparado con los 3GHz de los actuales procesadores. Sin embargo haciendo un calculo veloz:
  • Neuronas en el cerebro: 100 mil millones
  • Numero de sinapsis por neurona: 10000
  • Numero de sinapsis totales: 1000 billones.
Si decimos que cada sinapsis son 4 bits tenemos que la memoria del cerebro es de 4x10^15 bytes, con una velocidad de calculo de 10^17 conexiones por segundo ( recordar que las neuronas trabajan todas al mismo tiempo, mientras que en procesador hace una instrucción a la vez ).
Sin embargo lo que es difícil para el cerebro es fácil para el ordenador y viceversa.

Las neuronas

Las redes de neuronas artificiales (denominadas habitualmente en inglés como ANN ) son un paradigma de aprendizaje y procesamiento automático inspirado en la forma en que funciona el sistema nervioso de los animales. Se trata de un sistema de interconexión de neuronas en una red que colabora para producir un estímulo de salida. En inteligencia artificial es frecuente referirse a ellas como redes de neuronas o redes neuronales.

Para saber el preciso funcionamiento de estas redes se debe como primera cosa dar una explicacion del funcionamiento de las neuronas:

Digamos que del cerebro conocemos bastante poco, pero de lo que podemos estar seguros es del funcionamiento de cada ladrillo que forma el sistema nervioso, esos ladrillos son llamados neuronas. En el cerebro humano hay contenidas alrededor de 100 mil millones de neuronas, las cuales se conectan unas con otras a través de las sinapsis.
Antes de pasar a describir las partes que componen una neurona quiero dejar claro las clases de neuronas presentes en el sistema nervioso:
  • Neuronas sensores: que constituyen el "input" de la red, entre las que encontramos las bastoncillos de la retina, los sensores olfativos, táctiles, etc. Estas neuronas son casi 1 millón.
  • Neuronas motrices: son las encargadas de dar la salida a la red, generalmente para dar el impulso motorio a los músculos. Son otro millón mas
  • Inter-neuronas: son neuronas en un punto medio, están conectadas entre las de entrada, salida y entre ellas mismas.
La constitución de una neurona es relativamente simple:
  • Un cuerpo central llamado soma, que como todas las células, esta dotada del patrimonio genetico de cada individuo.
  • Del soma se extienden unas fibras nerviosas llamada axón, estas pueden ser largas desde 1mm hasta llegar al metro de longitud.
  • Del soma mismo se derivan las dendritas que son las distintas ramificaciones que se pueden ver en cualquier ilustración de la neurona.
  • Finalmente están las sinapsis, que son como botones ubicados al final de las dendritas y del axón y que hacen de puente de comunicacion entre otras neuronas.


Sin estar a explicar como se efectua el paso del impulso electrico entre neuronas, solo decir que las sinapsis pueden tener una acción estimulatoria del impulso ( lo amplifican ) o inhibitorio ( lo atenúan o lo rechazan )

A la neurona llegan muchos señales, provenientes de las sinapsis desde otras neuronas y esta emite una solo señal de salida a través del axón, que a su vez deja el impulso eléctrico en otras neuronas a las que se conecta el axón.

Básicamente la neurona es un sumador, a cada intervalo de tiempo recibe las señales de las sinapsis de otras neuronas y suma todas las entradas ( tener en cuenta que la señal puede ser atenuada por la sinapsis ) y si el valor total supera un cierto limite, la neurona envía un impulso a través del axón, o en caso de no superarlo no da ninguna salida. En un cierto sentido puede parecer a un circuito binario.
El limite de superar para que la neurona de una señal de salida puede cambiar en el tiempo, depende principalmente de la concentración de algunas sustancias química ( por ejemplo, el café puede bajar este limite, un tranquilizante la alza )

Una cosa que hace de la neurona un elemento único es el hecho que puede cambiar en el tiempo la cantidad de las sinapsis con otras neuronas y su carácter estimulatorio/inhibitorio. De esto deriva que el cerebro es "especialmente apto para aprender". Estas conexiones cambian constantemente en el curso de la vida del humano y no dependen del código genético.

Bienvenidos a mi blog

Saludos amables lectores.

En principio este blog tiene la finalidad de poner a dispocision material que personalmente he traducido, acerca de las redes de neurona artificiales ( conocidas con su nombre en ingles artificial neural network o ANN de ahora en adelante ).
Tratare de dar un enfoque no tan matemático como el que podrías encontrar en un libro, ya que este no es un argumento sencillo ni siquiera para los que estudian especialisticamente estos temas ( yo soy ingeniero mecánico, por ende nunca lo hice en la universidad, lo hago por motivacion personal ).
No pretendo ser tampoco el sustituto de un buen libro en el que podrías encontrar mucha mas información, pero a cambio de eso quiero que este tema llegue a mucha mas gente, al menos del lado teorico de las cosas.

Con este propósito y para poder dar ejemplos claros del funcionamiento de las ANN, usare dos herramientas: Matlab ( este no es específicamente un software gratuito, pero seguro que encuentras buenos demos por ahí ) y el uso del lenguaje de programacion C++ ( en el cual no soy tan experto, pero me defiendo bien ).

Sin mas que decir por el momento, espero que encontréis este blog interesante, ya que ese es mi propósito secundario.

Saludos