**IAIC** Introducción a las Redes Neuronales # Introducción El estudio de las redes neuronales artificiales, cuya historia se remonta a principios de los años 40, se inspira en gran medida en consideraciones biológicas. Por ello, aunque es posible realizar una introducción a las redes neuronales desde un punto de vista puramente matemático computacional, preferimos comenzar este capítulo con una breve introducción a las redes neuronales biológicas. Hemos de indicar que existe una tipología muy variada de neuronas, con distintas funcionalidades y especificaciones, por lo que nos centraremos en su versión más *ingenua* y resaltando las partes comunes que tienen todas ellas. ## Antecedentes biológicos ![](./img/Morfologia-de-la-neurona.jpg align="left" width=50%)La unidad (biológica y computacional) elemental de una red neuronal es la **neurona**, que consiste, esencialmente, en un cuerpo celular y prolongaciones a modo de cables que conectan la neurona con otras neuronas (ver figura adjunta) y que permiten interpretarlas como elementos comunicantes (de electricidad, o de información). Partiendo del cuerpo celular, un largo filamento, conocido como **axón** y que se comporta como vía de salida, se extiende hacia otras neuronas. A cierta distancia del cuerpo celular, el axón se divide en un delta de hilos más pequeños que terminan en **sinapsis** que forman las conexiones con otras neuronas. Las señales de entrada se reciben a través de las **dendritas**, que son prolongaciones más cortas conectadas directamente al cuerpo celular, y que están conectadas a los axones de muchas otras neuronas mediante sinapsis. La transmisión a través de las sinapsis es química para la mayoría de las neuronas: se liberan sustancias transmisoras en el lado axonal de la sinapsis y se difunden hacia el lado dendrítico, realizando la conexión. Aunque la transmisión en la sinapsis (es decir, entre neuronas) es química, la transmisión de señales dentro de una neurona es eléctrica. Cuando una neurona dispara una pulso, se propaga un potencial eléctrico (con velocidades de hasta 100 m/s) a lo largo del axón. Aunque es muy interesante, y ha dado lugar a otros modelos computacionales, no veremos aquí los detalles de este proceso. La frecuencia de disparo de las neuronas no es muy alta en comparación con las frecuencias de reloj de los ordenadores digitales. Después de cada pulso, una neurona necesita un **periodo de recuperación o relajación** (conocido como **periodo refractario**) durante el cual no puede disparar, lo que limita la frecuencia de disparo a 1 kHz o menos (en comparación con las frecuencias de reloj de unos pocos GHz -más de un millón de veces más rápidas- de los ordenadores modernos). Entonces, ¿cómo puede el cerebro realizar operaciones tan complejas si es relativamente lento? La respuesta está en la arquitectura masivamente paralela del cerebro, que le permite realizar muchas operaciones por ciclo. De hecho, incluso el cerebro de un insecto simple suele realizar muchas más operaciones por segundo que un ordenador. Las sinapsis entre neuronas pueden ser **excitatorias** o **inhibitorias**. Una sinapsis excitatoria aumenta la propensión de la neurona a disparar, mientras que una sinapsis inhibitoria hace lo contrario. **Una neurona funciona de forma binaria: se dispara o no se dispara**. Un cerebro animal típico consta de un gran número de neuronas: una rata tiene del orden de $10^{10}$ neuronas en su cerebro, y el cerebro humano contiene alrededor de $10^{12}$ neuronas, y entre $10^{14}$ y $10^{15}$ sinapsis. Por lo tanto, cada neurona está, de media, conectada a cientos de otras neuronas. Además, hay conexiones de corto y largo alcance, así como muchos bucles de retroalimentación, lo que hace que el cerebro sea una estructura extremadamente compleja, que está muy lejos de ser comprendida en este momento. ## Neuronas Artificiales ![](./img/mcp.jpg align="right" width=30%)Para distinguirlas de las neuronas biológicas, las que son simuladas en ordenadores o implementadas en hardware informático suelen denominarse **neuronas artificiales**. En la figura adyacente se muestra un modelo sencillo de una neurona, formulado por McCulloch y Pitts en la década de 1940. La **neurona McCulloch-Pitts** (MCP) consta de una serie de conexiones de entrada, que corresponden a las dendritas de la neurona biológica, un dispositivo sumador y un dispositivo umbral, que corresponden al cuerpo celular de la neurona que toma la decisión de disparar, y una conexión de salida que corresponde al axón de la neurona biológica. El dispositivo sumador suma las influencias de las conexiones de entrada. La fuerza de una conexión está representada por un número, conocido como **peso de la conexión**. El peso de una conexión puede ser positivo (excitador) o negativo (inhibidor). Además de las entradas externas, se suele definir una entrada constante adicional conocida como **término de sesgo** ($b$), que mide la propensión de la neurona a disparar en ausencia de una entrada externa. Matemáticamente, la salida del sumador se puede representar de forma sencilla en: $$s = \sum_{j=1}^n w_j x_j + b$$ donde $n$ es el número de neuronas, $w_j$ son los pesos de conexión y $x_j$ las señales de entrada. Para simplificar la notación, el término de sesgo suele escribirse $w_0x_0$, donde $w_0 = b$ y $x_0$ es siempre igual a $1$. Con esta notación, la salida del sumador puede escribirse como: $$s = \sum_{j=0}^n w_j x_j$$ La salida obtenida del sumador pasa por el dispositivo de umbral que, en el caso de la neurona MCP, es un limitador duro, es decir, su salida toma el valor $0$ si la entrada está por debajo del umbral ($T$), y $1$ si está por encima del umbral. Por lo tanto, la salida ($y$) de la neurona MCP se corresponde con la ecuación: $$y = \sigma(s) = \sigma(\sum_{j=0}^n w_j x_j)$$ donde $\sigma = 0$ si $s < T$, y $1$ en caso contrario. !!!Tip: Ejemplo 1 ![](./img/mcp2.jpg align="right" width=30%)Si se supone que las señales de entrada a una neurona MCP también son generadas por neuronas similares, toman los valores $0$ ó $1$. Cuando se introducen en el sumador, se multiplican por los pesos de conexión correspondientes. En la figura se muestra una neurona MCP donde dos de las entradas están activadas ($=1$) y dos desactivadas ($=0$). Las señales se suman de la siguiente manera: $$s = \sum_{j=0}^4 w_jx_j = 0,7 \times 1 + 0,5 \times 1 - 0,3 \times 0 + 0,4 \times 0 - 0,2 \times 1 = 1.0$$ y pasan por el umbral provocando, en este caso, el disparo de la neurona: $$y = \sigma(s) = 1$$ ya que $s > T = 0.5$. !!! La información de una ANN se almacena en los pesos de conexión entre las distintas neuronas de la red. Por tanto, **el cálculo (es decir, la funcionalidad) de la red no puede separarse de su arquitectura**. Si la arquitectura cambia, por ejemplo, añadiendo o eliminando algunas neuronas, el cálculo realizado por la red también cambia. El **aprendizaje en las ANN** es el proceso de determinar y ajustar los pesos que conectan las neuronas de la red para que ésta funcione como se desea. Al final de este capítulo veremos algunos de los algoritmos de aprendizaje. !!!Tip: Ejemplo 2 ![](./img/mcp3.jpg align="right" width=20%)Por ahora, examinemos una neurona sencilla, como la que se muestra en la figura adyacente. Las señales de entrada son recibidas por la red a través de los pequeños cuadrados de la izquierda en la figura. Permitiendo sólo señales de entrada binarias ($0$ o $1$) hay $4$ posibles combinaciones de entrada a esta red simple: $00$, $01$, $10$ y $11$. Las señales de salida correspondientes se muestran en la tabla siguiente. Por lo que la red codifica la función booleana `AND`. Recordemos que la función `AND` y otras funciones booleanas suelen representarse en forma tabular, mediante las llamadas tablas de verdad (que coincide, en este caso, con la función calculada por la neurona): ![](./img/mcp4.jpg width=10%) Hasta ahora sólo hemos considerado las neuronas que dan una salida binaria. En algunos casos puede ser útil una respuesta continua, que tome cualquier valor en un intervalo adecuado. Esta respuesta puede obtenerse fácilmente cambiando un poco la función de umbral $\sigma$. La elección más común de la función de respuesta graduada es la **sigmoidea**, definida por: $$\sigma(z) = \frac{1}{1 + e^{-cz}}$$ donde $c$ es un parámetro constante que determina la pendiente de la transición entre los dos valores límite: $0$ (cuando $z \to -\infty$) y $1$ (cuando $z \to +\infty$). A medida que $c \to \infty$, el sigmoide se convierte en un limitador duro con umbral $0$. En la siguiente figura se muestran dos funciones sigmoides para distintos valores de $c$ ($5$ y $10$). ![](./img/KcX81.png width=60%) Otra función umbral habitual, esta vez con salida en el intervalo $[-1, 1]$ en lugar de $[0, 1]$, es la función **tangente hiperbólica**. Su comportamiento es similar al de la sigmoidea, modificando el parámetro $\alpha$ podemos acercarnos también a un limitador duro con umbral $0$, tal y como muestra la siguiente figura: $$\tanh(z)=\frac{e^z-e^{-\alpha z}}{e^z+e^{-\alpha z}}$$ ![](./img/tanh.png width=50%) ## Redes Neuronales Artificiales ![](./img/ffn2.png align=right)Lo más interesante de las neuronas artificiales no es su uso individual, sino que están preparadas desde el principio para ser combinadas formando redes neuronales (artificiales) tal y como lo hacen las neuronas biológicas. La estructura de red más sencilla es la denominada **red feedforward en capas** (**FFNN**, por sus siglas en inglés), que se caracteriza por su particular estructura en la que las neuronas se organizan en capas separadas sin conexiones intracapa, y por la direccionalidad -de la entrada a la salida, sin conexiones de retroalimentación- del flujo de señales en la red. La red mostrada es una red de una sola capa. A primera vista podría decirse que contiene dos capas de neuronas. Sin embargo, la capa de entrada, representada en color naranja en la figura, sólo sirve para distribuir la entrada; no realiza ningún cálculo. Las unidades de dicha capa se denominan **elementos de entrada** para distinguirlas de las neuronas (en verde), que realizan el cálculo visto en la sección anterior. !!!Tip: Limitaciones de las redes monocapa Vamos a ver con ayuda de un ejemplo que la capacidad de representación de las redes de una sola capa son limitados. Consideremos un caso en el que se dan los pares de entrada-salida de la tabla siguiente: ![](./img/tabla1.jpg width=15%) ![](./img/ann3.jpg align="right" width=20%)Las señales de salida aparecen como bajas y altas en lugar de como valores numéricos. Esto es así porque los valores exactos no son importantes para el ejemplo que vamos a discutir. Los valores numéricos podrían ser $bajo = 0.1$ y $alto = 0.9$, o $bajo = 0.49$ y $alto = 0.51$. Consideremos ahora el caso de una red de una sola capa con dos elementos de entrada y una neurona, como se muestra en la figura. Los valores de salida producidos por la red para cada uno de los cuatro pares de entrada-salida son: $$y(1) = w_0 \mbox{ para } x_1 = 0, x_2 = 0$$ $$y(2) = w_0 + w_1 \mbox{ para } x_1 = 1, x_2 = 0$$ $$y(3) = w_0 + w_2 \mbox{ para } x_1 = 0, x_2 = 1$$ $$y(4) = w_0 + w_1 + w_2 \mbox{ para } x_1 = 1, x_2 = 1$$ Ahora, fijemos un valor de un separador $\alpha$ que distinga la salida baja de la salida alta. Por ejemplo, para los dos conjuntos $\{bajo, alto\}$ anteriores, $\alpha = 0.5$ sería un separador aceptable. Introduciendo $\beta = \alpha - w_0$ es fácil ver que, para reproducir los datos de salida deseados en la tabla anterior, la salida de la red tendría que satisfacer las ecuaciones: $$0 < \beta$$ $$w_1 > \beta$$ $$w_2 > \beta$$ $$w_1 + w_2 < \beta$$ Evidentemente, la última ecuación no puede satisfacerse si se cumplen las dos ecuaciones inmediatamente anteriores, y viceversa. Por lo tanto, la red de una sola capa es incapaz de formar una representación del conjunto de datos de la tabla anterior. Para el caso en el que los valores bajos se fijan en $0$ y los altos en $1$, el conjunto de datos de la tabla se corresponde con la función booleana exclusiva o `XOR`, y es sólo una de las muchas funciones booleanas que no pueden ser representadas por las redes monocapa. Es fácil ver que un cambio en la función de respuesta $\sigma(s) = s$ por cualquier otra función monótona tampoco ayudaría. El ejemplo anterior muestra que las redes de una sola capa son bastante limitadas en cuanto a los cálculos que pueden realizar, y no serían de una gran utilidad si nos quedáramos solo con ellas, por lo que a continuación consideraremos también las ANN multicapa, como muestra la figura siguiente: ![](./img/ffn.png width=30%) ![](./img/rnn.jpg align=right width=30%)La arquitectura FFNN no es la única posible para las ANN (Redes Neuronales Artificiales). Una red feedforward no puede recordar las señales de entrada encontradas anteriormente y, por tanto, no tiene memoria dinámica a corto plazo. Por supuesto, tiene una memoria estática a largo plazo proporcionada por el procedimiento de aprendizaje y codificada en sus pesos de conexión. Sin embargo, siempre dará la misma salida para cualquier entrada dada, independientemente de las señales que hayan precedido a esa entrada. Las redes neuronales recurrentes (RNN), en cambio, tienen conexiones de retroalimentación que proporcionan a la red una memoria limitada a corto plazo. La respuesta de una red de este tipo no sólo depende de los pesos de las conexiones, sino también de las señales de entrada anteriores. Las redes neuronales recurrentes no tienen por qué estar distribuidas en capas. En todo caso, en este capítulo solo nos dará tiempo a centrarnos en las FFNN. Las redes neuronales tienen muchas ventajas. Con la ayuda de algoritmos de aprendizaje, son capaces de formar su propia representación de los datos en las capas internas (ocultas) de la red, la mayoría de las veces de una manera no proporcionada explícitamente por el usuario. Sus elementos de computación no lineales les permiten representar mapeos muy complejos y no lineales. La naturaleza distribuida del cálculo en las ANN las hace tolerantes a los fallos; en una red grande, el fallo de una o unas pocas neuronas no degrada el rendimiento de la red de forma catastrófica. Esta degradación gradual es importante, especialmente en las implementaciones hardware de las ANN, que pueden ser muy robustas. Sin embargo, la computación distribuida también puede ser una desventaja, al menos desde el punto de vista de un observador humano. Las redes neuronales son a menudo muy difíciles de interpretar y, en algunos casos, deben utilizarse como cajas negras, proporcionando una salida correcta a cualquier entrada mediante la realización de cálculos que no son fácilmente accesibles para el usuario. # ANN como familia de funciones Si consideramos una arquitectura fija (seguimos centrándonos en redes FFNN) es fácil interpretar cómo, para cada posible juego de pesos en las conexiones de la red, se obtiene una función (probablemente distinta) que mapea datos de entrada en datos de salida. !!!Tip No es muy distinto a como ocurre con los polinomios: si se fija el grado del polinomio, $n$, basta cambiar los coeficientes para obtener funciones distintas. $$ \begin{array}{cccc} Pol: & \Real^{n+1} &\to & C^{\infty}(\Real,\Real)\\ & (a_0,a_1,\dots,a_n) &\mapsto & a_0+a_1 x + a_2 x^2 + \dots + a_n x^n \end{array} $$ Por lo que podemos ver $Pol$ como un generador de una familia de funciones que, dependiendo de los parámetros de entrada, produce distintas funciones matemáticas. En el caso de los polinomios la *arquitectura* es muy sencilla, porque es un vector de coeficientes todo lo que necesitamos para poder dar una parametrización adecuada. En el caso de las redes neuronales la estructura es un poco más compleja, pero no imposible de controlar: entre una capa de $N$ neuronas y otra de $M$ neuronas necesitamos algo parecido a una estructura con $(N+1)\times M$ valores (no olvidemos el sesgo), que pueden representarse por medio de una matriz como estructura de datos. Por lo que los parámetros asociados a una red con una arquitectura específica podrían darse como un conjunto de matrices, una matriz por cada conexión entre capas. La siguiente imagen muestra cómo se forma la matriz de pesos asociada a una sola capa (y no se explicita la función de activación, que es posterior a la agregación de valores de la capa anterior): ![](./img/FFNN.jpg width=80%) La ventaja que tiene usar una representación matricial es que podemos paralelizar el producto por el vector de entrada y hacer el cálculo simultáneamente sobre un conjunto de datos de entrada haciendo uso del producto de matrices (una operación implementada paralelamente en muchas estructuras de cálculo, como las GPUs): ![](./img/FFNN2.jpg width=80%) Trabajar con varias capas es tan sencillo como componer estos productos (y función de activación) de forma natural: ![](./img/FFNN3.jpg width=40%) ## ANN como Aproximadores Universales La pregunta natural que surge (o debería surgir como matemáticos e ingenieros computacionales) es ¿qué podemos calcular con esta familia?, ¿podemos conseguir todas las funciones posibles?. El conjunto de *todas* las posibles funciones es demasiado grande, y desde un punto de vista práctico, muy poco útil, así que la respuesta a esta segunda pregunta debe ser negativa. Sabemos que los polinomios no son capaces de representar exactamente *todas* las funciones posibles, pero sí que aproximan tanto como se quiera a una familia de funciones muy grande y útil (podemos hacer que en un intervalo el error de aproximación en cada punto sea tan pequeño como queramos). Así que nos bastaría poder responder afirmativamente a algo como: ¿podría al menos aproximar a otras funciones interesantes?, en este caso, ¿qué funciones serían aproximables? Hay dos trabajos clásicos que responden a esta pregunta con resultados muy interesantes: 1. [Hornik Stinchcombe y White, 1989](http://www.vision.jhu.edu/teaching/learning/datascience18/assets/Hornik-89.pdf). *Este trabajo establece rigurosamente que las FFNN con tan sólo una capa oculta que utilizan funciones de activación arbitrarias son capaces de aproximar cualquier función medible Borel de un espacio dimensional finito a otro, con cualquier grado de precisión deseado, siempre que se disponga de un número suficiente de unidades ocultas. En este sentido, las redes FFNN son una clase de aproximadores universales*. Esencialmente, este artículo dice que una clase muy grande de funciones puede ser aproximada por una FFNN muy simple. La función subyacente que pretendemos aproximar sólo tiene que ser *medible Borel* (entre espacios de dimensiones finitas), que es una familia de funciones que contiene prácticamente todas las funciones útiles que se utilizan en Ciencia (por ejemplo, las funciones continuas entre espacios de dimensión finita son funciones medibles por Borel). 2. [Barron, 1993](http://www.stat.yale.edu/~arb4/publications_files/UniversalApproximationBoundsForSuperpositionsOfASigmoidalFunction.pdf). *Se demuestra que las redes FFNN con una capa sigmoidal logran un error cuadrático de orden $O(1/n)$, donde $n$ es el número de nodos. Además, prueba que la tasa de aproximación es sorprendentemente ventajosa en entornos de alta dimensión*. Este trabajo dice que las FFNNA son especialmente buenas aproximadoras cuando se trabaja con muchas dimensiones. Dicho de otro modo, las FFNN pueden ayudar a mitigar la **maldición de la dimensionalidad**. !!!note: Maldición de la Dimensionalidad Una forma de entender la maldición de la dimensionalidad es que el número de puntos necesarios para aproximar una función crece exponencialmente con el número de dimensiones, no linealmente. En conjunto, estos dos resultados dicen que las ANN son muy buenas aproximadoras de funciones, incluso cuando el número de dimensiones es elevado y consideramos estructuras simples, como las FFNN. ## Búsqueda y Optimización Pero una cosa es que sepamos que *existe* una combinación de parámetros que aproxima la función que queremos modelar, y otra muy distinta es encontrar los parámetros adecuados para ello. El problema de encontrar los parámetros adecuados se corresponde completamente con los problemas de búsqueda que hemos visto en temas anteriores, pero en un espacio paramétrico terriblemente grande, por lo que habrá que ser muy precavido a la hora de comenzar a realizar esa búsqueda y disponer de herramientas suficientemente potentes. De hecho, durante muchos años las ANN fueron bastante ignoradas porque, a pesar de los buenos resultados mencionados en la sección anterior, no se disponía de un mecanismo adecuado para asegurar que esa búsqueda fuera exitosa ni de la capacidad computacional adecuada para poder manipular la cantidad de neuronas intermedias (y, por tanto, pesos) necesarias para asegurar una aproximación relativamente buena. En esencia, teniendo en cuenta que el problema general sigue siendo aproximar una función que conocemos a través de algunos puntos aislados, el problema de búsqueda se convierte en un problema de optimización en el que intentamos reducir el error cometido, así que podríamos usar cualquiera de los mecanismos vistos anteriormente (por ejemplo, un templado simulado, donde la función de energía es el error cometido), pero la práctica indicaba que esa metodología no resultaba ni rápida ni eficiente. ![](./img/GDO.gif align="left" width=30%)Pero no todo está perdido. Aunque es verdad que el problema es una optimización funcional, no es cualquier tipo de optimización. Tenemos libertad de elegir la función de activación que queramos (bajo ciertas restricciones), y podemos medir el error con libertad, así que ¿qué pasaría si eligiéramos estos elementos de forma que tuvieran buenas propiedades matemáticas?, por ejemplo, que sean continuas y derivables. Hemos visto que la función que calcula una red neuronal es una composición de productos y sumas simples, funciones de activación, ... y poco más. Así que si la función de activación es derivable, la función que calcula la red neuronal será derivable. Y si la función de error también lo es, entonces no estamos buscando en cualquier espacio, estamos buscando una optimización que podemos derivar, así que podemos hacer uso de las técnicas habituales que nos proporciona el Análisis Matemático, por ejemplo, algo parecido a un ascenso de la colina, pero haciendo uso de la derivada (gradiente) para saber en qué dirección nos dirigimos hacia un óptimo (al menos, local). Este es, esencialmente, el método de **Propagación hacia atrás** (**Retropropagación** o **Backpropagation**) que veremos en la siguiente sección y el resto de métodos derivados que se usan en la actualidad, y que se agrupan bajo el nombre de [Gradient Descent Optimization Algorithms](https://ruder.io/optimizing-gradient-descent/). # Aplicaciones Las redes feedforward constituyen la arquitectura más común para las redes neuronales artificiales, y el uso de la retropropagación para el entrenamiento de redes feedforward está muy extendido. La selección de una arquitectura adecuada para un sistema adaptativo es una cuestión difícil. Aunque no se puede proporcionar un método general, se pueden dar algunas directrices. En primer lugar, hay que recordar que una red neuronal representa a menudo una solución de caja negra a un problema: la red puede ser entrenada para aprender casi cualquier mapeo de entrada-salida, pero la representación interna del mapeo es a menudo muy difícil de analizar, principalmente porque el cálculo en las redes neuronales está distribuido, y cada neurona realiza sólo una pequeña parte del cálculo global. El cálculo distribuido es una gran ventaja en las implementaciones de hardware, donde la red neuronal debe ser capaz de proporcionar predicciones correctas incluso si una (o unas pocas) neuronas fallan. Las redes neuronales también suelen ser muy buenas a la hora de interpolar entre los valores de entrada utilizados para el entrenamiento y, por tanto, de generar una salida correcta incluso para valores de entrada no vistos previamente (incluso en problemas no lineales). En las aplicaciones industriales, a menudo es necesario encontrar un modelo de, por ejemplo, un sistema mecánico o eléctrico, que pueda generar la misma salida que el sistema real para cualquier entrada. La tarea de encontrar dicho modelo se conoce como identificación del sistema y es una aplicación común de las redes neuronales. En los casos en los que existe un modelo físico exacto, suele ser mejor ajustar los parámetros de este modelo que utilizar una red neuronal. Sin embargo, es frecuente que el modelo físico sea demasiado complejo para las técnicas de ajuste de parámetros disponibles. En estos casos, el uso de una red neuronal está bien motivado. Por supuesto, algunos sistemas son tan complejos que no hay ningún modelo disponible, y en esos casos una red neuronal es una elección natural de representación. También hay que tener en cuenta que, aunque el entrenamiento de la red neuronal puede ser complejo, el cálculo de la salida de la red (una vez completado el entrenamiento) es casi instantáneo. Esta propiedad suele ser especialmente útil en la identificación de sistemas dinámicos complejos; aunque exista un modelo físico, puede ser tan complejo que el cálculo de la salida no pueda realizarse en tiempo real, lo que motiva el uso de una red neuronal en lugar del modelo físico. ### Ejemplo: Aproximación de funciones Las redes neuronales feedforward (FFNN) se utilizan a menudo para aproximar funciones matemáticas. Como ejemplo concreto, consideremos el control de la temperatura de los gases de escape en el motor de un coche. En este caso, ha sido habitual utilizar tablas de búsqueda para relacionar las distintas variables implicadas y proporcionar una buena predicción de la temperatura. Sin embargo, se trata de un problema fuertemente no lineal con muchas variables involucradas, por lo que las tablas de búsqueda deben ser grandes para proporcionar una cobertura suficientemente fina del espacio de entrada. Con restricciones en el espacio de almacenamiento de memoria disponible, una red neuronal puede proporcionar una representación más compacta de la función. A modo de ejemplo, consideraremos ahora un ejemplo concreto de aproximación de funciones. Consideremos la función: ![](./img/sin.jpg align="right" width=25%)$$f(x, y) = \frac{1}{2} \left( \sin \sqrt{2}xy + \cos \sqrt{3}(x^2 + y^2)\right),\quad (x, y)\in [-1, 1]^2$$ Comenzamos generando un conjunto de datos de entrenamiento que contiene $441=21^2$ pares $(x,y)$ y el valor correspondiente $f(x, y)$, y un conjunto de validación similar con $400 = 20^2$ puntos. Usaremos dos arquitecturas FFNN $2/4/1$ y FFN $2/10/1$ que serán entrenadas sobre los datos de entrenamiento usando retropropagación. Como tanto $x$ como $y$, y la función $f(x, y)$ toman valores en $[-1, 1]$, no hace falta normalizar (a priori) y usaremos $tanh$ como función de activación. En cada época de entrenamiento se compara el error cometido por las redes en ambos conjuntos de datos. ![](./img/sin2.jpg align="left" width=40%)Como puede verse en la figura adjunta, el error de entrenamiento pasa de un valor inicial de alrededor de $0.38$ a un valor final de $0.03/0.05$ (dependiendo de la arquitectura), y el error de validación pasa de alrededor de $0.36$ a alrededor de $0.03/0.05$ (dependiendo de la arquitectura). Como puede observarse, el error obtenido en el caso de $2/10/1$ cae inicialmente más rápido que para la red $2/4/1$. Sin embargo, luego el error alcanza una meseta antes de empezar a caer de nuevo (durante unas épocas, la red más pequeña se comporta mejor). En la última época mostrada, la red más grande ha conseguido un error ligeramente menor que la red pequeña. Es comportamiento es (experimentalmente) muy común.