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.

2 comentarios:

  1. ayudame a hacer este programa pero un poco mas facil por fa!!

    ResponderEliminar
  2. Mira que cosas! tengo años que no veia este blog y no me llegan los comentarios a mi correo electronico. Y por casualidad hoy veo tu comentario.

    Este blog no lo actualizo mas, ya que tiene una simple demostracion de una red multilayer perceptron. Y esta implementacion mia en realidad no es muy flexible.

    Si tienes dudas mandame un correo electronico. Y si quieres algo simplificado mira en mi nuevo blog tengo algo con python http://leonardodaniel.me/modular-backpropagation/ y esta es una demostracion de que la red es muchisimo mas flexible que esta y que funciona https://github.com/Sauruxum/ipython-notebooks/blob/master/modular_backpropagation.ipynb

    ResponderEliminar