Red neuronal en la práctica: Gráfico de la Rectifier
Introducción
En el artículo anterior Red neuronal en la práctica: Surgimiento de C_Neuron, comenzamos a hacer las cosas de una forma un poco más organizada. Empezamos a construir una clase cuyo objetivo es contener todo lo que necesitaremos incluir dentro de una neurona. Esto nos permitirá, en el futuro, conectar varias neuronas dentro de una arquitectura cuyo propósito será, de hecho, construir una pequeña red neuronal sencilla. Nada al estilo de ChatGPT ni nada parecido. El objetivo aquí es presentar y mostrar, a los entusiastas de las redes neuronales, cómo funcionan por dentro. Sin utilizar ningún recurso adicional. Programándolo todo nosotros mismos.
Pues bien, nuestra primera neurona básica ya es capaz de hacer algunas cosas, puesto que consigue converger en muchas de las situaciones sencillas en las que podemos probarla. Como ya nos permite modificar, de forma extremadamente sencilla y rápida, la cantidad de entradas, esto la hace mucho más interesante a la hora de pensar en sus posibles aplicaciones. Sin embargo, todavía se encuentra en una fase muy inicial. Su modelo aún no incluye ningún tipo de función de activación ni tampoco un formato que nos permita colocar las neuronas en cascada, formando así una secuencia de neuronas y, por consiguiente, una pequeña red neuronal.
Pero, antes de ver esto, necesitamos hablar de otro tema. Así que aquí comenzaremos a hablar un poco sobre las funciones de activación. En muchas ocasiones, estas funciones son utilizadas de forma incorrecta por gran parte de los principiantes cuando se trata de redes neuronales. Y el motivo de este mal uso se debe a que se comprenden mal y, muchas veces, también se explican de manera bastante deficiente en términos generales.
Algunas personas simplemente dicen que se debe utilizar una u otra función de activación, lo cual ya es un error. Pero lo peor es que quien utiliza esas funciones ni siquiera posee los conocimientos matemáticos mínimos sobre el tema. Y cuando ve que su red neuronal ha dejado de aprender, no consigue entender por qué. Como consecuencia, termina tomando malas decisiones precisamente por desconocer las matemáticas implicadas.
Lo que explicaré en este artículo, y para lo cual muy probablemente sean necesarios otros, es precisamente lo que sucede dentro de la función de activación. La idea es que tú, estimado lector, consigas comprender cuándo, por qué y cómo utilizar y seleccionar la función de activación más adecuada. Cada caso es diferente. No existe una receta universal ni algo que deba hacer o utilizar siempre. Sin embargo, para tomar las mejores decisiones, es necesario comprender cómo funciona cada una de las funciones de activación y cuál es su impacto en toda la red neuronal, hablando en términos generales.
Muy bien, como existen decenas, literalmente, de funciones de activación, mostraré solo algunas. Al menos las más populares o las que se utilizan en la mayoría de los casos. Así que no pienses que existen únicamente las que se mostrarán y explicarán aquí. Busca información sobre expresiones o ecuaciones matemáticas que puedan utilizarse como funciones de activación. Pero, por lo menos, intentaré proporcionarte una base que te permita comprender los fundamentos de este tema, del que muchos hablan, pero pocos entienden realmente cómo funciona.
Entendiendo el problema
Existe un problema a la hora de intentar explicar ciertas cosas. No porque sean complicadas. Nada más lejos de la realidad. Muchas veces, el problema es que necesitamos comprender varios elementos que parecen independientes, pero que forman parte de un mismo conjunto. El tema de las funciones de activación, al igual que la retropropagación, sobre la que hablaré más adelante, es uno de esos casos. Resulta muy complicado explicar cómo funciona sin entrar en algunos asuntos que quizá todavía no se hayan abordado adecuadamente.
Así que, de forma muy resumida, la función de activación sirve para recortar y modificar la recta secante que está construyendo la función de coste. Básicamente, eso es todo. Puede parecer una locura expresarlo de esta manera, pero la función de activación no tiene otra finalidad que la que acabo de mencionar. Y no, la función de coste no utiliza la recta tangente, sino la recta secante. Ya expliqué esto en otros artículos. Pero esta es la parte sencilla de la explicación. Lo complicado es mostrar cómo la función de activación consigue producir este efecto y por qué, en algunos casos, realmente necesitaremos una función de activación, mientras que en otros podremos prescindir perfectamente de ella. Esto contradice la afirmación, bastante frecuente, de que siempre es necesario utilizar alguna función de activación en una red neuronal.
Bien, me parece bastante razonable centrar la explicación en mostrar cómo sucede todo esto. Sin embargo, existe otro problema, que surge porque estamos utilizando el descenso de gradiente en lugar del método de mínimos cuadrados. Este problema se abordará con algo más de detalle cuando veamos la retropropagación, pero ya implica ciertas precauciones a la hora de elegir una función de activación. ¿Y cuál es ese problema? Pues bien, su origen se encuentra en las derivadas.
¿Pero cómo es posible? ¿No sirven las derivadas para reducir el coste computacional de la función de coste? Sí, estimado lector. Sin embargo, cuando utilizamos derivadas en la función de coste, debemos preocuparnos por los efectos que tendrán sobre la función de activación. Como mencioné hace un momento, este problema se manifiesta con mayor intensidad durante la retropropagación. Ahí es donde el uso de las derivadas realmente cobra importancia. Esto se debe a que, si eliges una función de activación incorrecta, llegará un momento en que la neurona, o mejor dicho, la red neuronal, simplemente se estancará. Ya no podrás seguir convergiendo debido a un fallo matemático. Es decir, puedes creer que sabes lo que estás haciendo, mientras tu red neuronal simplemente deja de funcionar precisamente por falta de conocimientos matemáticos sobre el tema.
Por lo tanto, lo que explicaré aquí solo se aplica cuando utilizamos derivadas en la función de coste. Si no utilizas derivadas, buena parte de lo que se explicará no será aplicable a tu red neuronal. Aclarado esto, podemos comenzar a analizar las cosas de una manera que intentaré hacer lo más didáctica posible. El gran detalle es que habrá bastante matemática en la explicación. Por lo tanto, quien no disfrute de las matemáticas seguramente odiará este tema. Pero, desgraciadamente, las redes neuronales son así. Hay muchísima matemática y, cuando uno cree que está terminando, todo vuelve a empezar, solo que con todavía más matemáticas de por medio.
Explicar las cosas utilizando pocas matemáticas es prácticamente imposible. Sin embargo, comenzaremos con una función de activación muy sencilla, cuya matemática es igualmente sencilla. De este modo, tú, estimado lector, no te asustarás ni tendrás miedo de las siguientes cosas que veremos.
La función de activación Rectifier
Bien, la primera función de activación que veremos será la Rectifier, conocida popularmente como ReLU. ¿Pero de dónde viene el nombre ReLU? Pues bien, proviene de su nombre completo: REctifiedLinearUnit. Las letras en mayúscula son precisamente las utilizadas para formar el nombre. De ahí ReLU. Su objetivo es muy sencillo: eliminar todos los valores negativos de la red neuronal. Así de simple. En determinados momentos, los valores negativos pueden perjudicarnos más de lo que nos ayudan. En otros, en cambio, son esenciales para conseguir una convergencia adecuada de los parámetros de la red neuronal. Si tu red concreta está teniendo problemas debido a la presencia de valores negativos, puedes utilizar ReLU para eliminarlos. Su fórmula se muestra a continuación.
![]()
Pero, hombre, ¿eso es todo lo que hace la función de activación? Sí, estimado lector. Sin embargo, tiene un problema cuando utilizamos derivadas en la función de coste. Un detalle: siempre que hable de derivadas, deberás entender que me estoy refiriendo a la función de coste, salvo que mencione expresamente otra cosa. De esta manera, evitaré repetir continuamente el mismo término.
Pero volvamos al tema. Si conoces los fundamentos de las derivadas, sabrás que no es posible definir una derivada en el punto cero. Y este es uno de los problemas que aparecen con ReLU. Cualquier valor negativo se convertirá automáticamente en cero. Y cuando tengamos que calcular la derivada en ese punto, surgirá un problema. De nuevo, el problema no aparece ahora, sino durante la retropropagación.
Ahora bien, la derivada de esta función es muy sencilla. De hecho, es una de las más simples entre las funciones de activación. La ecuación se muestra a continuación.

