Português
preview
Red neuronal en la práctica: Funciones de activación

Red neuronal en la práctica: Funciones de activación

MetaTrader 5 — Aprendizaje automático |
17 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior , desarrollamos una aplicación con la que podíamos visualizar el gráfico de la función de activación junto con su correspondiente derivada. Sin embargo, lo que vimos allí es solo una base para algo un poco más elaborado, que será el tema principal de este artículo.

Sé que muchos pueden considerar innecesario comprender el gráfico de una función de activación y también el de su derivada, y que podríamos profundizar directamente en el tema dejando este tipo de cuestiones de lado. Sin embargo, es mucho más sencillo entender por qué la función sigmoide no puede utilizarse en redes muy profundas si comprendes su gráfico.

Así que aquí nos centraremos precisamente en eso. Seleccionaremos algunas funciones de activación y veremos cómo podríamos modificar el código del artículo anterior para visualizar gráficamente tanto la función de activación como su derivada.


Una nueva aplicación

Toda la explicación del artículo anterior sigue siendo válida. Además de lo visto allí, aquí veremos el nuevo código. También daremos una breve explicación de cada una de las funciones seleccionadas. Antes de empezar, quiero dejarte un pequeño recordatorio, mi amigo lector. Existe una enorme cantidad de funciones de activación. Algunas son más populares que otras. Sin embargo, cada una tiene su finalidad y un campo de aplicación específico, con el propósito de optimizar de algún modo la velocidad de entrenamiento. Más adelante veremos cómo se utilizan algunas de estas funciones en la práctica, para que puedas comprobar cómo un simple cambio en la elección puede marcar una gran diferencia en los resultados que podemos obtener durante el entrenamiento de la red. De todos modos, ahora nos centraremos únicamente en la parte realmente divertida del sistema, en la que utilizaremos las funciones de manera completamente aislada. Así será más sencillo entender cuál podría ser la mejor elección en determinadas situaciones. En cualquier caso, no te preocupes por esto ahora. Aunque la parte matemática sea bastante densa y complicada, intentaremos simplificarla mucho al final. Quiero que seas realmente capaz de entender cómo funciona una red neuronal. El objetivo no es decirte: «Haz esto. No hagas aquello». Ese tipo de indicaciones no aporta nada a tus conocimientos. Solo crea una falsa ilusión de que entiendes algo que, en realidad, no comprendes.

