Tags

, , , , , , , , ,

Lectores y webdependientes, con este post vuelvo a la temática de la programación avanzada y los resultados de mis proyectos. Esta ocasión la materia es Inteligencia Artificial, en específico Redes Neuronales y cómo entrenarlas con el método o técnica de Retro-Propagación (Back-Propagation).

Teoria Previa

Sacando de la Wikipedia la definición de Red neuronal, encontramos lo siguiente:

Las redes de neuronas artificiales (denominadas habitualmente como RNA o en inglés como: “ANN” Artificial Neural Network) 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.

Entonces una red neuronal es un conjunto de elementos que, conectados entre sí, colaboran para generar un valor de salida. El algoritmo que vamos a estudiar está diseñado para entrenar redes neuronales de varias capas; está basado en un modelo matemático conocido como la generalización de la delta y su funcionamiento busca reducir el error cuadrático de la red por medio de un gradiente.

Todo esto quizá suene complicado si no estás familiarizado con los términos matemáticos antes mencionados; sin embargo, en la programación y gracias a los estudios de quienes desarrollaron estas técnicas, no es tan complicado, un poco de paciencia y quizá varias releídas a ésto y le entenderás.

La siguiente figura nos muestra un ejemplo de una red neuronal multicapa:

Ejemplo de Red Neuronal Multicapa

Esta red, presenta 4 capas:

  • La capa 0, también denominada capa de entrada.
  • Las capas 1 y 2 que pertenecen al conjunto de capas ocultas
  • La capa 3, o capa de salida.

El modelo matemático de las redes neuronales está basado en elementos independientes llamados neuronas y presentan la siguiente forma:

Ejemplo de Neurona

Donde las Xi son las entradas, que pueden provenir del mundo real (mediante sensores o algo similar) o bien, desde otros perceptrones o neuronas, estas entradas pueden ser números reales, palabras, etc. Nosotros usaremos como entradas valores booleanos (1,0) aunque en las capas ocultas y la capa final obtendremos un número real. Los pesos, identificados por Wi ponderan el valor que se le ha de asignar a cada entrada y que permitirán al perceptrón comportarse de una forma determinada. Para ello es necesario realizar la suma ponderada de las entradas, es decir, multiplicar cada entrada por el peso asignado y después sumar todos esos valores, yo le llamo a esa suma s:

s = Σ (i=0 → n) Xi Wi

Y si nos fijamos bien, esto no es mas que el producto interno de 2 vectores, uno que contiene las entradas y el otro que contiene los pesos. Resalto que la Capa 0 no tiene pesos de entrada, se conceptualiza de esa forma para entender el funcionamiento total, pero no hay perceptrones en la Capa 0 (o de entrada). En el caso de las Unidades Lógicas de Umbral, la suma ponderada (s) se compara con un valor denominado umbral (θ), si la suma es mayor o igual a éste valor, entonces la salida del perceptrón será un 1, en el caso contrario la salida será un 0. Para nuestro estudio y aplicación, agregaremos el valor del umbral al vector de pesos como el elemento n+1 cambiando el signo; y en contraparte, agregaremos al vector de entradas un elemento n+1 que siempre será 1, de modo que el vector de pesos y el vector de entradas, quedarán de la siguiente forma:

X = {x1, x2, x3, …, xn, 1}

W = {w1, w2, w3, …, wn, -θ}

Esto nos sirve para comparar el resultado de la suma ponderada con el 0, lo cual es más sencillo de programar e implementar. Lo que aquí vamos a ver, es precisamente cómo es que se asignan los valores del vector de pesos y del umbral. Cabe mencionar que ésto es todo un arte, que el modelo de Retro-Propagación nos acerca a los valores más adecuados para los pesos, cambiándolos en sentido de la reducción del error cuadrático por medio de la delta generalizada.