En la expresión anterior tenemos el resultado de la derivada. Pero observa lo siguiente: estamos definiendo el valor para x menor que cero y para x mayor que cero. ¿Qué ocurre cuando x es igual a cero? Pues bien, en ese caso, el valor estaría indefinido. Pero en computación no podemos trabajar así. Por lo tanto, necesitamos recurrir a un pequeño truco, o trampa. Cuando el valor de x es igual a cero, establecemos nosotros mismos cuál será el valor de la derivada. Normalmente se define como cero. Pero esto no significa que realmente sea cero. Se trata únicamente de un artificio utilizado en computación. Bien, esta ha sido la parte matemática de la explicación. Pero, como programadores, no nos interesan demasiado estas notaciones matemáticas. De hecho, algunas parecen jeroglíficos de lo confusas y complicadas que resultan de leer. Disculpen los matemáticos que estén por aquí, pero no pude resistirme a hacer la broma.
Muy bien, ahora, para hacer todo esto un poco más didáctico, implementaremos un pequeño indicador que nos permita estudiar estas funciones de una forma algo más fácil de comprender, viéndolas representadas en un gráfico. De este modo, creo que será mucho más sencillo entender qué está sucediendo. Podríamos utilizar distintos lenguajes para crear los gráficos, pero, como el objetivo es hacerlo todo en MQL5, lo haremos íntegramente en MQL5. Será interesante. Sin embargo, para separar un poco los temas, veremos esto en una nueva sección.
Representación gráfica de las funciones
Como muchas personas tienen grandes dificultades para observar una ecuación o expresión matemática y comprenderla, creo que mostrarla de forma gráfica resulta considerablemente más didáctico. De este modo, puedes estudiar tanto el gráfico como el código utilizado para crearlo. Los códigos que veremos tienen precisamente el objetivo de facilitar la interpretación de lo que representa la ecuación matemática.
Bien, teniendo esto en cuenta, veamos el código inicial de nuestro indicador. Se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property indicator_chart_window 004. #property indicator_plots 0 005. //+------------------------------------------------------------------+ 006. #include <Canvas\Canvas.mqh> 007. //+------------------------------------------------------------------+ 008. #define def_Limit 10.0 009. #define def_Step 0.5 010. //+------------------------------------------------------------------+ 011. CCanvas *canvas; 012. //+------------------------------------------------------------------+ 013. struct st_00 014. { 015. int x, 016. y, 017. maxX, 018. maxY; 019. double Position; 020. }global; 021. //+------------------------------------------------------------------+ 022. void PlotTextPosition(void) 023. { 024. uint w, h; 025. string sz0; 026. 027. sz0 = StringFormat("Position: [%.1f]", global.Position); 028. TextGetSize(sz0, w, h); 029. (*canvas).TextOut(global.x - (w / 2), global.maxY + 5, sz0, ColorToARGB(clrBlack)); 030. } 031. //+------------------------------------------------------------------+ 032. void Function_Curve(void) 033. { 034. } 035. //+------------------------------------------------------------------+ 036. void UpdateGraphics(int direct) 037. { 038. double value = global.Position + def_Step * direct; 039. 040. global.Position = MathAbs(value) <= def_Limit ? value : global.Position; 041. 042. (*canvas).Erase(ColorToARGB(clrWhite, 255)); 043. 044. (*canvas).LineVertical(global.x, 0, global.maxY, ColorToARGB(clrRoyalBlue, 255)); 045. (*canvas).LineHorizontal(0, global.maxX, global.y, ColorToARGB(clrRoyalBlue, 255)); 046. 047. PlotTextPosition(); 048. Function_Curve(); 049. 050. (*canvas).Update(true); 051. } 052. //+------------------------------------------------------------------+ 053. void Resize(void) 054. { 055. uint w, h; 056. 057. global.maxX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); 058. global.maxY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); 059. 060. if (canvas != NULL) 061. { 062. (*canvas).Destroy(); 063. delete canvas; 064. } 065. canvas = new CCanvas; 066. (*canvas).CreateBitmapLabel("BL", 0, 0, global.maxX, global.maxY, COLOR_FORMAT_ARGB_NORMALIZE); 067. global.x = global.maxX / 2; 068. global.y = global.maxY / 2; 069. TextGetSize("M", w, h); 070. global.maxY -= (int)(h * 2); 071. } 072. //+------------------------------------------------------------------+ 073. int OnInit() 074. { 075. ZeroMemory(global); 076. canvas = NULL; 077. 078. return INIT_SUCCEEDED; 079. } 080. //+------------------------------------------------------------------+ 081. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 082. { 083. return rates_total; 084. } 085. //+------------------------------------------------------------------+ 086. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 087. { 088. int direct = 0; 089. 090. switch (id) 091. { 092. case CHARTEVENT_CHART_CHANGE: 093. Resize(); 094. break; 095. case CHARTEVENT_KEYDOWN: 096. if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT)) direct = -1; 097. if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT)) direct = 1; 098. break; 099. } 100. UpdateGraphics(direct); 101. } 102. //+------------------------------------------------------------------+ 103. void OnDeinit(const int reason) 104. { 105. (*canvas).Destroy(); 106. delete canvas; 107. } 108. //+------------------------------------------------------------------+
Este código constituye el esqueleto básico de lo que necesitaremos y utilizaremos. En este momento concreto no hace gran cosa. Simplemente crea una representación de un plano cartesiano en el gráfico y nos muestra un pequeño texto. Además, por supuesto, nos permite interactuar de alguna manera con el gráfico. Algo realmente sencillo y básico. Pero, para quien observe este código y no consiga comprender cómo funciona, haremos un rápido repaso de sus principales puntos. De este modo, a ti, estimado lector, que quizá seas un entusiasta que ha llegado a este artículo sin demasiado contexto, te resultará más sencillo comprender qué estamos haciendo en este código tan sencillo.
Básicamente, en todo este código existen dos puntos que podrás modificar si lo deseas. Existe otro más, pero lo veremos posteriormente. En principio, no necesitarás cambiar nada.
Pero, si decides modificar algo, te recomiendo centrarte en los siguientes puntos: el primero es el valor definido en la línea ocho y el segundo es el valor definido en la línea nueve. Estos valores se utilizan para establecer el desplazamiento de un indicador que posteriormente se colocará en el gráfico. Básicamente, estos son los dos únicos puntos que puedes modificar por ahora.
Así pues, hagamos un rápido repaso para comprender otros detalles de este código. En la línea 11 defino una variable que se utilizará para acceder a la biblioteca estándar de MQL5. Observa que estoy definiendo dicha variable como un puntero. Y esto tiene un motivo. Al definirla de esta manera, posteriormente puedo comprobar si el gráfico está inicializado o no.
En la línea 13 definimos una estructura global cuyo objetivo es proporcionar un acceso más controlado a determinadas variables. En la línea 22 tenemos un procedimiento que nos permitirá representar cierta información en forma de texto. En este caso, únicamente mostraremos el valor de la posición actual. En la línea 32 encontramos un procedimiento que analizaremos más adelante. Sin embargo, dependerá de lo que vayamos a colocar en el gráfico. En la línea 36 tenemos un procedimiento destinado a actualizar el gráfico cuando realizamos algún tipo de cambio en él. Algo muy sencillo que no requiere demasiadas explicaciones.
Ahora bien, en la línea 53 encontramos un procedimiento bastante curioso. Básicamente, sirve para ajustar las dimensiones del objeto bitmap que estamos utilizando, mediante la biblioteca estándar. Todo el procedimiento es bastante sencillo. Sin embargo, en la línea 60 realizamos una comprobación para verificar si la aplicación ha instanciado o no la clase CCanvas. Si está instanciada, la eliminaremos del gráfico y, posteriormente, en la línea 65, inicializaremos el puntero para poder volver a instanciar la clase. Este es el motivo por el que utilizo un puntero en la declaración de la línea 11. ¿Pero por qué hago todo esto? Para comprenderlo, es necesario entender cómo MetaTrader 5 ejecuta realmente el indicador.
Cuando indicamos a MetaTrader 5 que queremos añadir el indicador al gráfico, MetaTrader 5 comienza a ejecutar el código desde la función OnInit, presente en la línea 73. En esta función inicializamos los distintos elementos. Por ejemplo, en la línea 75 inicializamos los valores globales, garantizando que todos comiencen en cero. Y en la línea 76 indicamos que el puntero es nulo. Es decir, no apunta a ninguna instancia. Una vez hecho esto, MetaTrader 5 genera un nuevo evento, que será capturado por la función de la línea 86. El primer evento es CHARTEVENT_CHART_CHANGE, que indica a todas las aplicaciones presentes en el gráfico que algo ha cambiado. Como nuestra aplicación captura este evento, en la línea 93 llamaremos al procedimiento de la línea 53. Y será en ese momento cuando comenzaremos a instanciar la clase CCanvas de la biblioteca estándar, ya que en la línea 65 crearemos la instancia.
Bien, ¿pero realmente era necesaria toda esta complejidad? En realidad, no se trata de una complicación. Si modificas las dimensiones del gráfico, MetaTrader 5 volverá a generar el evento CHARTEVENT_CHART_CHANGE. Gracias a ello, podemos mantener siempre un dimensionamiento correcto sin tener que comprobar constantemente cada elemento. Pero, volviendo a la explicación, en la línea 81 tenemos el controlador de eventos OnCalculate. No hará nada, pero es necesario declararlo porque estamos utilizando una aplicación de tipo indicador. En la línea 86 tenemos el controlador de eventos OnChartEvent. Como ya hemos visto, se utiliza durante la inicialización o cuando se producen cambios en el gráfico. Pero también sirve para capturar las pulsaciones de las flechas derecha e izquierda, modificando así la posición que veremos en breve.
En la línea 103 tenemos el procedimiento encargado de tratar el evento Deinit, que eliminará cualquier elemento colocado por la aplicación que no deba permanecer en el gráfico. Si compilas este código y lo ejecutas en algún gráfico, verás la animación siguiente cuando pulses las flechas derecha e izquierda.