Muy bien, antes de ver cada una de las funciones seleccionadas, vamos a echar un vistazo rápido al código. Ha sufrido algunos pequeños cambios y, en mi opinión, algunos de ellos merecen una explicación. Lo mostraré completo a continuación. Así será mucho más sencillo explicar los nuevos detalles añadidos. Así que no te asustes a primera vista. Este código es mucho más sencillo de lo que parece.
001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property indicator_chart_window
004. #property indicator_plots 0
005. //+------------------------------------------------------------------+
006. #include <Canvas\Canvas.mqh>
007. //+------------------------------------------------------------------+
008. enum eFnActivate {
009.                     Sigmoid, 
010.                     Tangh, 
011.                     ReLU,
012.                     eLU, 
013.                     SoftSign, 
014.                     SoftPlus, 
015.                 };
016. //+------------------------------------------------------------------+
017. input eFnActivate   user_00 = Sigmoid;   //Activation function
018. input double        user_01 = 10.0;      //Limits
019. input double        user_02 = 0.1;       //Granulation
020. input double        user_03 = 9.0;       //Zoom Y axis
021. //+------------------------------------------------------------------+
022. CCanvas *canvas;
023. //+------------------------------------------------------------------+
024. struct st_00
025. {
026.     int     x,
027.             y,
028.             maxX,
029.             maxY;
030.     double  Position,
031.             dx, fx;
032. }global;
033. //+------------------------------------------------------------------+
034. void PlotTextPosition(void)
035. {
036.     uint w, h;
037.     string sz0;
038. 
039.     sz0 = StringFormat("%s Position: [%.1f] <<  fx: %.8f    dx: %.8f  >>", EnumToString(user_00), global.Position, global.fx, global.dx);
040.     TextGetSize(sz0, w, h);
041.     (*canvas).TextOut(global.x - (w / 2), global.maxY + 5, sz0, ColorToARGB(clrBlack)); 
042. }
043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+
112. void UpdateGraphics(int direct)
113. {
114.     double value = global.Position + user_02 * direct;
115. 
116.     global.Position = MathAbs(value) <= user_01 ? value : global.Position;
117. 
118.     (*canvas).Erase(ColorToARGB(clrWhite, 255));
119. 
120.     (*canvas).LineVertical(global.x, 0, global.maxY, ColorToARGB(clrRoyalBlue, 255));
121.     (*canvas).LineHorizontal(0, global.maxX, global.y, ColorToARGB(clrRoyalBlue, 255));
122. 
123.     Function_Curve();
124.     PlotTextPosition();
125. 
126.     (*canvas).Update(true);
127. }
128. //+------------------------------------------------------------------+
129. void Resize(void)
130. {
131.     uint w, h;
132.    
133.     global.maxX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
134.     global.maxY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
135. 
136.     if (canvas != NULL)
137.     {
138.         (*canvas).Destroy();
139.         delete canvas;
140.     }
141.     canvas = new CCanvas;
142.     (*canvas).CreateBitmapLabel("BL", 0, 0, global.maxX, global.maxY, COLOR_FORMAT_ARGB_NORMALIZE);
143.     global.x = global.maxX / 2;
144.     global.y = global.maxY / 2;
145.     TextGetSize("M", w, h);
146.     global.maxY -= (int)(h * 2);
147. }
148. //+------------------------------------------------------------------+
149. int OnInit()
150. {        
151.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
152.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
153.     ZeroMemory(global);
154.     canvas = NULL;
155. 
156.     return INIT_SUCCEEDED;
157. }
158. //+------------------------------------------------------------------+
159. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
160. {
161.     return rates_total;
162. }
163. //+------------------------------------------------------------------+
164. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
165. {
166.     int direct = 0;
167. 
168.     switch (id)
169.     {
170.         case CHARTEVENT_CHART_CHANGE:
171.             Resize();
172.             break;
173.         case CHARTEVENT_KEYDOWN:
174.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT)) direct = -1;
175.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT)) direct = 1;
176.             break;
177.     }
178.     UpdateGraphics(direct);
179. }
180. //+------------------------------------------------------------------+
181. void OnDeinit(const int reason)
182. {
183.     (*canvas).Destroy();
184.     delete canvas;
185.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, true);
186.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, true);
187. }
188. //+------------------------------------------------------------------+

Vaya. Este código sí parece bastante más complicado. Tranquilo. Voy a explicar cada parte para que puedas entenderlo. En realidad, es más sencillo que el código anterior. En primer lugar, se añadió una enumeración en la línea ocho. Su finalidad es permitirnos seleccionar la ecuación deseada. Cada uno de los nombres incluidos en esta enumeración corresponde, en realidad, al nombre de una ecuación.

Estas seis ecuaciones se utilizan en funciones de activación. Como ya mencioné, existen otras. Muchas de ellas son pequeñas variaciones de las que aparecen en esta lista. Si lo deseas, puedes añadirlas sin ningún problema. Solo hay que tener una precaución: colocar la expresión matemática en el lugar correcto y de la forma adecuada. Si haces esto, podrás trazar el gráfico de cualquier ecuación matemática que quieras. Y todo ello de forma bastante sencilla y sin mayores complicaciones.