Ahora bien, cada perceptrón de la red va a arrojar una salida que servirá como entrada a los perceptrones de la capa siguiente o la salida final, a este valor le llamaremos f. Para calcular este valor, los estudiosos de este tema determinaron que la función sigmoide se adecúa a los fines del entrenamiento. La función sigmoide posee muchas bondades y la razón por la cual fué elegida es que en el estudio matemático de la teoría, el gradiente precisa una función que pueda ser derivable, es decir que sea continua en todo el dominio de derivación; la simple comparación del valor de s con el 0 es una función escalón unitario la cual presenta una discontinuidad en el 0 precisamente. La función sigmoide es continua y derivable en todo su dominio además de que es muy similar a la función escalón:

Función escalón contrapuesta a la función sigmoide

La función sigmoide presenta la siguiente ecuación:

f = 1/1+e^(-s)

Donde s es el valor obtenido del producto interno del vector de pesos y el vector de entrada, previamente descrito.

Entrenamiento de una red neuronal mediante Retro-Propagación

Diseñar una red neuronal es todo un arte, determinar cuantos perceptrones tendrá cada capa es cosa de la experiencia del programador y de la habilidad de discernir entre mucha información y obtener la más útil. El proceso de determinar los valores adecuados del vector de pesos es llamado “entrenamiento de una ULU”, para ello es necesario un conjunto de vectores de entrenamiento (Ξ) que son conjuntos de distintos escenarios que pudiera recibir la ULU como entrada; por ejemplo, si quisieramos una ULU que modele el comportamiento de una compuerta AND, un posible conjunto de entrenamiento podría ser 2 de los 4 posibles valores de entrada a la ULU:

A B | A&&B                                                         
0 0 |   0                                                          
0 1 |   0                                                          
1 0 |   0 ->                                                       
1 1 |   1 -> Vectores que conformarían el conjunto de entrenamiento

De modo que para este ejemplo, podríamos tener dos vectores X1 = {1,0,1} y X2 = {1,1,1} recordemos que hay que agregar el valor n+1 en las entradas, siempre con el valor de 1 (o el más adecuado segun la categoria de las entradas) y el conjunto de entranamiento quedaría como Ξ = {X1,X2}.

Es importante mencionar que el conjunto de entrenamiento debe ser mínimo, en el caso de la compuerta AND no habría ningun problema de considerar todas las opciones como vectores de entrenamiento puesto que son pocos, pero hay casos (como el que tuve que programar) en los cuales tener en cuenta todos los valores de entrada es muy complicado y en algunos otros casos es imposible. El valor que sabemos, es el resultado correcto se llama deseado y hay un valor deseado por cada vector del conjunto de entrenamiento; para el caso de redes que posean más de una salida es necesario tener el valor deseado para cada una de las salidas consideradas.

El siguiente elemento necesario para el entrenamiento, es el conjunto de vectores que contendrán los pesos de cada entrada de cad perceptrón de la red. Estos pesos son asignados, en un inicio, de manera arbitraria, según mi propia experiencia y algunas cosas que he leído por ahi poner valores muy pequeños, del orden de los decimales, resulta conveniente puesto que los cambios que van ocurriendo son muy pequeños y fácilmente se van adaptando y acercando al valor deseado.

Un último dato independiente necesario es el llamado coeficiente de aprendizaje (c) este valor representa la magnitud de cambio en cada iteración y junto con un factor de cambio (δ) y al igual que los pesos iniciales y el diseño de la red neuronal, queda delegado al programador, yo he usado valores desde 0.1 hasta 4 o 5 dependiendo el problema. Para el entrenamiento de las redes neuronales use el valor de 1 por convenio con el profesor y con el texto.

La explicación matemática de lo siguiente puede ser vista en el libro “Inteligencia Artificial” del autor Nils J. Nilsson en los capítulos 2 y 3 (la nomenclatura usada en éste texto es la misma que maneja dicho autor).

