Red neuronal en la práctica: Funciones de activación
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.
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.
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.

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.
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.
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.



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.
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.
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.


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.



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.



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.
Función de activación SoftSign



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.
Función de activación SoftPlus

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.


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.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13909
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ásico al intermedio: Objetos y subventanas (I)
Del báscio al intermedio: Subventanas (IV)
Red neuronal en la práctica: Gradiente
Red neuronal en la práctica: Gráfico de la Rectifier
- 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