Bien, ahora que ya tenemos los nombres de las ecuaciones, lo siguiente que se modificó fue la forma en que el usuario elige la ecuación que se va a representar gráficamente. Antes teníamos que compilar el código cada vez que hacíamos un cambio, ya fuera en la ecuación o en los parámetros utilizados. Sin embargo, como mencioné al principio, la idea aquí es ofrecer una mayor interactividad al usuario. Así, no tendrás que estar compilando el código continuamente. Para conseguirlo, se añadieron cuatro inputs. Antes teníamos dos parámetros definidos: uno para indicar el intervalo de valores y otro para establecer el paso utilizado para generar la curva. Ahora, además de mantener el acceso a esos mismos ajustes, añadí un input para indicar qué ecuación quieres representar y otro para definir el factor de zoom del gráfico. Esto se debe a que, en algunos casos, los valores del eje Y quedan muy próximos a 1.0. Cuando esto ocurre, conviene permitir que el usuario modifique la escala del eje Y. De esta forma, el gráfico cambiará de escala y la curva trazada podrá analizarse con mayor facilidad.

Muy bien, de forma predeterminada, el gráfico se trazará utilizando la ecuación de la función sigmoide, con valores comprendidos entre menos diez y diez, y un paso de una décima. Esto garantiza una curva bastante suave. Además, se utilizará un factor de escala de nueve en el eje Y. Con estos parámetros, podrás ver el siguiente gráfico en MetaTrader 5.

Observa que se han cambiado algunas cosas en la presentación. En realidad, el cambio se realizó únicamente en la parte correspondiente al texto mostrado. Antes solo mostrábamos la posición en el eje X. Ahora, además de ese valor, también mostramos el nombre de la ecuación, su valor en el eje Y en función del valor del eje X, así como el valor de la derivada en esa misma posición del eje X. Y todo esto se consiguió modificando apenas unas líneas de código. Básicamente, los valores de fx y dx que puedes ver en la imagen se muestran allí mediante la línea treinta y nueve del código. Observa que, en esta línea, accedemos a la enumeración para obtener el nombre de la función. También accedemos a los valores definidos en la línea treinta y uno. Estas dos variables de la línea treinta y uno no existían antes.

Bien, como la mayor parte del código permaneció intacta, las únicas modificaciones fueron la incorporación de las líneas 151, 152, 185 y 186, cuyo objetivo es mantener la ventana del gráfico lo más limpia posible. Como esto es sencillo de entender, podemos centrar nuestra atención en el punto principal: el procedimiento de la línea cuarenta y cuatro.

Este procedimiento sufrió pequeños cambios en su estructura. Todos ellos buscan que el código sea mucho más sencillo de programar y modificar si en el futuro fuera necesario realizar cambios en el código, incluso para quien esté empezando a programar. Este nuevo código se diseñó para que puedas incorporar cualquier ecuación que quieras sin demasiadas dificultades. Para verlo con más claridad, vamos a aislar el código y analizarlo con calma, centrándonos únicamente en el fragmento que se muestra a continuación.

043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+

Si comparas este código con el que vimos en el artículo anterior, notarás claramente que aquí aparecen dos variables nuevas. Se encuentran en la línea cuarenta y ocho. Incluso las variables fx y dx han cambiado con respecto a su finalidad inicial. Ahora ambas se utilizarán para almacenar temporalmente los valores calculados.

Puede parecer complicado, pero, si nunca has programado nada antes, presta atención, porque ahora vas a aprender a hacerlo en unos pocos pasos.

Supongamos que quieres añadir una nueva función a esta aplicación para poder representar gráficamente tanto el resultado de la ecuación como el de su derivada. ¿Cómo deberías hacerlo? Bien, lo primero es ir a la línea ocho, donde tenemos los nombres de las funciones, y añadir el nombre de tu nueva función. Recuerda que NO PODRÁS utilizar espacios dentro del nombre. Si necesitas separar palabras, utiliza el carácter de guion bajo ( _ ).

Una vez hecho esto, ya podrás seleccionar la ecuación cuando utilices la aplicación compilada en MetaTrader 5. Sin embargo, todavía no verás ningún gráfico. Para poder trazarlo, simplemente tendrás que ir al procedimiento que aparece en el fragmento anterior y hacer lo siguiente: en la línea sesenta tenemos una instrucción switch. Esta seleccionará qué código debe ejecutarse en función del valor correspondiente a la ecuación seleccionada por el usuario.

