Skip to content

Juan Mellado

IA: Redes Neuronales (4)

De igual forma que las puertas lógicas se conectan para crear circuitos lógicos, las neuronas artificiales se pueden conectar para crear redes neuronales artificiales. Al conectar una neurona con otra se construye un sistema con la capacidad de realizar tareas que una neurona no puede realizar por si sola de manera individual.

Red Neuronal Artificial

Uno de los diseños de red neuronal más utilizado hoy en día es la red neuronal multicapa. En este diseño las neuronas se distribuyen en capas, donde todas las neuronas de una capa se conectan con todas las neuronas de la siguiente capa.Red Neuronal Artificial

Dicho de una forma más técnica, las neuronas se organizan formando un grafo dirigido acíclico. Es decir, mediante conexiones dirigidas en un único sentido que va desde la entrada hasta la salida de la red, sin ciclos.

Una red neuronal artificial se compone de los siguientes elementos:

  • Un vector de valores de entrada:

X = \begin{bmatrix} x_1 \ldots x_{n} \end{bmatrix}

  • Una capa de neuronas de entrada:

L_0

  • Una serie de capas de neuronas ocultas:

L_{0 \lt i \lt k}

  • Una capa de neuronas de salida:

L_k

  • Un vector de valores de salida:

Y = \begin{bmatrix} y_1 \ldots y_{m} \end{bmatrix}

 

Vector de Valores de Entrada

El vector de valores de entrada representa los valores a ser introducidos en la red neuronal, como por ejemplo los pixeles de una imagen concreta dentro de un conjunto de imágenes.

Es el mismo vector de valores de entrada visto anteriormente en el análisis de las neuronas artificiales de forma individual.

Capa de Neuronas de Entrada

La capa de neuronas de entrada recibe el vector de valores de entrada y los introduce en la red. No realiza ningún tipo de procesamiento, lo que se introduce en la entrada se emite por la salida. Es una manera conveniente de poder gestionar todas las capas de una manera uniforme. En la mayoría de las representaciones se obvia.

Esta capa tiene tantas neuronas como elementos tiene el vector de valores de entrada.

Capas de Neuronas Ocultas

Las capas de neuronas ocultas son las que se encuentran entre la capa de entrada y la capa de salida. En la práctica son iguales que las capas de entrada y salida, aunque a diferencia de estas su número de neuronas no está determinado por las entradas y salidas.

El número de capas ocultas, y de neuronas por capa, necesarias para resolver un determinado problema no se conoce de antemano. Determinar el número de capas y neuronas a utilizar forma parte de encontrar la solución a un problema utilizando redes neuronales.

Capa de Neuronas de Salida

La capa de neuronas de salida recibe los valores de la última capa oculta y calcula los valores finales resultantes de la red neuronal.

Esta capa tiene tantas neuronas como elementos tiene el vector de valores de salida.

Vector de Valores de Salida

El vector de valores de salida representa los valores resultantes del cálculo realizado por la red neuronal.

No se impone ningún límite al número de elementos que puede tener el vector de valores de salida, a diferencia de una neurona individual, que sólo puede tener un valor de salida.

Notación

Para identificar los distintos elementos de una red neuronal en las expresiones matemáticas se utilizan índices, tanto superiores como inferiores. En los superiores se indica el número de capa al que pertenece el elemento entre paréntesis. En los inferiores el índice correspondiente del elemento dentro del vector de entrada o salida, o los índices correspondientes a un par de neuronas interconectadas entre sí.

Ejemplos:

  • Entrada 4 de la capa 2:

x^{(2)}_4

  • Peso de la conexión entre la neurona 1 de la capa 3 y la neurona 6 de la capa anterior:

w^{(3)}_{1, 6}

  • Neto de la neurona 2 de la capa 5:

z^{(5)}_2

  • Salida de la neurona 4 de la capa 1:

y^{(1)}_4

XOR

Una red neuronal artificial puede implementar una puerta lógica XOR, algo que una neurona artificial no puede hacer por si sola.

Una puerta lógica XOR admite dos entradas y produce una salida positiva sólo cuando una de las entradas es negativa y la otra positiva.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{x_1 \oplus x_2} \\ 0 & 0 & 0 \\ 0 & 1 & 1 \\ 1 & 0 & 1 \\ 1 & 1 & 0 \end{matrix}

Este tipo de puerta puede implementarse con una red neuronal de dos capas como la siguiente.

Red Neuronal - Puerta XORLa primera neurona de la primera capa implementa la operación lógica x_1 \lor x_2 , y la segunda \overline{x_1} \lor \overline{x_2} . La segunda capa, con una neurona, implementa (x_1 \lor x_2) \land (\overline{x_1} \lor \overline{x_2}) , que es precisamente la operación lógica XOR.

Los valores de salida de la capa de entrada se corresponden con los del vector de valores de entrada.

x^{(0)}_1 = y^{(0)}_1 = x_1

