Apéndices

Apéndice A. Diseño e implementación de una red de retropropagación

Apéndice B. Ejemplo de aplicación de una red de retropropagación

Apéndice C. Programa en C para implementar el algoritmo de retropropagación

Apéndice A. Diseño e implementación de una red de retropropagación

A continuación presento un sencillo guión de los pasos a seguir para realizar una aplicación mediante el desarrollo de una RNA.

Análisis del problema

Entrenamiento de la red

Test

Apendice B. Ejemplo de aplicación de una red de retropropagación

Supongamos que disponemos de una tableta digitalizadora que nos convierte lo que escribimos sobre la tarjeta en un mapa de bits. Podríamos diseñar un sistema capaz de reconocer dígitos manuscritos de la siguiente manera. Escribiríamos dígitos sobre la tarjeta y guardaríamos los mapas de bits generados por la misma. Asignaríamos a cada dígito un código. Entrenaríamos a la red para que asociara los mapas de bits correspondientes a cada dígito con el código correspondiente. Una vez entrenada podríamos comprobar si la red es capaz de generalizar y reconocer los mismos dígitos escritos por la misma persona o incluso por otra persona, mediante generalización de lo aprendido.

Para simplificar supongamos que cada dígito se representa mediante una cuadrícula de 3*5. Abajo podemos ver un ejemplo que representa los dígitos 0, 1, 2 y 3.

Al lado de cada dígito hemos puesto la representación mediante bits. Cómo no usamos colores, sólo necesitamos un bit para representar cada celda de la cuadrícula, el 0 indica blanco y el 1 indica negro. Lo normal para un sistema de reconocimiento es promediar el color de la celda, así pues, si predomina el blanco se le asigna un 0, si predomina negro se le asigna un 1.

Cada dígito se representará pues mediante un patrón de 15 bíts. Si empezamos de arriba a abajo y de izquierda a derecha, el 0 sería: 110101101101111.

Una de las formas, aunque no la única, para introduir datos en la red es usar una entrada por bit, en ese caso nuestra red necesitará 15 unidades de entrada, una por cada celda de la cuadrícula.

Para codificar la salida tenemos diferentes alternativas, una es usar un bit para cada valor de salida que queremos que aprenda la red. En este caso hay cuatro dígitos, así pues usaríamos 4 unidades de salida. Con esta codificación, la unidad de salida que alcance un valor de activación mayor nos estará indicando de que dígito se trata. Otra posibilidad es codificar el resultado usando el mínimo número de bits posibles. Como en este caso sólo queremos aprender 4 dígitos, sólo necesitaremos 2 bits para codificar la salida, o sea 2 unidades de salida. La combinación de las 2 unidades de salida nos indicará cual es el dígito. Por ejemplo, podemos usar el 00 para representar el 0, el 01 para representar el 1, el 10 para el 2, y el 11 para el 3.

En el ejemplo propuesto, como es un problema sencillo, con muy pocos ejemplos de aprendizaje, no es necesario usar muchas unidades ocultas, probamos por ejemplo con 5 unidades ocultas, quedando una red 15:5:2 (15 unidades de entrada, 5 ocultas, y 2 de salida), y un total de 15*5*2=150 conexiones. Podemos fijar como error cuadrático máximo, por ejemplo 0,01. El aprendizaje finalizará cuando el error cuadrático sea menor de ese valor criterio para el conjunto de patrones de entrenamiento.

Creamos un fichero con los ejemplos de aprendizaje, incluyendo los patrones de entrada y los patrones de salida deseados (objetivos). Para usar el código que se incluye en el apéndice C, hemos decidido representar cada patrón de entrenamiento en una fila de un archivo de texto. Cada fila tiene que incluir los valores de la entrada y los de la salida deseada, que en nuestro ejemplo totalizan 17 bits, 15 bits para codificar el patrón de entrada, y 2 bits para la salida deseada. Nos quedará un fichero con la apariencia siguiente:

111101101101111 00
001011001001001 01
111101010100111 10
110001110001110 11

Fijamos los parámetros de la regla de aprendizaje, pe:

Entrenamos la red, en este caso durante aproximadamente 10.000 ciclos de aprendizaje (en cada ciclo se presentan todos los ejemplos de aprendizaje), y obtenemos, por ejemplo (recordemos que el resultado depende de los valores inciales de las conexiones, que son aleatorios.), el siguiente error:

RMS Error: 0.008706

Entrada

Salida obtenida

Salida deseada

Error

0

(0.006425, 0.007660)

(0, 0)

0.009998 +

1

(0.005664, 0.993746)

(0, 1)

0.008438 +

2

(0.994077, 0.005495)

(1, 0)

0.008079 +

3

(0.995146, 0.993430)

(1, 1)

0.008168 +

Preparamos un fichero de test simulando ruido en los datos (errores o imprecisiones), para lo cual podemos coger el fichero de entrenamiento original y modificar algunos bits al azar. En el código de ejemplo los patrones de test se ponen en un archivo de texto separado de los de entrenamiento. Pasamos los datos de test y obtenemos el siguiente resultado:

RMS Error: 0.102622

Entrada

Salida obtenida

Salida deseada

Error

0

(0.010521, 0.108501)

(0, 0)

0.109010 -

1

(0.004973, 0.992143)

(0, 1)

0.009298 +

2

(0.998309,
0.02478)

(1, 0)

0.024846 -

3

(0.986624, 0.732999)

(1,1)

0.267336 -

Evaluación de los resultados:

Lo más importante es saber si la red ha sido capaz de generalizar los valores aprendidos y es capaz de acertar correctamente con unos valores diferentes, para lo cual hemos modificado los patrones de entrenamiento originales. Cómo usamos una codificación binaria para la salida, tenemos que redondear los valores de salida, que son reales, a 0 o 1, y veremos que el resultado ha sido correcto para los cuatro dígitos

Apéndice C: Programa en C para implementar el algoritmo de retropropagación

/******************************************************************************/

/* ALGORITMO DE RETROPROPAGACION */

/* REGLA DELTA GENERALIZADA EXTENDIDA */

/* Mario Gomez Martinez */

/* magomar@navegalia.es */

/* mario@iiia.csic.es

/******************************************************************************/

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <math.h>

#include <time.h>

#include <conio.h>

#define n_in 15 /* num. de unidades de entrada */

#define n_hide 5 /* num. de unidades ocultas */

#define n_out 2 /* num. de unidades de salida */

#define n_casos 4 /* num. de ejemplos de aprendizaje */

#define REAL double

/* funcion de activacion */

REAL Sigm (REAL x)

{

return 1/(1+exp(-x));

};

void main()