Así, en la línea sesenta y uno, después de la llave de apertura, deberás pulsar ENTER y escribir case, dejar un espacio y, justo después, escribir el nombre de tu ecuación seguido de dos puntos ( : ). Vuelve a pulsar ENTER y escribe break seguido de punto y coma ( ; ). Con esto crearás una estructura muy parecida a la que aparece en el código. Ahora viene la parte divertida. Entre los dos puntos ( : ) y la palabra break, deberás escribir algo muy parecido a lo que aparece en cada una de las estructuras comprendidas entre las líneas sesenta y dos y ochenta y seis. Observa que tenemos fx = algo y dx = otra cosa. ¿Y qué significa esto? Bien, en fx debes colocar la ecuación de la función y, en dx, la derivada de la ecuación utilizada en fx. Y eso es todo lo que necesitas hacer. El resto lo realiza el propio código o se hace mientras lo utilizas en MetaTrader 5. Si no has entendido cómo trabajar con la instrucción switch, consulta este otro artículo mío, . En ese artículo explico en detalle cómo trabajar con la instrucción switch.

Bien, una vez entendida esta parte más sencilla, podemos pasar a los siguientes temas, donde veremos cada una de las ecuaciones que se implementarán de forma predeterminada.


Función de activación sigmoide

Esta función ya se trató en otro artículo, donde mostré la necesidad de utilizar una función de activación para poder salir de un punto de estancamiento. Consulta los artículos anteriores para entender mejor este tema. Pues bien, cuando añadimos el indicador al gráfico y seleccionamos la sigmoide, podremos interactuar con el gráfico como se muestra en la siguiente animación.

Aquí seguimos las mismas reglas explicadas en el artículo anterior. Así que, si observas el código de la línea sesenta y tres, verás cuál es la ecuación utilizada. Esa misma ecuación aparece en notación matemática a continuación.

Su derivada, definida en la línea sesenta y cuatro, se representa mediante la ecuación que se muestra a continuación.

Ahora bien, si te limitas a observar estas expresiones matemáticas, quizá no notes cuánto reduce los valores la derivada de la sigmoide. Esto resulta, en cierta medida, bastante inconveniente, ya que tiene consecuencias importantes cuando queremos utilizar esta función en una red más profunda. Debemos tener cierto cuidado al decidir en qué punto de la red utilizaremos la sigmoide. Si la empleas de forma indiscriminada, llegará un momento en que impedirás que la red continúe propagando los datos. Esto se entenderá mejor más adelante. Quien tenga algunos conocimientos de matemáticas y derivadas probablemente ya estará pensando en el siguiente tema: REGLA DE LA CADENA. Ese es el nombre del problema. Sin embargo, solo nos afecta realmente cuando utilizamos las derivadas y únicamente durante la retropropagación. Como ya mencioné, veremos todo esto con más detalle más adelante.

Aun conociendo este problema, la sigmoide es una función extremadamente útil porque normaliza los valores de salida del perceptrón para mantenerlos entre cero y uno. Además, se utiliza mucho cuando queremos emplear un modelo con el que pretendemos predecir la probabilidad de algo a partir del valor de salida de la red neuronal. Como el valor de salida siempre se encuentra entre cero y uno, resulta perfecto para convertir los datos en porcentajes. La sigmoide también ofrece otra gran ventaja, siempre que se utilice con cuidado: permite evitar cambios bruscos durante el descenso del gradiente. Esto se debe a que es una función diferenciable, lo que significa que podemos determinar la pendiente de la curva utilizando dos puntos cualesquiera.

Sin embargo, aunque haya mencionado todo esto, por ahora solo debes observar y comprender qué ocurre con cada valor introducido en la ecuación y qué resultado produce ese mismo valor al aplicarlo a la derivada. Muy bien, con esto ya tenemos algo sobre lo que reflexionar acerca de la sigmoide. Podemos pasar a la siguiente función de la lista.


Función de activación tangente hiperbólica