x^{(0)}_2 = y^{(0)}_2 = x_2

Los valores de salida de la capa oculta se corresponden con los de la operación lógica correspondiente implementada por cada una de las neuronas.

z^{(1)}_1 = w^{(1)}_{1, 1} * y^{(0)}_1 + w^{(1)}_{1, 2} * y^{(0)}_2 = 1 * x_1 + 1 * x_2 = x_1 + x_2

z^{(1)}_2 = w^{(1)}_{2, 1} * y^{(0)}_1 + w^{(1)}_{2, 2} * y^{(0)}_2 = (-1) * x_1 + (-1) * x_2 = -x_1 - x_2

y^{(1)}_1 = \begin{cases} 1 & \quad \text{if } x_1 + x_2 \geq 1 \\ 0 & \quad \text{if } x_1 + x_2 < 1 \end{cases}

y^{(1)}_2 = \begin{cases} 1 & \quad \text{if } -x_1 - x_2 \geq -1 \\ 0 & \quad \text{if } -x_1 - x_2 < -1 \end{cases}

La valores de salida de la capa de salida es el resultado de la operación XOR.

z^{(2)}_1 = w^{(2)}_{1, 1} * y^{(1)}_1 + w^{(2)}_{1, 2} * y^{(1)}_2 = 1 * y^{(1)}_1+ 1 * y^{(1)}_2 = y^{(1)}_1 + y^{(1)}_2

y_1 = \begin{cases} 1 & \quad \text{if } y^{(1)}_1 + y^{(1)}_2 \geq 2 \\ 0 & \quad \text{if } y^{(1)}_1 + y^{(1)}_2 < 2 \end{cases}

Sustituyendo para todos los posibles valores de entrada se puede verificar que se obtiene el resultado esperado.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{z^{(1)}_1} & \mathbf{z^{(1)}_2} & \mathbf{y^{(1)}_1} & \mathbf{y^{(1)}_2} & \mathbf{z^{(2)}_1} & \mathbf{y_1} \\ 0 & 0 & 0 & 0 & 0 & 1 & 1 & 0 \\ 0 & 1 & 1 & -1 & 1 & 1 & 2 & 1 \\ 1 & 0 & 1 & -1 & 1 & 1 & 2 & 1 \\ 1 & 1 & 2 & -2 & 1 & 0 & 1 & 0 \end{matrix}

Cada neurona de la capa oculta se centra en una característica concreta que deben satisfacer los vectores de entrada. Trazan fronteras de decisión que realizan una primera clasificación en base a esa característica, sin pretender resolver el problema en su totalidad de una sola vez.

La neurona de la capa de salida vuelve a clasificar los resultados de la primera capa, no en base a los valores de entrada, sino en base a la clasificación realizada en la capa anterior.

En la práctica, la primera neurona clasifica en función de si alguno de los valores del vector de entrada es igual a 1. Y la segunda neurona en función de si alguno de los valores es igual a 0. Lo que gráficamente puede representarse mediante una superficie plana sobre la que se traza una recta que determina una frontera de decisión para cada una de las operaciones lógicas implementadas por cada una de las neuronas.

Fronteras de Decisión - Puerta XOR

La neurona de salida clasifica como pertenecientes a la clase 1 a todos los valores clasificados como pertenecientes a la clase 1 por las dos neuronas de la capa anterior, y el resto como pertenecientes a la clase 0. Lo que puede interpretarse diciendo que clasifica un valor en función de si se encuentra a un mismo lado o no de las dos fronteras de decisión definidas en la capa oculta. De esta forma se evita las limitaciones de las neuronas individuales, que sólo pueden clasificar en base a una única frontera de decisión.

Frontera de Decisión - Puerta XOR

Es importante entender en este punto que las clases 0 y 1 para cada neurona tienen un significado distinto. Para la primera neurona de la capa oculta la clase 1 representa los vectores de entrada que tienen al menos uno de sus valores a 1. Para la segunda neurona representa los vectores de entrada que tienen al menos uno de sus valores a 0. Y para la neurona de la capa de salida representa los vectores de entrada que han sido clasificados como pertenecientes a sus correspondientes clases 1 por las dos neuronas artificiales anteriores.

De igual forma que es importante comentar que puede haber más de una solución para un mismo problema, no sólo utilizando una única neurona, sino también utilizando una red. Por ejemplo, la siguiente red también implementa la operación lógica XOR.

Red Neuronal - Puerta XOR La primera neurona de la primera capa implementa \overline{x_1} \land x_2 , y la segunda x_1 \land \overline{x_2} . La segunda capa implementa (\overline{x_1} \land x_2) \lor (x_1 \land \overline{x_2}) , que es otra forma de expresar la operación lógica XOR.

Intuitivamente resulta fácil imaginar que puede diseñarse una red neuronal para cualquier tipo de expresión lógica siguiendo este esquema o similares.

IA: Redes Neuronales (3)