Para entrenar una ULU unica y aislada hay varios métodos: el procedimiento Widrow-Hoff, la delta generalizada y la corrección del error. Todos estan basados en el método del gradiente descendente con modificaciones sútiles pero que reflejan distintos resultados. La retropropagación está basada en la delta generalizada y el algoritmo es el siguiente:

  1. Tomar un vector del conjunto de entrenamiento.
  2. Hacerlo pasar por la red neuronal, esto equivale a evaluar las entradas en cada perceptrón o ULU y seguir el camino marcado hasta las diferentes salidas, la forma de hacerlo es el siguiente:
    1. Obtener el valor s para cada ULU de la primer capa oculta (s = Σ (i=0 → n) Xi Wi).
    2. Obtener el valor de f para cada s de la primer capa (f = 1/1+e^(-s)).
    3. Usar los valores obtenidos como entradas para la siguiente capa
    4. Repetir los pasos del 1 a l 3 avanzando por las capas ocultas en cada iteración hasta llegar al final de la red neuronal
  3. Comparar las salidas (el valor de f para cada ULU en la capa final) con alguna condicion de paro. En caso de cumplirse terminar.
  4. Calcular el factor de cambio δ para cada ULU en la capa final de la red neuronal mediante la siguiente fórmula: δ = (d-f) f (1-f) Esto es, d es el valor deseado para esta ULU y f es el valor obtenido previamente para esta ULU.
  5. Calcular el factor de cambio δ para cada ULU de las capas ocultas mediante la siguiente formula: δi = fi (1-fi) βi ; el valor de β esta dado de la siguiente forma: βi = Σ δ’i * w’i, donde  δ’ es cada una de los valores δ de la capa siguiente (por ejemplo, despues de calcular los δ de la capa final segun el paso 4, se calcularán los δ de la capa anterior, entonces δ’ son cada uno de los delta de la capa final). El valor de w’ está dado por cada valor de peso sale de la ULU que deseamos entrenar, para explicarlo mejor usare el siguiente gráfico:
    Calcular el valor delta en capas intermediasComo vemos, para calcular cada δ’ es necesario el de la capa siguiente, lo cual nos obliga a hacer estso cálculos desde la capa final hacia atrás, de ahí el nombre de retropropagación
  6. Una vez calculados todos los δ de la red (uno por cada ULU) viene el reajuste de los pesos para cada ULU, la fórmula es la siguiente: Wi ← Wi + c δi X’ donde Wi es el vector de pesos de la ULU i; c es el coeficiente de aprendizaje; δ’ es el factor de cambio que calculamos para la ULU i; X’ es el vector de entrada a la ULU i, genéricamente podemos decir que son las salidas de la capa anterior.
  7. Tomar el siguiente vector del conjunto y reiniciar desde el paso 2.

Para mi particular caso, la condición de paro estuvo dada por el valor que obtuve de f tras la salida; este valor era ponderado de forma tal que si f >= 0.5 , entonces f = 1; caso contrario f = 0. Después comparaba este valor con el valor deseado para el vector que utilice y lo guardaba para comparar que la red neuronal cumpliera efectivamente con los resultados deseados, para todos los vectores del conjunto de entrenamiento.

Como vemos el proceso es reiterativo y requiere muchas iteraciones; la programación de un modelo genérico no es tan complicada, pero no es trivial. Yo desarrollé el algoritmo aqui mostrado para una red neuronal de la siguiente forma:

Red Neuronal para el Agente seguidor de bordes

Con todas las ULU interconectadas entre sí, de modo que cada ULU recibe 8 entradas (9 con el valor n+1) y al final tengo 4 salidas. Para el proceso de entrenamiento comence con 3 vectores, con sus respectivos 4 valores deseados (uno por cada salida). Los pesos iniciales que use son: 0.1, 0.2, 0.1, 0.0, -0.1, -0.2, -0.1, 0.0, 0.1; los use en todas las ULU iguales; el entrenamiento requirio al rededor de 100 iteraciones  y la implementación respondió bien hasta cierto punto. En el momento del error, tome el valor de las entradas en las cuales la red falló y la meti al conjunto de entrenamiento como un vector más, y de nuevo volvi a entrenar desde 0, de nuevo 100 iteraciones (+ o -) y volvi a implementarla; este proceso se repitió hasta que al final me quede con 13 vectores de los 256 disponibles (2^8) y con los pesos acomodados para cada ULU de manera eficaz.

Las redes de neuronas artificiales (denominadas habitualmente como RNA o en inglés como: “ANN”[1] ) 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.