Esta función de activación intenta resolver uno de los problemas de la sigmoide: que no permite valores negativos. Además, en algunos casos, el hecho de que la sigmoide esté limitada a valores comprendidos entre cero y uno supone un inconveniente adicional. Esto se aprecia cuando sus valores se aproximan a cero. El efecto puede resultar, en muchas ocasiones, devastador en una red de perceptrones, ya que prácticamente «mata» algunos de los pesos utilizados, haciendo que terminen ignorándose por completo, cuando en realidad podrían ayudar a clasificar los nuevos datos que entran en la red.

Aunque muchas redes de perceptrones se construyen únicamente para adquirir determinados conocimientos, algunas continúan recibiendo nuevos datos con el paso del tiempo. Cuando esto ocurre, una sigmoide puede terminar afectando negativamente a algún peso específico de la red. Y, si el peso afectado tiene cierto grado de relevancia para los nuevos datos que están entrando, parte del aprendizaje puede volverse bastante más difícil.

Bien, al seleccionar la función tangente hiperbólica en la aplicación, veremos la siguiente animación.

En cuanto a la ecuación, existen pequeñas variaciones en su formulación. Una de ellas es la que aparece en el código, en la línea sesenta y siete, y es la misma que se muestra a continuación.

También existe otra forma de expresar la misma ecuación. Puede verse a continuación.

En la ecuación anterior puedes ver claramente que es posible utilizar ecuaciones trigonométricas en una red de perceptrones, al contrario de lo que muchos creen. En ocasiones, utilizar ecuaciones trigonométricas aporta muchos más beneficios de los que podrías imaginar, ya que pueden desarrollarse utilizando relaciones trigonométricas bien conocidas. Sin embargo, el ejemplo anterior era solo una curiosidad. Otra forma de representar esta misma expresión se muestra a continuación.

A pesar de estas variaciones en la forma de escribir la ecuación, la parte correspondiente a su derivada no cambia demasiado. Aun así, tenemos algunas alternativas para expresarla. En el código puedes ver la derivada en la línea sesenta y ocho, donde se utiliza la ecuación que se muestra a continuación.

También podemos utilizar una ecuación como la que aparece a continuación, que igualmente permite obtener la derivada.

Y, como no podía faltar, también existe la forma trigonométrica que se muestra a continuación.

Claro que todas estas expresiones son solo una pequeña muestra de las distintas formas de representar lo mismo. Es decir, no debes aferrarte a fórmulas o expresiones que encuentres en algún lugar solo porque alguien haya dicho que esa es la mejor alternativa. Como ya mencioné, la humanidad no sabe exactamente cómo construir una red de perceptrones genérica. Lo único que hemos conseguido comprender es cómo crear una red que se adapte de una forma más o menos adecuada a un determinado tipo de problema.

Bien, continuemos, porque todavía quedan algunas cosas por mostrar en este artículo.


Función de activación ReLU

Esta función ya la vimos en el artículo anterior. Así que aquí pasaremos rápidamente por ella. Al seleccionarla, aparecerá el siguiente gráfico, con el que podremos interactuar.

La ecuación de la línea setenta y dos puede verse en la expresión que se muestra a continuación.

La derivada de la línea setenta y tres se representa mediante la expresión que aparece a continuación.

Aunque esta función ya se mostró en el artículo anterior, vuelve a aparecer aquí por un motivo muy sencillo: es, con toda seguridad, la función de activación que cuenta con el mayor número de variaciones. Personalmente, imagino que debe de ser la favorita de todos, superando incluso a la sigmoide. La sigmoide no tiene tantas variaciones de su expresión original. ReLU, en cambio, ha dado lugar a muchas otras funciones que, de una forma u otra, incorporan algún componente de ReLU en su formulación. Entre todas las que puedes encontrar, existe una que también se utiliza en cálculos de ingeniería. Por esta razón, la seleccioné y será la que veremos en el siguiente tema.


Función de activación eLU

Esta es una de las muchas variaciones que existen de la función ReLU. Se mencionó en el tema anterior precisamente para mostrar su expresión matemática, para que, si nunca has oído hablar de variaciones de una expresión matemática, puedas comparar una de ellas con la expresión original, por así decirlo.