{

/* definicion de variables y constantes */

REAL in[n_in],hide[n_hide],out[n_out]; /* unidades de de proceso */

REAL whide [n_in+1][n_hide],wout[n_hide+1][n_out]; /* pesos de las conexiones */

/* se definen conexiones extras para añadir el bias */

REAL oldincwhide[n_in+1][n_hide+1];

REAL oldincwout[n_hide+1][n_out+1];

REAL error; /* discrepancia entre la salida deseada y la obtenida */

REAL delta[n_out],deltah; /* derivada del error respecto al peso */

REAL incw; /* incremento del peso */

REAL rmsparcial[n_casos]; /* error cuadratico de cada ejemplo */

REAL rmsmax=.05; /* criterio de error cuadratico minimo */

REAL rms; /* error cuadratico global, indica la evolucion de la red */

REAL oldrms=0, incrms;

REAL aux;

FILE *datos;

FILE *salida;

const REAL LCout=.3; /* coeficiente de aprendizaje de la capa de salida*/

const REAL LChide=.4; /* coedifiente de aprendizaje de la capa oculta */

const REAL momentum=.5; /* coeficiente de la ultima modificacion del peso */

const REAL bias=(REAL) 1; /* bias es un valor constante que se aplica como entrada auxiliar, mediante su correspondiente conexion ponderada, en las unidades ocultas y de salida, y puede acelerar el aprendizaje */

 

int patrones[n_casos][n_in]; /* patrones de entrada de los ejemplos de aprendizaje */

int targets[n_casos][n_out]; /* patrones de salida deseados (objetivos) */

int goodpats=0; /* numero de ejemplos que cumplen el criterio de error */

long ciclo; /* ciclo de aprendizaje */

int caso; /* ejemplo de aprendizaje */

int i,j,k; /* contadores*/

time_t seed; /* semilla para el generador de numeros aleatorios */

char fin=0;

 

/* comienzo del program principal */

clrscr();

printf(" *** Pulse <p> para detener el aprendizaje ***");

delete[] oldincwhide;

delete[] oldincwout;

/* Aleatorizamos los pesos de las conexiones*/

srand((unsigned) time(&seed));

for(i=0;i<n_in+1;i++)

for(j=0;j<n_hide+1;j++)

{

aux=(REAL) rand()/ RAND_MAX;

if (random(2)==0) whide[i][j]=aux;

else whide[i][j]=aux*(-1);

for(k=0;k<n_out;k++)

{

aux=(REAL) rand()/ RAND_MAX;

if (random(2)==0) wout[i][j]=aux;

else wout[i][j]=aux*(-1);

}

};

/* Abrimos el fichero de ejemplos de aprendizaje y un fichero de resultados */

datos=fopen("digit.pat","r");

salida=fopen("backprop.log","w");

/* Leemos los datos de entrada y las salidas deseadas(targets) del fichero de ejemplos de entrenamiento y los almacenamos en vectores*/

for (caso=0;caso<n_casos;caso++)

{

for(i=0;i<n_in;i++) fscanf(datos,"%d",&patrones[caso][i]);

for(k=0;k<n_out;k++) fscanf(datos,"%d",&targets[caso][k]);

}

/* EMPIEZA EL ENTRENAMIENTO */

/* en cada ciclo se presentan todos los ejemplos de aprendizaje y se ajustan los pesos de las conexiones para reducir el error */

/* el aprendizaje termina cuando todos los ejemplos tiene un error menor al criterio (goodpats==n_casos) */

for (ciclo=1;goodpats<n_casos && !fin;ciclo++)

{

/* COMIENZO DE UN CICLO DE APRENDIZAJE !!! */

/* inicializamos el error cuadratico global y el numero de ejemplos correctamente aprendidos */

rms=0;

goodpats=0;

for (caso=0;caso<n_casos;caso++)

{

/* Para cada ejemplo de entrenamiento... */

/* inicializamos el error cuadratico parcial*/

rmsparcial[caso]=0;

/* Cargamos las unidades de entrada con los patrones del ejemplo de entrenamiento */

for(i=0;i<n_in;i++) in[i]=(REAL) patrones[caso][i];

 

/* Realizamos un paso feed-forward, es decir, propagamos por toda la red hasta calcular la salida */

for(j=0;j<n_hide;j++)

{

hide[j]=0;

for(i=0;i<n_in;i++) hide[j]+=in[i]*whide[i][j];

hide[j]+=bias*whide[n_in][j];

hide[j]=Sigm(hide[k]);

};

for(k=0;k<n_out;k++)

{

out[k]=0;

for(j=0;j<n_hide;j++) out[k]+=hide[j]*wout[j][k];

out[k]+=bias*wout[n_hide][k];

out[k]=Sigm(out[k]);

};

/* Calculamos el error cometido por cada unidad de salida y ajustamos los pesos de las conexiones de la capa de salida, de paso calculamos el error cuadratico parcial */

for(k=0;k<n_out;k++)

{

error=((REAL) targets[caso][k]-out[k]);

delta[k]=error*out[k]*(1-out[k]);

for (j=0;j<n_hide;j++)

{

incw=LCout*delta[k]*hide[j]+momentum*oldincwout[j][k];

wout[j][k]+=incw;

oldincwout[j][k]=incw;

}

incw=LCout*delta[k]*bias+momentum*oldincwout[j][k];;

wout[n_hide][k]+=incw;

rmsparcial[caso]+=error*error;

oldincwout[n_hide][k]=incw;

};

/* comparamos el error cuadratico parcial con el criterio y lo sumamos al error cuadratico global */

rmsparcial[caso]=rmsparcial[caso]/2;

if (rmsparcial[caso]<= rmsmax) goodpats++;

rms+=rmsparcial[caso];

/* Propagamos el error hacia la capa oculta y ajustamos los pesos de las conexiones de la capa oculta */

for(j=0;j<n_out;j++)

{

error=0;

for(k=0;k<n_out;k++) error+=delta[k]*wout[j][k];

deltah=error*hide[j]*(1-hide[j]);

for (i=0;i<n_hide;i++)

{

incw=LChide*deltah*in[i]+momentum*oldincwhide[i][j];

whide[i][j]+=incw;

oldincwhide[i][j]=incw;

}

incw=LChide*deltah*bias+momentum*oldincwhide[n_in][j];

whide[n_in][j]+=incw;

oldincwhide[n_in][j]=incw;

}

/* Fin de los calculos para un ejemplo de entrenamiento */

}

 

/* FIN DE UN CICLO COMPLETO DE APRENDIZAJE !!! */

rms+=rms/n_casos; /* error global=como promedio de los errores parciales */

incrms=rms-oldrms;

oldrms=rms;

gotoxy(0,3);

printf("Ciclo: %ld\n",ciclo);

if (ciclo%50==0)

{printf("RMS = %12.7f\n",rms);

printf("Incremento de RMS = %12.7f\n",incrms);

printf("Good pats = %d\n",goodpats);

for(caso=0;caso<n_casos;caso++)

printf("RMS(%d)=%12.7f\n",caso,rmsparcial[caso]);

if (kbhit())

if (getch()=='p')

{

gotoxy(0,7+n_casos);

printf("*** PAUSE ***\n");

printf("Para finalizar pulsa <f>\n");

printf("Para continuar pulsa cualquier otra tecla");

fin=getch()=='f';

gotoxy(0,7+n_casos);

printf(" \n");

printf(" \n");

printf(" \n");

}

}

}

 

/* FIN DEL ENTRENAMIENTO */

gotoxy(0,7+n_casos);

printf("*** Resultados ***\n");

for (caso=0;caso<n_casos;caso++)

{

printf("Ejemplo(%d) ",caso);

for(i=0;i<n_in;i++) in[i]=(REAL) patrones[caso][i];

for(j=0;j<n_hide;j++)

{

hide[j]=0;

for(i=0;i<n_in;i++) hide[j]+=in[i]*whide[i][j];

hide[j]=Sigm(hide[k]);

};

for(k=0;k<n_out;k++)

{

out[k]=0;

for(j=0;j<n_hide;j++) out[k]+=hide[j]*wout[j][k];

out[k]=Sigm(out[k]);

};

printf(" Target: ");

for(k=0;k<n_out;k++) printf("%2d ",targets[caso][k]);

printf(" Output: ");

for(k=0;k<n_out;k++) printf("%6.3lf ",out[k]);

printf("\n");

}

fclose(datos);

fclose(salida);

}