El diseño de neurona artificial utilizado para modelar puertas lógicas representa un sistema de clasificación con dos categorías o clases. Si el valor neto de entrada iguala o supera el umbral de activación entonces la neurona clasifica el vector de entrada como perteneciente a la clase 1, y si no lo supera como perteneciente a la clase 0.

Interpretación Geométrica

Para entender mejor el funcionamiento de una neurona como sistema de clasificación es conveniente separar su mecanismo interno en dos partes. Por una parte la función de cálculo del valor neto de entrada, y por otra parte la función de activación.

La función de cálculo del valor neto define una combinación lineal (suma de productos de grado uno) que puede representarse gráficamente como una superficie formada por todos los puntos resultantes de evaluar dicha función:

z(X) = w_1 x_1 + w_2 x_2 + \ldots + w_n x_n  

Y la función de activación define una frontera de decisión sobre dicha superficie, formada por todos los puntos  que cumplen la siguiente condición:

w_1 x_1 + w_2 x_2 + \ldots + w_n x_n = T

O de forma implícita:

w_1 x_1 + w_2 x_2 + \ldots + w_n x_n - T = 0

En resumidas cuentas, lo que quiere decir todo esto es que si el valor neto es igual a T entonces el vector de entrada representa un punto que se encuentra sobre dicha frontera. En caso contrario un punto que se encuentra a un lado un otro de la misma.

En el caso más sencillo, con una única entrada x_1 , la expresión del valor neto se corresponde con la ecuación de una recta:

z(x_1) = w_1 x_1 , siendo w_1 la pendiente

Y la frontera de decisión está formada por un único punto sobre dicha recta:

w_1 x_1 = T

Esta interpretación permite visualizar el funcionamiento de la neurona de una forma más gráfica.Frontera de Decisión - 2 DimensionesEn este caso la función de activación clasifica un vector de entrada en base a su correspondiente valor neto sobre la recta contra el umbral T . Los valores en la región amarilla están por encima del umbral, o sobre el mismo umbral, y se clasifican en la clase 1. Los valores que están por debajo del umbral están en la región verde y se clasifican en la clase 0.

Para una neurona con dos entradas la ecuación del valor neto es de la forma:

z(x_1, x_2) = w_1 x_1 + w_2 x_2

Y la frontera de decisión es el conjunto de puntos que cumplen:

w_1 x_1 + w_2 x_2 = T

Si para dos dimensiones la expresión del valor neto representaba una recta, para el caso de tres dimensiones representa un plano.

Frontera de Decisión - 3 Dimensiones

O de forma simplificada, con una vista cenital:Frontera de Decisión - 3 DimensionesEn este caso, la función de activación clasifica los vectores de entrada en base a si sus valores netos sobre el plano están a un lado u otro de la recta que define la frontera de decisión. De igual forma que antes, los valores en la región amarilla se clasifican en la clase 1, y los que están en la región verde en la clase 0.

Para dos dimensiones la frontera de decisión es un punto sobre una recta, y para tres dimensiones una recta sobre un plano. O visto desde otro punto de vista, para dos dimensiones la frontera de decisión es la intersección de dos rectas, y para tres dimensiones es la intersección de dos planos.

A medida que aumenta el número de entradas aumenta el número de dimensiones. Pero desde el punto de vista matemático es indiferente, ya que la suma de varias combinaciones lineales da como resultado otra combinación lineal. El aumento de dimensiones lo único que hace es dificultar su representación de una forma visual. Para un número mayor de tres dimensiones las superficies son hiperplanos que no se pueden representar de una forma sencilla.

Clasificador Lineal

Armados con esta nueva visión del funcionamiento interno de una neurona artificial, se pueden reinterpretar de una manera más visual los diseños de neuronas que implementan puertas lógicas vistos anteriormente, y su funcionamiento como sistemas de clasificación. Fronteras de Decisión - Puertas OR y ANDEn la imagen de la izquierda está representada la superficie y frontera de decisión determinados por una combinación específica de pesos y umbral de activación que implementan una puerta lógica OR. En este caso la frontera de decisión es una recta que divide los valores netos sobre una superficie plana en dos clases. A un lado de dicha recta se encuentra el valor para el vector de entrada [0, 0], perteneciente a la clase 0, y al otro lado los valores para las entradas [0, 1], [1, 0] y [1, 1], pertenecientes a la clase 1.

De forma similar, en la imagen de la derecha está representada una puerta AND. A un lado de la frontera los valores para [0, 0], [0, 1] y [1, 0] pertenecientes a la clase 0, y al otro lado el valor [1, 1] perteneciente a la clase 1.

Frontera de Decisión - Puerta NOT

En el caso de una puerta NOT, por encima del umbral el valor para [0] perteneciente a la clase 1, y por debajo del umbral el valor para [1] perteneciente a la clase 0.

Fronteras de Decisión - Puertas NAND y NOR