Cuando selecciones eLU, verás algo parecido a la siguiente animación.

Observa que su gráfico es diferente del de la ReLU original. Esto se debe a una pequeña variación en la forma de calcular los valores. A continuación puedes ver la expresión utilizada en la línea setenta y seis del código.

Vaya, qué expresión tan fea. ¿Estás seguro de que se escribe así? Sí, mi querido lector. Así se representa matemáticamente la expresión implementada en la línea setenta y seis. Sé que parece algo espantoso e intimidante. Sin embargo, esto se debe a la propia forma de representar ciertas cosas mediante notación matemática. Antes de continuar, veamos la expresión de la derivada. Se encuentra en la línea setenta y siete y su forma matemática se muestra a continuación.

Por el amor de DIOS, habrá que llamar a un exorcista para estas dos expresiones. Qué cosa tan aterradora. Bien, independientemente de lo que puedas estar pensando y de lo mucho que puedan asustarte estas expresiones, en ambas puedes observar algo en común: una letra griega, ALFA. Esta aparece en la línea setenta y cinco del código. El objetivo de este alfa es modificar la curvatura de la ecuación. Dependiendo del valor que le asignes, puedes obtener una curva más cerrada o más abierta. O, dicho de otro modo, el radio del arco que puedes ver en el gráfico puede hacerse algo menor o mayor. Esto genera una ligera diferencia en los valores de salida del perceptrón. Ahora bien, al observar ambas expresiones, puedes notar que, a diferencia de la ReLU original, esta eLU no tiene problemas con el cero, ya que la propia ecuación puede manejar el valor cero en la derivada.

Muy bien, ya casi hemos terminado. Quedan otras dos ecuaciones que se seleccionaron para incluirlas en este artículo y también en el código de demostración. Así que pasemos a la penúltima ecuación, que veremos en el siguiente tema.


Función de activación SoftSign

Esta es una función muy interesante y se utiliza ampliamente en algunos tipos de redes de perceptrones. Básicamente, podrás verla en redes de reconocimiento de voz, escritura e incluso en otras aplicaciones donde necesitamos que el descenso del gradiente no pierda eficacia demasiado rápido. A continuación puedes ver la animación de esta función.

El código de la línea ochenta implementa exactamente lo que se muestra en la siguiente expresión.

La derivada de la línea ochenta y uno se representa mediante la expresión que aparece a continuación.

Si la observas con calma, notarás que SoftSign se encuentra en un punto intermedio entre la función sigmoide y la tangente hiperbólica. Sin embargo, en determinadas situaciones puede resultar mucho más adecuada que ambas. Dependiendo del problema que quieras resolver, puede ayudar al gradiente a buscar un mínimo de forma algo más eficiente que la sigmoide o la tangente hiperbólica. Además, por su forma de cálculo, esta función puede evaluarse más rápido en redes más profundas. Y esto, en algunos casos, supone una diferencia importante durante el entrenamiento. Los modelos que utilizan TensorFlow emplean preferentemente esta función en lugar de la sigmoide y la tangente hiperbólica, precisamente por este factor durante el entrenamiento del modelo.

Para terminar, pasemos a la última función seleccionada. La veremos en el siguiente tema.


Función de activación SoftPlus

Esta función se basa en ReLU. También es una variación de la forma en que se calcula y utiliza ReLU. Al seleccionarla en la aplicación de demostración, verás la animación que se muestra a continuación.

Puedes observar claramente que suaviza bastante la transición entre valores negativos y positivos, siguiendo la misma idea de la ReLU original. Sin embargo, a diferencia de la ReLU original y de eLU, que vimos hace poco, SoftPlus presenta un comportamiento mucho más suave y gradual. Es decir, a partir de un determinado valor, la transición entre cero y uno se vuelve mucho más suave. Esto difiere de ReLU, que cambia bruscamente hacia cero o uno simplemente en función de si los valores son negativos o positivos.

El código correspondiente a esta función puede verse en la línea ochenta y cuatro y representa la expresión que se muestra a continuación.