Esta idea es muy interesante, porque ahora todo lo que tendremos que hacer será definir la expresión o ecuación que queremos mostrar en el gráfico. A partir de ahí podremos visualizarla prácticamente sin modificar nada del código. Sin embargo, existe una cuestión interesante en este punto. Necesitamos describir la ecuación mediante código, no en su forma matemática. Aunque, si desarrolláramos un sistema pensado específicamente para ello, podríamos escribir la fórmula tal y como aparece normalmente en los libros y en la literatura matemática. Pero, como el objetivo aquí es únicamente mostrar determinados detalles, no iremos tan lejos. Nos mantendremos en un nivel más básico.
Bien, ahora necesitamos pensar en cómo representaremos las curvas en el gráfico. Es decir, debemos introducir algún código dentro del procedimiento de la línea 32. Esta quizá sea la parte realmente interesante, puesto que podemos hacerlo de varias maneras. Pero, como quiero utilizar la menor cantidad de código posible, lo haremos mediante un mecanismo que utilizaba cuando daba mis primeros pasos como programador. Es un mecanismo relativamente sencillo, pero nos permite hacer muchas cosas con muy poco esfuerzo, incluso en equipos más modestos.
Veamos, entonces, cómo se realizará la representación de las curvas, que al principio no serán demasiado suaves. Sin embargo, verás que podemos ir mucho más allá; todo dependerá de lo que queramos conseguir. Recuerda que, para hacer las curvas más suaves, bastará con modificar los valores presentes en las líneas ocho y nueve. En breve comprenderás mejor este punto. Veamos entonces el código. Y, como únicamente este fragmento será diferente para cada una de las funciones de activación que se mostrarán, lo separaré en bloques o secciones. Así pues, veamos la primera de las funciones en la sección siguiente.
Representación de ReLU y su derivada
El código para representar tanto la función de activación ReLU como su derivada se muestra a continuación.
31. //+------------------------------------------------------------------+ 32. void Function_Curve(void) 33. { 34. int x[], y[], d[]; 35. uint p; 36. double fx, step; 37. 38. ArrayResize(x, (int)(((def_Limit * 2) / def_Step) + 1)); 39. ArrayResize(y, x.Size()); 40. ArrayResize(d, x.Size()); 41. 42. step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / def_Step; 43. 44. fx = -def_Limit; 45. for (uint c = 0, m = x.Size(); c < m; c++, fx += def_Step) 46. { 47. p = (fx <= global.Position ? c : p); 48. x[c] = global.x + (int)(step * fx); 49. y[c] = global.y - (int)(step * MathMax(0, fx)); 50. d[c] = global.y - (int)(step * (fx <= 0 ? 0 : 1)); 51. } 52. 53. (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND); 54. (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND); 55. 56. (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255)); 57. (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255)); 58. 59. ArrayFree(d); 60. ArrayFree(y); 61. ArrayFree(x); 62. } 63. //+------------------------------------------------------------------+
Muy bien, ahí tenemos un código precioso y magnífico. Parece incluso algo divino, ya que realmente consigue hacer muchas cosas con muy poco código. Y todo esto de una manera extremadamente sencilla y fácil de comprender. Pero, sobre todo, fácil de explicar, lo cual lo hace bastante didáctico, aunque no sea especialmente eficiente en términos de ejecución. Veamos entonces qué está sucediendo aquí. Pero, antes, ¿qué tal si observamos el resultado de la ejecución? Puede verse en la animación siguiente.