Por último, una puerta NAND es igual que la puerta AND, pero con las mitades intercambiadas. Lo que puede interpretarse diciendo que el plano de la puerta NAND tiene la misma pendiente que la puerta AND pero con signo contrario. Lo que es acorde a la intuición de que una puerta NAND es la negación de una puerta AND. Y lo mismo puede decirse para la puerta NOR con respecto a una puerta OR.

Un aspecto interesante de esta representación gráfica es que facilita entender porqué las combinaciones de valores de pesos y umbrales propuestos para modelar las puertas lógicas no son los únicos posibles. Existen otras combinaciones de valores que también son soluciones válidas. Por ejemplo, cualquiera de las siguientes fronteras de decisión representa una solución válida para modelar una puerta AND.

Posibles Fronteras de Decisión - Puerta AND

Aún así, a pesar de todas sus propiedades, las neuronas artificiales tienen una limitación importante que hay que tener en cuenta. Y es que una neurona sólo puede clasificar clases que sean linealmente separables. Es decir, que todos los valores netos para una clase deben ser menores que el umbral de activación, y todos los valores netos para la otra clase deben ser iguales o mayores que el umbral de activación. Lo que dicho de una forma un tanto burda quiere decir que se debe poder dibujar una línea recta que separe los valores de una clase a un lado de la recta, y los valores de la otra clase al otro lado.

En los ejemplos de las puertas lógicas anteriores siempre se puede trazar una línea recta que separa el conjunto de valores en dos clases. Pero desafortunadamente esto no se cumple para todos los casos, como el de la puerta lógica XOR.

Frontera de Decisión Imposible - Puerta XORLas entradas [0, 0] y [1,1] deben clasificarse como pertenecientes a la clase 0, y las entradas [0, 1] y [1, 0] a la clase 1, pero no existe ninguna forma de trazar una única línea recta que los separe dejando todos los puntos de una clase en un lado y todos los puntos de la otra clase en el otro lado. No son linealmente separables. Luego no se puede modelar una puerta XOR con una neurona artificial.

IA: Redes Neuronales (2)

Ejemplos habituales de neuronas artificiales muy sencillas de diseñar son aquellas que modelan el comportamiento de puertas lógicas. En la práctica no es necesario ni práctico utilizar neuronas artificiales para modelar puertas lógicas, pero son buenos ejemplos que permiten intuir su carácter de herramientas universales para resolver problemas.

OR

Una puerta lógica OR admite dos entradas y produce una salida positiva sólo cuando al menos uno de los dos valores de entrada es positivo.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{x_1 \lor x_2} \\ 0 & 0 & 0 \\ 0 & 1 & 1 \\ 1 & 0 & 1 \\ 1 & 1 & 1 \end{matrix}

Se considera el criterio habitual de que los únicos valores de entrada y salida válidos son 1 y 0. Donde 1 representa un valor positivo (true) y 0 un valor negativo (false).

Una puerta lógica OR puede diseñarse con una neurona de dos entradas. Cada entrada con un peso igual a 1. Y un umbral de activación igual también a 1.

Neurona Artificial - Puerta OR

Con esta configuración, el valor neto de entrada z es igual a la suma aritmética de los valores de entrada x_1 y x_2 :

z = \displaystyle\sum_{i=1}^{2} w_i x_i = w_1 * x_1 + w_2 * x_2 = 1 *  x_1 + 1 * x_2 = x_1 + x_2

Si ambas entradas son 0, el valor neto es 0. Si una de las entradas es 1 y la otra 0, el neto es 1. Y si ambas entradas son 1, el neto es 2.

Utilizando un umbral de activación T igual a 1, el valor de salida de la neurona y será 1 cuando el neto sea igual o mayor que 1, y 0 en caso contrario:

y = f(z) = f(x_1 + x_2) = \begin{cases} 1 & \quad \text{if } x_1 + x_2 \geq 1 \\ 0 & \quad \text{if } x_1 + x_2 < 1 \end{cases}

Lo que quiere decir todo esto es que la salida será 1 cuando la suma aritmética de las entradas sea mayor o igual que 1, algo que sucede cuando al menos una de las entradas es 1 (true).

Sustituyendo, es fácil verificar que para las distintas posibles combinaciones de entrada se obtiene el resultado esperado:

f(0 + 0) = f(0) = 0

f(0 + 1) = f(1) = 1

f(1 + 0) = f(1) = 1

f(1 + 1) = f(2) = 1

En este punto es interesante observar que se puede utilizar cualquier valor mayor de cero para los pesos y el umbral de activación. Por ejemplo, un valor de 4 en los pesos y el umbral siguen modelando una puerta OR. Es decir, un mismo diseño con parámetros de configuración distintos puede resolver un mismo problema.

AND

Una puerta lógica AND admite dos entradas y produce una salida positiva sólo cuando los dos valores de entrada son positivos.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{x_1 \land x_2} \\ 0 & 0 & 0 \\ 0 & 1 & 0 \\ 1 & 0 & 0 \\ 1 & 1 & 1 \end{matrix}

Este tipo de puerta puede modelarse con una neurona de dos entradas siguiendo el mismo diseño utilizado para la puerta OR. Ponderando las entradas con un mismo peso, pues ambas son iguales de importante, pero utilizando esta vez como umbral de activación el doble de dicho peso, pues sólo se quiere activar la neurona cuando ambas entradas sean positivas.

Neurona Artificial - Puerta AND

De igual forma que en el diseño anterior, el valor neto es igual a la suma aritmética de las entradas:

z = \displaystyle\sum_{i=1}^{2} w_i x_i = w_1 * x_1 + w_2 * x_2 = 1 *  x_1 + 1 * x_2 = x_1 + x_2

Utilizando 2 como umbral, el valor de salida de la neurona será 1 cuando el neto sea igual o mayor que 2, y 0 en caso contrario:

y = f(z) = f(x_1 + x_2) = \begin{cases} 1 & \quad \text{if } x_1 + x_2 \geq 2 \\ 0 & \quad \text{if } x_1 + x_2 < 2 \end{cases}

Sustituyendo, es fácil verificar que para las distintas posibles combinaciones de entrada se obtiene el resultado esperado:

f(0 + 0) = f(0) = 0

f(0 + 1) = f(1) = 0

f(1 + 0) = f(1) = 0

f(1 + 1) = f(2) = 1

Es interesante observar en este punto que un mismo diseño de neurona ha servido para modelar tanto la puerta OR como la AND. Es decir, un mismo diseño con parámetros de configuración distintos puede resolver problemas distintos.

NOT

Una puerta lógica NOT admite una entrada y produce una salida positiva sólo cuando la entrada es negativa.

  \begin{matrix} \mathbf{x_1} & \mathbf{\overline{x_1}} \\ 0 & 1 \\ 1 & 0 \end{matrix}

Esta puerta puede modelarse con una neurona de una entrada con peso -1 y un umbral de activación igual a 0. Utilizar un valor negativo responde a la idea intuitiva de que una puerta NOT invierte su valor de entrada.

Neurona Artificial - Puerta NOT

En este caso el valor neto es igual a la entrada con el signo cambiado:

z = \displaystyle\sum_{i=1}^{1} w_i x_i = w_1 * x_1 = -1 *  x_1 = -x_1

Utilizando 0 como umbral, el valor de salida de la neurona será 1 cuando el neto sea igual o mayor que 0, y 0 en caso contrario:

y = f(z) = f(-x_1) = \begin{cases} 1 & \quad \text{if } -x_1 \geq 0 \\ 0 & \quad \text{if } -x_1 < 0 \end{cases}

Nuevamente, sustituyendo, es fácil verificar que para las distintas posibles combinaciones de entrada se obtiene el resultado esperado:

f(0) = 1

f(-1) = 0

NAND

Una puerta lógica NAND admite dos entradas y produce una salida positiva sólo cuando al menos una de las entradas es negativa.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{\overline{x_1 \land x_2}} \\ 0 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 0 & 1 \\ 1 & 1 & 0 \end{matrix}

Este tipo de puerta lógica puede diseñarse con una neurona de dos entradas, con peso -1 cada una de ellas, y -1 como umbral de activación. El hecho de que todos los valores sean negativos responden a la idea intuitiva de que una puerta NAND es la negación de una puerta AND. 

Neurona Artificial - Puerta NAND

z = \displaystyle\sum_{i=1}^{2} w_i x_i = w_1 * x_1  + w_2 * x_2 = -1 *  x_1 - 1 * x_2 = -x_1 - x_2

y = f(z) = f(-x_1 - x_2) = \begin{cases} 1 & \quad \text{if } -x_1 - x_2 \geq -1 \\ 0 & \quad \text{if } -x_1 - x_2 < -1 \end{cases}

Nuevamente, sustituyendo para todas las combinaciones posibles de entrada, se verifica el resultado esperado:

f(0 - 0) = f(0) = 1

f(0 - 1) = f(-1) = 1

f(-1 - 0) = f(-1) = 1

f(-1 - 1) = f(-2) = 0

Es interesante que una neurona artificial pueda modelar una puerta NAND, ya que es una puerta lógica universal, lo que quiere decir que puede diseñarse cualquier circuito lógico utilizando sólo puertas NAND.

NOR

Una puerta lógica NOR admite dos entradas y produce una salida positiva sólo cuando las dos entradas son negativas.

  \begin{matrix} \mathbf{x_1} & \mathbf{x_2} & \mathbf{\overline{x_1 \lor x_2}} \\ 0 & 0 & 1 \\ 0 & 1 & 0 \\ 1 & 0 & 0 \\ 1 & 1 & 0 \end{matrix}

Este tipo de puerta lógica puede diseñarse con una neurona de dos entradas, con peso -1 cada una de ellas, y 0 como umbral de activación.

Neurona Artificial - Puerta NOR

z = \displaystyle\sum_{i=1}^{2} w_i x_i = w_1 * x_1  + w_2 * x_2 = -1 *  x_1 - 1 * x_2 = -x_1 - x_2

y = f(z) = f(-x_1 - x_2) = \begin{cases} 1 & \quad \text{if } -x_1 - x_2 \geq 0 \\ 0 & \quad \text{if } -x_1 - x_2 < 0 \end{cases}

Nuevamente, sustituyendo para todas las combinaciones posibles de entrada, se verifica el resultado esperado:

f(0 - 0) = f(0) = 1

f(0 - 1) = f(-1) = 0

f(-1 - 0) = f(-1) = 0

f(-1 - 1) = f(-2) = 0

Por último, comentar que no todas las puertas lógicas pueden modelarse con una neurona artificial. El ejemplo paradigmático de esta limitación es la operación XOR. No existe ninguna combinación de pesos y umbral de activación que la modelen.

IA: Redes Neuronales (1)

Dentro del campo de estudio de la Inteligencia Artificial existe un conjunto de técnicas que se engloban bajo el nombre de Machine Learning, lo que en castellano suele traducirse como Aprendizaje Automático. Las técnicas más populares de Machine Learning hoy en día son las que se basan en el uso de Redes Neuronales Artificiales.

Red Neuronal Artificial

Una red neuronal artificial en la práctica no es más que un programa que admite unas entradas, realiza un cálculo, y genera unas salidas. Es decir, hasta aquí nada nuevo. La diferencia está en que el cálculo que realiza una red neuronal no tiene porque estar codificado de manera explícita. El código fuente de una red neuronal no tiene que escribirse programando las reglas que resuelven el problema. Aplicando técnicas de Machine Learning la propia red neuronal puede llegar a descubrir cómo resolver el problema.

Imaginemos que queremos escribir un programa que admita como entrada una serie de imágenes aleatorias, y genere como salida un valor indicando para cada imagen si contiene una mesa o una silla. Es evidente que intentar resolver este problema utilizando técnicas tradicionales de programación sería muy complicado. Sobre todo, la mayor dificultad estaría en definir el propio concepto de mesa o silla de una forma universal mediante un lenguaje de programación.

Las redes neuronales artificiales representan una forma de escribir programas que facilitan la resolución de problemas como el descrito en el párrafo anterior. Su diseño está inspirado en las redes neuronales biológicas presentes en el sistema nervioso y su capacidad de abstracción, útil para generalizar conceptos. Lo que quiere decir que las redes neuronales tienen la capacidad de representar, tanto características complejas que resultan habitualmente difíciles de expresar de una forma sencilla, como las relaciones no evidentes a primera vista existentes entre las mismas.

Red Neuronal Biológica

Las redes neuronales biológicas son la fuente de inspiración de las redes neuronales artificiales. Estas últimas se diseñaron como una versión simplificada de las primeras a raíz de las estructuras y procesos observados en el cerebro por la biología.

Una red neuronal biológica es un conjunto de células interconectadas entre sí mediante señales eléctricas y químicas llamadas neuronas. Las neuronas responden a estímulos externos y generan una respuesta.

Neurona Biológica

Cada neurona está compuesta por los siguientes elementos:

  • Dendritas

Conexiones de entrada a través de las que se reciben señales. Son conjuntos de pequeñas ramificaciones a través de las cuales se pueden llegar a recibir varios miles de señales.

  • Soma

Núcleo de la célula. Procesa las señales de entrada. La suma de todas las entradas determina si la neurona se excita o se inhibe. Si se excita se desencadena un proceso que produce una reacción química y eléctrica de mayor o menor intensidad.

  • Umbral de activación

Valor que tiene que superar la suma de las señales de entrada para excitar la neurona.

  • Axón

Conexión de salida. Es una única ramificación gruesa y alargada a través de la cual se comunica el impulso nervioso. En su extremo tiene múltiples conexiones que se utilizan para conectarse con otras neuronas.

  • Sinapsis

Conexión que se establece con otras neuronas. En la práctica esto sucede en el espacio intermedio que existe entre las neuronas. Hoy en día se considera que es en el conjunto de todas estas conexiones donde realmente se almacena el conocimiento.

Es muy interesante notar en este punto como se diferencia una red neuronal de un ordenador convencional. Los ordenadores actuales siguen la arquitectura de Von Neumann, en la que la memoria se encuentra separada de la unidad de proceso que proporciona toda la capacidad de cálculo. En cambio, en las redes neuronales la capacidad de cálculo viene determinada por pequeñas unidades simples sin mucha capacidad y por las conexiones concretas que se establecen entre las mismas.

Debido a la importancia de las conexiones, las estructuras que siguen esta arquitectura se conocen como modelos computacionales conexionistas.

Neurona Artificial

Sin entrar a revisar los distintos modelos propuestos a lo largo de la historia, el diseño básico de una neurona artificial se basa en la morfología de las neuronas biológicas descritas anteriormente.

Neurona Artificial

Una neurona artificial se compone de los siguientes elementos:

  • Un vector de valores de entrada:

X = \begin{bmatrix} x_1 \ldots x_n \end{bmatrix}

  • Un vector de pesos:

W = \begin{bmatrix} w_1 \ldots w_n \end{bmatrix}

  • Un valor neto de entrada, suma de las entradas ponderadas por los pesos:

z = \displaystyle\sum_{i=1}^{n} w_i x_i

  • Un umbral de activación:

T

  • Una función de activación:

f(x) = \begin{cases} 1 & \quad \text{if } x \geq T \\ 0 & \quad \text{if } x < T \end{cases}

  • Un valor de salida, resultado de la función de activación aplicada al valor neto de entrada:

y = f(z)

 

Vector de Valores de Entrada

El vector de valores de entradas representa los valores a ser introducidos en la neurona, como por ejemplo los pixeles de una imagen concreta dentro de un conjunto de imágenes.

También suele llamarse vector de características, traducción del inglés «features», en referencia a que cada entrada representa una característica del conjunto de datos que se quiere analizar. Por ejemplo, si se estuviera analizando un grupo de personas, algunas características analizadas podrían ser la edad, el estado civil, o la altura.

Vector de Pesos

El vector de pesos sirve para ponderar las entradas, para dar mayor importancia a unas que a otras en función del problema concreto que se esté intentando resolver. Por ejemplo, la edad de una persona es más relevante que su altura en un sistema de detección temprana de cáncer.

Los valores que deben asignarse a cada peso para resolver un problema concreto no se conocen inicialmente. Determinar estos valores es encontrar parte de la solución al problema.

Valor Neto de Entrada

El valor neto de entrada es la suma ponderada de todas las entradas por sus pesos correspondientes.

Este valor es un compendio de todas las entradas recibidas por la neurona teniendo en cuenta la relevancia de cada una de ellas.

Umbral de Activación

El umbral de activación, referido habitualmente en inglés como «threshold», sirve para controlar la excitación o inhibición de las neuronas. Si el valor neto de entrada supera el umbral entonces la neurona se excita, en caso contrario se inhibe.

Al igual que con los pesos, el valor del umbral de activación necesario para resolver un problema concreto tampoco es conocido inicialmente.

Función de Activación

La función de activación determina el valor de salida de la neurona. En su forma más simple retorna 1 si el valor neto de entrada es igual o mayor que el umbral de activación, y 0 en caso contrario. Esta función de activación concreta se conoce como función escalón, del inglés «step». Al retornar sólo dos posibles valores finitos limita los posibles usos de la neurona, pero en este momento resulta conveniente por su simplicidad.

La función de activación a aplicar para resolver un problema concreto se determina durante la fase de construcción de la red neuronal. En la práctica existe un conjunto de funciones con propiedades bien definidas y se trata de escoger una de ellas en función de la naturaleza del problema.

Valor de Salida

El valor de salida de una neurona es el resultado de aplicar la función de activación al valor neto de entrada.

Técnicamente, el tipo de neurona artificial descrito se conoce con el nombre de Perceptron. Y a pesar de la aparente simplicidad de su estructura, es la base de la mayoría de desarrollos actuales de Inteligencia Artificial.

Scala (y 5)

Finalizando el tour de Scala.

Self-Type

Un auto-tipo en Scala indica que un trait extiende de otro. Es similar al uso de la palabra reservada extends, pero utiliza una nomenclatura distinta y no obliga a importar el trait del que se extiende.

Resulta un tanto confuso en un principio, pero por lo que podido leer al respecto, la motivación principal es permitir modelar relaciones de tipo «requiere un» frente a las más clásicas «extiende de». Algo que resulta de utilidad para implementar algunos patrones, en particular el de inyección de dependencias.

Para definir un auto-tipo hay que definir un identificador, normalmente this, seguido del nombre del trait del que se quiere obligar a extender, seguido del símbolo =>.

La declaración del auto-tipo obliga a extender con with de Vehicle, y por consiguiente, a definir el miembro brand. El compilador generará un error si no se implementa dicho requerimiento.

Implicit Parameters

Un método puede declarar una o más listas de parámetros con la palabra reservada implicit. Esto hace que dichos parámetros sean opcionales y que Scala trate de proporcionar un valor para los mismos si no se pasan de manera explícita.

En el código de ejemplo anterior, Scala resuelve en tiempo de ejecución el valor de y para completar la llamada a scale. En este sencillo ejemplo casan tanto el nombre como el tipo, pero podría cambiarse el nombre y aún así Scala seguiría casando el valor. Si ningún valor tuviera el mismo tipo entonces Scala trataría de resolver el valor atendiendo a unas determinadas reglas de conversión de tipo y resolución de contexto.