La derivada, por su parte, se encuentra en la línea ochenta y cinco, y su expresión es la que aparece a continuación.

Como SoftPlus persigue básicamente los mismos objetivos que ReLU, en algunos casos puedes probar a utilizarla en una red neuronal profunda. Esto se debe a que la variación del descenso del gradiente al utilizar SoftPlus será mucho más suave que si utilizáramos ReLU en la misma situación. Sin embargo, como ya sabemos, no existe una regla que determine el uso de una función específica. Todo depende de cada caso concreto.


Consideraciones finales

Confieso que el resultado final obtenido en este artículo terminó sorprendiéndome, ya que resultó mucho más interactivo. No necesitas realizar grandes cambios en el código para añadir nuevas funciones con las que estudiar posibles modelos matemáticos. Todo lo que tendrás que hacer es añadir el nombre de la ecuación y, después, su expresión matemática.

Así, en este artículo, además de ver cómo distintas funciones de activación tienen diferentes implicaciones en la salida del perceptrón, también aprendiste que pequeñas variaciones en la forma de realizar los cálculos pueden influir enormemente en la elección de la función de activación. Recuerda que no todos los sistemas de perceptrones utilizan funciones como las que se mostraron aquí. Sin embargo, ese será un tema para otro momento.

Espero que este artículo haya despertado tu curiosidad y tu interés por las matemáticas que hay detrás de las redes de perceptrones. Aunque pueda parecer algo muy complicado, una red de perceptrones, o cualquier otro tipo de red neuronal, no es más que esto: expresiones matemáticas que programadores y desarrolladores utilizan de una manera bastante inteligente. En ningún caso se trata de una aplicación mágica capaz de comprender algo que ningún otro ser humano haya conseguido entender o concebir. Así que estudia con calma lo que hemos visto aquí y nos vemos en el próximo artículo.









Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13909

Archivos adjuntos |
Anexo.mq5 (6.26 KB)
Del básico al intermedio: Objetos y subventanas (I) Del básico al intermedio: Objetos y subventanas (I)
El artículo detalla la creación y el posicionamiento del objeto OBJ_CHART dentro de subventanas, destacando las diferencias entre la ventana principal y las subventanas. Muestra cómo integrar indicadores mediante recursos (#resource, ChartIndicatorAdd) e identificar la subventana correcta a partir del nombre corto del indicador. El resultado es un código más estable, portátil y fácil de reutilizar.
Del báscio al intermedio: Subventanas (IV) Del báscio al intermedio: Subventanas (IV)
En este artículo veremos que no todo es como muchos piensan al principio. Una de las cosas más interesantes de la programación es que podemos garantizar que las cosas siempre sean como las hemos planeado. Así que lean este artículo con atención para aprender algunos de los conceptos más confusos relacionados con el uso de subventanas. Si entiendes lo que se explicará aquí, podrás comprender muchas de las cosas que haremos más adelante.
Red neuronal en la práctica: Gradiente Red neuronal en la práctica: Gradiente
El artículo explica por qué y cómo se utiliza el gradiente durante el entrenamiento de un perceptrón, partiendo del error cuadrático mínimo y de la regla de la cadena para obtener las derivadas parciales. Mostramos la implementación del cálculo del gradiente en la clase C_Neuron en MQL5 y lo validamos con ejemplos de 1 y 2 entradas. Aprenderás a ajustar los pesos y el sesgo mediante descenso de gradiente y a prepararte para la propagación hacia adelante y la retropropagación.
Red neuronal en la práctica: Gráfico de la Rectifier Red neuronal en la práctica: Gráfico de la Rectifier
Construimos, íntegramente en MQL5, un indicador para representar en el gráfico una función de activación y su derivada, tomando la ReLU como ejemplo. Explicamos el impacto de las derivadas en la elección de la activación y las precauciones necesarias en los puntos no diferenciables. El lector puede visualizar las curvas de forma interactiva y obtiene una base práctica para decidir cuándo utilizar o no determinadas funciones de activación.