Trabajo - DavidHorat.com

Anuncio
Índice:
1. Problema a resolver................................................................................... 2
2. Estudio teórico del perceptrón monocapa................................................. 3
Definición y funcionamiento........................................................... 3
¿Por qué la función de activación no debe ser lineal?..................... 4
Posibilidades y limitaciones ............................................................ 4
Entrenamiento de una red monocapa .............................................. 5
Conclusión ....................................................................................... 5
3. Resolución ................................................................................................. 6
Estudio teórico del diseño ............................................................... 6
Estudio teórico del desarrollo.......................................................... 7
Implementación en Borland C++ Builder 6.0 ................................. 8
4. Conclusiones ........................................................................................... 10
5. Bibliografía.............................................................................................. 11
Apéndice A: Listado de código................................................................... 12
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
1. Problema a resolver
En esta práctica deberemos diseñar un perceptrón monocapa para discriminar
líneas verticales de líneas horizontales en un grid de 3x3 elementos. Para ello se nos dan
una serie dos datos: por un lado los patrones que actuarán de estímulo (figura 1) y, por
otro, las conexiones entre unidades de retina y de asociación (figura 2).
Horizontales:
Verticales:
Figura 1
Figura 2
Los principales objetivos de esta práctica son el estudio del perceptrón
monocapa. Tanto su funcionamiento teórico, topología, neurodínamica, aprendizaje,
hasta su aplicabilidad, pero desde el punto de vista de resolver una tarea. Otro aspecto
importante son sus limitaciones. Una vez resuelta la práctica, analizaremos el
perceptrón y su funcionamiento según la modificación de sus parámetros.
2
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
2. Estudio teórico del perceptrón monocapa
Definición y funcionamiento
La neurona artificial es el elemento básico de procesamiento de la red neuronal
artificial, muchas veces llamada perceptrón simple. Es un sistema con varias entradas
(como las dendritas) y una salida (como el axón), la cual depende del estado interno de
la neurona.
La conexión (o dependencia) entre la salida y las entradas se puede modificar y
ello se consigue mediante unos factores (pesos) que se aplican a cada una de las
entradas. El resultado de tal aplicación es un solo valor a partir de varios (varias
entradas sopesadas, es decir, multiplicadas cada una por un peso) lo cual se consigue
mediante una suma y una función no lineal.
y = fac(S xi*wi - Umb)
La idea es que la neurona solo se activa (valor alto a la salida) si la suma de las
entradas ponderada mediante unos factores llamados pesos o, lo que es lo mismo, el
producto escalar del vector de pesos por el vector de entradas supera un umbral, es
decir, si el producto escalar de los vectores pesos y entradas es suficientemente alto.
No ocurrirá esto cuando entradas y pesos sean ortogonales (producto escalar nulo),
opuestos (producto escalar < 0) o de módulo pequeño.
Su funcionamiento, por tanto, consiste en tomar las entradas, multiplicarlas cada
una por un factor llamado peso (cada entrada tiene el suyo), sumar todos estos
productos, restar un umbral y aplicar al resultado una función no lineal.
- Entradas. (xi)
- Función de activación. (fac)
- Pesos. (wi)
- Salida. (y)
- Umbral. (Umb)
3
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
¿Por qué la función de activación no debe ser lineal?
Si no aplicásemos la función de activación o ésta fuese una función lineal
habríamos conseguido muy poco ya que la neurona sería equivalente a una aplicación
lineal. Para neuronas aisladas es difícil explicar porqué se aplica esta función (excepto
para casos concretos donde usemos neuronas aisladas solo para ver cuándo con unos
valores concretos supera un cierto umbral). Más adelante veremos que importancia tiene
esta función y porqué debe ser no lineal cuando tenemos redes de neuronas con varias
capas.
Una de las ventajas de la función de activación es que permite concentrar los
valores de salida en un conjunto acotado (típicamente de 0 a 1 o de -1 a 1). La elección
de la función de activación junto con la forma de ponderación (variantes del producto
escalar) van a determinar las características de la neurona artificial.
Posibilidades y limitaciones
Veamos que posibilidades tiene una neurona de dos entradas si suponemos que
la función de activación es de tipo escalón (u = función escalón unidad):
s = u(x1*w1+x2*w2-Umb) =
=1
si
x1*w1 + x2*w2 >= Umb.
=0
si
x1*w1 + x2*w2 < Umb.
¿ Qué puntos del plano devuelven un uno (s = 1) ?
x1*w1+ x2*w2 >= Umb. <=>
( x2 >=Umb/w2 -(w1/w2)*x1 ) y ( w2 > 0 )
( x2 <= Umb/w2 -(w1/w2)*x1 ) y ( w2 < 0 )
La recta de separación se puede definir de forma implícita con la ecuación:
x1*w1 + x2*w2 - Umb = 0
Cuando tengamos más de dos dimensiones (N) la ecuación de la superficie de
separación será : x*w - Umb = 0 (siendo x el vector N-dimensional de entradas y w el
vector N-dimensional de pesos).
4
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
Entrenamiento de una red monocapa
Dado que solo hemos aplicado una no-linealidad a la salida podemos deshacerla
(calculamos la inversa para todas las salidas-deseadas) y por tanto el problema se puede
reducir a un sistema de ecuaciones lineales que en general no será compatible
determinado. Si lo fuera sería muy sencillo: los pesos que debemos poner son la
solución única del sistema. Si el sistema es indeterminado también es bastante sencillo:
tomaremos una de las infinitas soluciones (Podemos también definir un criterio para
determinar el/los grado/s de libertad porque puede ser que algunas sean mejores
soluciones que otras: sensibilidad a variaciones en las entradas...). Pero si el sistema es
incompatible deberemos tomar la solución que mejor se ajuste según algún criterio.
Para sistemas compatibles resolvemos el sistema y para los incompatibles
buscamos la solución por el método de los mínimos cuadrados o por cualquier otro. Por
tanto no necesitamos un procedimiento iterativo. Basta con métodos directos aunque
quizá prefiramos el iterativo.
Conclusión
Una neurona solo puede hacer una separación lineal en el espacio de entradas.
Cuando este espacio es de dos dimensiones esta separación lineal consiste en una recta
que separa dos semiplanos, cuando es de tres dimensiones consistirá en un plano que
separa dos semiespacios y en general cuando es de N dimensiones consistirá en un
hiperplano (espacio de N-1 dimensiones) que divide el espacio de N dimensiones en
dos.
Si no elegimos bien la estructura puede que estemos intentando entrenar a una
red que nunca puede producir los resultados que queremos.
5
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
3. Resolución
Estudio teórico del diseño
Lo primero que debemos hacer es estudiar el problema. Dado que en los datos de
entrada nos dan las conexiones entre unidades de retina y de asociación, y que el
objetivo es discriminar líneas verticales de horizontales, es de suponer que la
composición de nuestra red vendrá dada por una capa de donde vengan los estímulos (la
lattice), una capa en donde se hacen las conexiones entre unidades, cuyas salidas van a
un adaline que finalmente nos da una salida binaria discriminando entre líneas verticales
u horizontales. Este diseño se puede observar en la figura 3 que viene a continuación:
ADALINE
0/1
Lattice
de entrada
Asociación
ALC
Conmutador
Bipolar
Salida
binaria
Figura 3
Las conexiones entre la lattice de entrada y la capa de asociación vienen dadas
por la figura 2 estudiada en el punto 1. Evidentemente las neuronas de la capa de
asociación tendrán un umbral 2, ya que, aunque el número de conexiones activas será
siempre de 3, deseamos que la red sea flexible a la hora de reconocer otros patrones. A
la luz del diseño realizado, el objetivo de nuestra red será ajustar los pesos que van de la
capa de asociación a nuestro ALC (Adaptive Linear Combiner) ya estudiado en la
práctica anterior con un desarrollo extenso.
6
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
Estudio teórico del desarrollo
A continuación (figura 4) mostraremos como se encuentran las neuronas de la
capa de asociación para cada uno de los patrones de entrada basándonos en las
conexiones entre unidades de retina y de asociación expuestos en la figura 2.
Figura 4
7
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
Implementación en Borland C++ Builder 6.0
En esta práctica, hemos decidido implementar nuestra solución en Borland C++
Builder 6.0 para ir probando la eficacia de cada lenguaje de programación en el diseño e
implementación de redes neuronales. En nuestra pasada práctica demostramos como
Mathematica resuelve eficazmente nuestras redes, aunque para una mayor interactividad
y facilidad de manejo es mejor un lenguaje con capacidad para crear interfaces gráficas,
como puede ser Visual Basic, que si bien pierde rendimiento, gana en facilidad de uso.
Por ello, hemos decidido combinar ambas cosas (eficacia y facilidad de uso) usando esta
herramienta de programación que nos permite aprovechar la eficiencia de C++ con el
uso de widgets gráficos para windows.
Después de una exhaustiva programación, el interfaz que permite interactuar al
usuario con la nuestra implementación se muestra en la figura 5 a continuación:
Figura 5
8
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
Cómo se puede comprobar en la figura su manejo resulta sencillo conociendo las
bases de la computación neuronal. Sin embargo, si miramos el código fuente, nos
damos cuenta de la calidad del diseño de la interfaz que permite ocultar gran cantidad
de código pero permitiendo la funcionalidad requerida para la práctica. Parte del código
se adjunta en el Apéndice A, ya que debido a la gran cantidad de código nos ha sido
imposible integrarlo completamente en la memoria; sin embargo está disponible bajo
petición previa.
Para probar el programa, simplemente deberemos seguir estos pasos:
-
Cambiar pesos
-
Inicializar el Alpha y el umbral de asociación a los valores deseados
-
Entrenar
-
Introducir datos en la lattice
-
Usar
Aquí ya les dejamos para que prueben las combinaciones que deseen, en la
siguiente parte expondremos las conclusiones al hacer las pruebas con el programa.
9
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
4. Conclusiones
Durante la realización de la práctica hemos aprendido sus ventajas y sus
limitaciones. Haciendo un estudio del diseño del perceptrón vemos que, por propia
definición, su salida es bipolar, por lo que empezamos a restringir su uso. Además,
incluye un ALC y arrastra muchas de sus desventajas, como por ejemplo el hecho de ser
supervisada por patrones, lo que impide que su uso sea generalizado, teniendo que
adaptarlo a cada problema específicamente.
Finalmente, nuestro perceptrón ha conseguido diferenciar entre líneas verticales
y horizontales, que era nuestro objetivo. El algoritmo del perceptrón entra dentro de las
funciones discriminantes en el ámbito del reconocimiento de forma. Su uso permitió
iniciar la percepción de formas a base de discriminar que es y que no es (salida bipolar)
y así avanzar en el campo de la percepción humana.
10
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
5. Bibliografía
Libros y prácticas:
Referencia
[HechtTÍTULO Neurocomputing
Nielsen/90]
AUTORES
Hecht-Nielsen, R
EDITORIAL
Addison-Wesley Company
AÑO
1990
Referencia
[Cañizales- TÍTULO Implementación de funciones booleanas
Horat]
usando un ALC
AUTORES
J.L. Cañizales, D.J. Horat
EDITORIAL
AÑO
2003
Manuales:
Librería de referencia de VCL de Borland C++ Builder 6.0
Páginas web:
http://www.philbrierley.com/ -> Información y código sobre redes neuronales
http://thales.cica.es/rd/Recursos/rd98/TecInfo/07/capitulo4.html -> Redes neuronales
con conexiones hacia delante
11
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
Apéndice A: Listado de código
1: // Manejo de ficheros:
2: #include <fstream>
3: using namespace std;
4:
5:
6: // Más cómodo:
7: typedef unsigned int nat;
8: typedef float* vector; // const vector == float []
9:
10:
11:
12: //---------------------------------------------------------------13: //--- A P A R A T O M A T E M Á T I C O : --14: //---------------------------------------------------------------15:
16: // Función signo (de umbral)
17: inline float sgn(const float x)
18: {
19: return x >= 0 ? 1 : -1;
20: }
21:
22: // Función producto escalar
23: float dotProd(const float u[], const float v[], nat n)
24: {
25: float sum = 0;
26: for(nat i=0; i<n; i++) sum += u[i]*v[i];
27: return sum;
28: }
29:
30: // Función diferencia (error)
31: // El resultado ha de ser destruido con delete.
32: vector dif(const float u[], const float v[], nat n)
33: {
34: float* d = new float[n];
35: for(nat i=0; i<n; i++) d[i] = u[i]-v[i];
36: return d;
37: }
38:
39: // Función error cuadrático medio
40: float mse(const float u[], const float v[], nat n)
41: {
42: vector err = dif(u, v, n);
43: float m = dotProd(err, err, n) / n;
44: delete[] err;
45: return m;
46: }
47:
48:
49:
50: //---------------------------------------------------------------51: //--- E S T R U C T U R A D E L A R E D : --52: //---------------------------------------------------------------53:
54: // nº de EPs por capa
55: const nat nEPs[] = {9, 5, 1};
56:
57:
58: // Salida de los EPs:
59: // -----------------60: vector salida[] = {NULL, /*[nEPs[0] + 1]*/ // entrada: no hay que
tomar espacio
61: new float[nEPs[1] + 1], // capa oculta + bias para la salida
62: new float[nEPs[2]]}; // capa de salida
12
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
63:
64: /*
65: * El elemento añadido a las capas anteriores a la salida
corresponde al
66: * bias, que siempre estará a 1 y permite que el umbral de las
neuronas
67: * sea dinámico.
68: */
69:
70:
71: // forma más cómoda e intuitiva: entrada[i]==salida[0][i]
72: vector &entrada = salida[0];
73:
74:
75:
76: // Matrices de pesos. Una entre capa y capa:
77: // ----------------------------------------78: vector* pesos[] = {new vector[nEPs[1]]/*[nEPs[0]+1]*/, // pesos
input ->hidden
79: new vector[nEPs[2]]/*[nEPs[1]+1]*/}; // pesos hidden->output
80:
81: /*
82: * pesos[0][2][5] es el peso entre la (5+1)ª entrada (2ª fil-3ª
col) y
83: * la (2+1)ª neurona de la capa de asociación (capa 0+1).
84: *
85: * pesos[k][i][nEPs[k]] es el peso del término bias para la neurona
86: * (i+1) de la capa (k+1), osea, el opuesto de su umbral. por eso
el
87: * segundo índice tiene nEPs[k]+1 elementos.
88: */
89:
90:
91:
92: // Patrones de entrada y su Salida asociada + Salida obtenida:
93: // ----------------------------------------------------------94: vector patron[] = {new float[nEPs[0]+1],
95: new float[nEPs[0]+1],
96: new float[nEPs[0]+1],
97: new float[nEPs[0]+1],
98: new float[nEPs[0]+1],
99: new float[nEPs[0]+1]};
100:
101: vector yEsp = new float[6];
102: vector yObt = new float[6];
103:
104:
105:
106: //--------------------------------------------------------------107: //--- D I N Á M I C A D E L A R E D : --108: //--------------------------------------------------------------109:
110: // Función de salida compuesta con activación:
111: // uso: salida[k][j] = f(pesos[k-1][j], salida[k-1], nEPs[k1]+1);
112: // (k = 1, 2; j = 0...nEPs[k]-1)
113: float f(vector w, vector x, nat n)
114: {
115: return sgn(dotProd(w, x, n));
116: }
117:
118: // Función de actualización de pesos. Regla de Widrow-Hoff:
119: // uso (k=2;j=0): widrow_hoff(pesos[1][0], salida[1], nEPs[1]+1,
120: // yEsp[p] - yObt[p], float(Edit1->Text.ToDouble()));
121: void widrow_hoff(vector w, vector x, nat n, float e, float a)
122: {
123: for(nat i=0; i<n; i++) w[i] += a*e*x[i];
124: }
125:
13
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
126:
127:
128: //--------------------------------------------------------------129:
130: //--------------------------------------------------------------131:
132: __fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
133: {
134: // Pone la salida de los términos bias a 1. No se cambiarán más
135: for(nat k=1; k<2; k++) salida[k][nEPs[k]] = 1;
136:
137: // Crea los vectores de pesos
138: for(nat k=0; k<2; k++) // k: nivel de pesos
139: for(nat j=0; j<nEPs[k+1]; j++) // j: neurona sináptica
140: pesos[k][j] = new float[nEPs[k]+1]; // nEPs[k] señales entrantes
+ bias
141:
142:
143: // Carga los pesos de la retina desde "pesos.txt"
144:
145: ifstream fPesos("pesos.txt");
146: if(!fPesos){
147: ShowMessage("¡No se pudo abrir el fichero \"pesos.txt\"!");
148: Application->Terminate();
149: }
150:
151: for(nat j=0; j<nEPs[1] ; j++) // j: neurona sináptica
152: for(nat i=0; i<nEPs[0]+1; i++) // i: entrada (la última es el
bias)
153: fPesos >> pesos[0][j][i];
154:
155: fPesos.close();
156:
157: // Carga los patrones de entrada/salida desde "training.txt"
158:
159: ifstream fPatrones("training.txt");
160: if(!fPatrones){
161: ShowMessage("¡No se pudo abrir el fichero \"training.txt\"!");
162: Application->Terminate();
163: }
164:
165: for(nat p=0; p<6; p++){ // p: patron
166: for(nat i=0; i<nEPs[0]; i++) fPatrones >> patron[p][i];
167: patron[p][nEPs[0]] = 1;
168: fPatrones >> yEsp[p];
169: }
170:
171: fPatrones.close();
172: }
173:
174: //--------------------------------------------------------------175:
176: void __fastcall TForm1::Button10Click(TObject *Sender)
177: {
178: ofstream fE2m("grafica.txt");
179: if(!fE2m) ShowMessage("¡No se pudo abrir el fichero
\"grafica.txt\"!");
180:
181: const nat maxIter = 500;
182: const float tol = 0.01;
183:
184: nat iter=0;
185: float error2m = tol*4;
186:
187: while( (iter < maxIter) && (error2m > tol) ){
188:
189: // Asigna el patrón correspondiente:
190: nat p = iter % 6;
191: entrada = patron[p];
14
Discriminación de patrones usando un perceptrón monocapa
por davidj y euyyn
192:
193: // Procesa las salidas:
194: for(nat k=1; k<3; k++) // k: capa de neuronas
195: for(nat j=0; j<nEPs[k]; j++) // j: elemento de proceso
196: salida[k][j] = f(pesos[k-1][j], salida[k-1], nEPs[k-1]+1);
197:
198: // Calcula el error:
199: yObt[p] = salida[2][0];
200: if(iter<6)
201: error2m = tol*4; // Para que no salga
202: else{
203: error2m = mse(yEsp, yObt, 6);
204: }
205: if(fE2m) fE2m << error2m;
206:
207: // Actualiza los pesos:
208: widrow_hoff(pesos[1][0], salida[1], nEPs[1]+1,
209: yEsp[p] - yObt[p], float(Edit1->Text.ToDouble()));
210:
211: for(nat i=0; i<nEPs[1]; i++)
212: if(fE2m) fE2m << "\tw[" << i+1 << "]=" << pesos[1][0][i];
213: if(fE2m) fE2m << "\tumbral:" << -pesos[1][0][nEPs[1]] << endl;
214:
215:
216: iter++;
217: }
218:
219:
220:
221: if(error2m > tol) // Si falló
222: ShowMessage("La red no ha convergido en 500 iteraciones.");
223: else{
224:
225: ofstream fSalida("resultado.txt");
226: if(!fSalida){
227: ShowMessage("¡No se pudo abrir el fichero \"resultado.txt\"!");
228: return;
229: }
230:
231: fSalida << "Se completó el entrenamiento en " << iter << "
iteraciones." << endl;
232: fSalida << "Los pesos y umbral finales son:" << endl;
233: for(nat i=0; i<nEPs[1]; i++)
234: fSalida << "w[ " << i+1 << " ] = " << pesos[1][0][i] << endl;
235: fSalida << "\tUmbral: " << pesos[1][0][nEPs[1]] << endl;
236:
237: fSalida.close();
238: }
239:
240: }
241: //---------------------------------------------------------------
15
Descargar