La resolución de los valores implícitos es un tanto compleja. La documentación oficial contiene un apartado dedicado de forma específica a explicar los detalles del proceso. Es conveniente revisarlo si se quiere comprender como implementa Scala esta característica, ya que es materia para un artículo completo.

Implict Conversions

Las conversiones implícitas en Scala son similares a los castings automáticos de algunos lenguajes de programación, pero con algunos matices propios de Scala.

Si se espera un valor de tipo T y se proporciona un valor de tipo S entonces se trata de hacer la conversión. En este sentido, Scala tiene un paquete llamado scala.Predef con un conjunto de conversores predefinidos.

En el código de ejemplo anterior se convierte un valor de tipo scala.Int en otro de tipo java.lang.Integer.

El compilador avisa con warnings cuando detecta alguna de estas conversiones implícitas, que se pueden silenciar importando el paquete scala.language.implicitConversions.

Polymorphic Methods

Scala denomina métodos polimórficos a los métodos con tipos parametrizados. Estos métodos se definen y comportan de manera similar a las clases genéricas.

En el código de ejemplo anterior se observa que el tipo parametrizado se puede indicar de forma explícita, o dejar que sea el propio Scala el que lo determine.

Type Inference

En línea con lo comentado en el apartado anterior, y lo visto a lo largo de estos artículos, Scala es capaz de inferir los tipos de determinadas expresiones sin necesidad de que se indiquen de forma explícita.

Se puede omitir el tipo en la declaración de un valor o variable inicializada en su definición.

En algunos casos se puede omitir el tipo resultante de un método o función cuando se puede inferir automáticamente del resultado.

Se puede omitir el tipo parametrizado en la instanciación de clases genéricas o métodos polimórficos cuando se puede inferir del contexto.

Scala no puede inferir los tipos de los parámetros, excepto en funciones anónimas pasadas como parámetros.

Por último, comentar que Scala no puede inferir el tipo del resultado en el caso de funciones recursivas.

Operators

Scala permite definir operadores, de igual forma que otros lenguajes de programación, tratándolos como métodos de la clase dentro de la que se definen.

De forma alternativa, los operadores pueden escribirse utilizando la notación punto como cualquier otro método.

By-name Parameters

Los parámetros por nombre son una forma que tiene Scala de permitir indicar que el valor de un parámetro debe evaluarse cada vez que se acceda a él dentro del cuerpo de un método, en vez de cuando se realice la llamada al método.

Notar que el nombre de esta característica resulta un poco confusa, y que además, aunque lo parezca, no es totalmente equivalente a una evaluación perezosa. De hecho, Scala tiene la palabra reservada lazy para evaluar perezosamente un valor.

En el código de ejemplo anterior el valor impreso es siempre el mismo, ya que se evalúa una sola vez, justo cuando se invoca al método execute. Y aunque este es el comportamiento por defecto esperado, Scala permite modificarlo utilizando los parámetros por nombre. Es una característica curiosa del lenguaje.

Los parámetros por nombre se denotan anteponiendo el símbolo => delante del tipo del parámetro.

En el nuevo código modificado el valor impreso es diferente cada vez, ya que la expresión pasada como parámetro ahora es evaluada cada vez que se accede a ella.

En buena lógica, el caso de uso habitual de esta característica es del demorar la ejecución de procesos computacionalmente costosos hasta que sea realmente necesario acceder al resultado de los mismos.

Annotations

Scala permite utilizar las anotaciones definidas en Java de la misma forma que se hacen en el propio código Java.

La documentación de Scala menciona un conjunto de anotaciones de propósito bien conocido como @deprecated, @deprecatedName, @transient, @volatile, @SerialVersionUID y @throws. Algunas más específicas como @scala.beans.BeanProperty y @scala.beans.BooleanBeanProperty. Y algunas que afectan a la forma en la que compilador genera el código como @unchecked, @uncheckedStable y @specialized.

Respecto a la construcción de anotaciones propias, la especificación las menciona en un apartado, pero no proporciona ningún ejemplo.

Default Parameter Values

El tour de Scala reintroduce en este punto un apartado acerca de los valores por defecto de los parámetros. Algo ya visto anteriormente.

Named Arguments

En la misma línea que en el apartado anterior, el tour de Scala comenta en este punto que los parámetros pueden pasarse en cualquier orden indicando su nombre.

Packages and Imports

Las clases en Scala se organizan en paquetes de igual forma que en Java, aunque no se fuerza a que la ruta del fichero coincida con el nombre del paquete.

Los paquetes se pueden definir de forma anidada utilizando bloques.

La sentencia import es similar a la de Java, pero puede utilizarse en cualquier punto, no sólo al principio de los ficheros, usa el caracter _ en vez de * para importar todos los miembros de un paquete, permite importar varias clases en una sola línea, e incluso permite renombrar un miembro al ser importado.