Es realmente impresionante lo que podemos hacer cuando estamos dispuestos a hacerlo. Bien, pero comprendamos qué representa esta animación. Para ello, debemos volver nuestra atención al fragmento mostrado al comienzo de la sección. Así pues, veamos los detalles. En la animación puedes observar dos segmentos de recta: uno de color índigo, que sería un tono de azul, y otro de color naranja. También aparecen dos figuras circulares, una verde y otra roja. Estos objetos se crean respectivamente mediante las siguientes líneas: 53, la línea índigo; 54, la línea naranja; 56, el círculo verde; y, finalmente, 57, el círculo rojo.
Observa que todas estas líneas tienen algo en común. Todas utilizan, de una forma u otra, los arrays declarados en la línea 34. Así pues, vamos a entender cómo se dibujan los distintos elementos en la pantalla. Esto es importante porque gran parte del código mostrado en el fragmento no cambiará. Solo tendremos que modificar dos líneas para poder representar las demás funciones de activación.
Bien, en las líneas 38 a 40 reservamos espacio suficiente para almacenar todos los valores dentro del límite establecido en la línea ocho. En la línea 42 calculamos el tamaño de los pasos o, dicho de otra manera, la resolución con la que se trazarán las curvas. Como este valor dependerá tanto del tamaño de la ventana gráfica como de los valores definidos en las líneas ocho y nueve del código, quizá tengamos que ajustar ligeramente algunos parámetros para conseguir una curva bien representada. Si la granularidad es demasiado alta, verás que el dibujo de la curva presenta muchas irregularidades. Como ReLU es prácticamente una recta, todavía no podemos apreciar este problema, pero más adelante lo verás claramente.
Como esta parte del código no cambiará, únicamente tendrás que modificar el valor de la línea nueve para aumentar la resolución y conseguir que la curva se dibuje de forma más suave. En cualquier caso, en la línea 44 inicializamos el valor de fx. Este será el valor utilizado en los cálculos. A continuación entramos en el bucle for, en la línea 45. Este bucle realiza prácticamente todo el trabajo, ya que será el encargado de crear las curvas o líneas, dependiendo del caso concreto.
En la línea 47 realizamos una pequeña búsqueda para determinar el índice dentro del array. Este índice servirá para representar los círculos en el gráfico. ¿Pero por qué hacerlo de esta manera? La razón es bastante sencilla: evitar repetir innecesariamente el cálculo. Sin esta línea 47, estaríamos obligados a ejecutar el cálculo dos veces: una para dibujar la curva de la función y la de su derivada, y otra para saber dónde colocar los puntos sobre la curva, de modo que podamos ver dónde se está realizando el muestreo. En la línea 48 encontramos el cálculo encargado de determinar dónde estarán los puntos sobre el eje X. Estos puntos dependen tanto del límite como del número de divisiones. Ambos valores se declaran en las líneas ocho y nueve del código.
Perfecto, ahora vamos a ver las dos únicas líneas que tendremos que modificar para representar diferentes gráficos y funciones. Quiero que tú, estimado lector, prestes mucha atención a las líneas 49 y 50. Observa que son prácticamente iguales, salvo por la parte final de cada una. ¿Sabes qué significa esto? Pues bien, si has prestado atención al artículo, notarás que el final de la línea 49 corresponde exactamente al cálculo mencionado anteriormente para ReLU. Por su parte, el final de la línea 50 contiene el cálculo de la derivada de la ecuación de ReLU. Puede parecer sorprendente que estos cálculos sean tan sencillos de expresar mediante código, pero así es. Tanto es así que absolutamente todo el código permanecerá inalterado, independientemente de la situación. Lo único que cambiará será precisamente el contenido final de estas líneas 49 y 50.
Muy bien, esto ha quedado estupendo. Para finalizar, utilizamos la línea 53 para representar la curva de la función calculada y la línea 54 para representar la curva de su derivada. En la línea 56 posicionamos un pequeño círculo sobre la curva de la función y, en la línea 57, colocamos otro círculo sobre la curva de la derivada. Esto sí que resulta sorprendente. Sin necesidad de hacer grandes malabarismos con el código, conseguimos producir algo capaz de representar la curva de una función y la de su derivada, de tal manera que podemos analizar de forma interactiva qué sucede en cada punto de la curva. Simplemente maravilloso.
Consideraciones finales
En este artículo, básicamente, nos hemos limitado a construir un código íntegramente en MQL5 que nos permita estudiar con mayor tranquilidad la cuestión de las derivadas y de las funciones correspondientes. Comprender qué sucede con el resultado de una derivada o de su función será extremadamente importante para entender cómo funcionan realmente las funciones de activación. Pero, sobre todo, para comprender cuándo y por qué utilizar una u otra función de activación.
Como este artículo ha estado centrado, durante buena parte de su desarrollo, en mostrar el indicador que utilizaremos para visualizar la curva de la derivada y la de la función de activación, en el archivo adjunto encontrarás únicamente disponible la función de activación ReLU. Sin embargo, próximamente veremos otras funciones de activación. Si tú, estimado lector, ya conoces la ecuación que debes utilizar, puedes implementarla directamente en el código. Pero, si no es tu caso, no te preocupes. Veremos una por una al menos las funciones de activación más populares o más utilizadas. De esta manera podrás estudiar tranquilamente cada una de ellas, probando y aprendiendo cómo varían a medida que se ajustan los valores.
En cualquier caso, aprovecha los conocimientos presentados en este artículo. Y, como tarea para casa, ¿qué tal crear una cuadrícula sobre el plano cartesiano? No es algo complicado de hacer. Sin embargo, lo dejaré como ejercicio para que cada uno intente implementarlo de la mejor manera posible. La idea es poder visualizar los valores de los ejes X e Y mientras desplazas los pequeños círculos por el gráfico.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13902
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Del báscio al intermedio: Subventanas (IV)
Herramientas de trading de MQL5 (Parte 8): Panel informativo mejorado con funciones de arrastre y minimizado
Red neuronal en la práctica: Funciones de activación
Arbitraje estadístico mediante acciones cointegradas (Parte 2): Asesor Experto, pruebas retrospectivas y optimización
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso