Red neuronal en la práctica: El caso de la puerta XOR
Introducción
En el artículo anterior, Red neuronal en la práctica: La práctica lleva a la perfección, expliqué cómo podíamos agilizar el cálculo o el ajuste de los valores dentro de una simple neurona. Todo ello dentro de lo que estoy desarrollando y que más adelante mostraré cómo implementar. Aquí no pretendo, de ninguna manera, mostrarte cómo crear una red neuronal cuyo objetivo sea intentar operar con ella en el mercado financiero. El objetivo es explicar, de la forma más clara y didáctica posible, cómo aprende una red neuronal, pero, principalmente, cómo funciona entre bastidores.
En ese mismo artículo mostré una pequeña tabla que puede verse a continuación:

Esta pequeña tabla, también conocida como tabla de verdad, para quienes busquen información sobre el tema o estudien electrónica, nos muestra cómo responde un circuito a los datos aplicados a su entrada. Es decir, observando esta tabla podemos saber cómo funcionará un dispositivo electrónico. Básicamente, se muestran las configuraciones de las principales puertas lógicas que pueden implementarse. De izquierda a derecha, tenemos las puertas AND, NAND, OR, NOR, XOR y NXOR.
Muy bien, debido a algunos factores, nuestra simple neurona consigue aprender cómo funcionan las cuatro primeras puertas. Sin embargo, no consigue aprender cómo funcionan las dos últimas. Si intentas hacer que una neurona artificial aprenda cómo funcionan las puertas XOR y su inversa, que es la NXOR, no conseguirá generar los valores correctos para representar dichas puertas. ¿Y por qué ocurre esto? El motivo es que la lógica que hay detrás de las puertas XOR y NXOR es bastante más compleja que la de las demás puertas.
Mucha gente cree, erróneamente, que una neurona puede aprender a representar cualquier caso dentro de un espacio específico. Sin embargo, como tú, mi querido lector, puedes comprobar fácilmente, esto no es cierto. Hay casos en los que una red neuronal o, en el contexto actual, una neurona, no consigue entender o, mejor dicho, aprender algo. Observa que, aunque pueda parecer extraño, esto ocurre. Por esta razón estoy mostrando este caso concreto. Hay otros casos en los que resulta más o menos complicado entender el motivo por el que una red neuronal, o una neurona, no consigue aprender adecuadamente a representar los datos. Pero este caso, en el que estoy utilizando puertas lógicas, es el más sencillo de explicar. Y también el más sencillo para que todos puedan comprender lo que ocurre.
Así, creo que todos estamos de acuerdo con lo que hemos visto y expuesto. De esta forma, podemos continuar nuestro recorrido. Pues bien, en el artículo anterior dejé un pequeño desafío, en el que te pedí, mi querido lector, que intentaras de alguna manera hacer que una sola neurona aprendiera a representar una puerta XOR o su inversa, la puerta NXOR. Para explicar adecuadamente cómo lo haremos, vamos a iniciar un nuevo tema.
Un poco sobre electrónica digital
Lo que intentaré explicar aquí forma parte de los cursos de electrónica digital. Pero también se estudia en cursos de mecatrónica, donde aprendemos a crear máquinas y mecanismos que combinan partes mecánicas y electrónicas. Como la formación en mecatrónica es una de las más avanzadas, debido a que muchas veces los alumnos también necesitan aprender sobre programación, sistemas operativos, actuadores, sensores, entre otras cosas, proporciona una base muy sólida para comprender los conceptos y conocimientos necesarios para entender mecanismos inteligentes o crear interfaces hombre-máquina. Definitivamente, es una de esas áreas en las que nunca dejas de ser alumno y siempre sigues estudiando. En fin, lo primero que quiero mostrar es que la solución que buscamos no siempre está en el software. Muchas veces, está en tener conocimientos de otras áreas.
Para entender cómo vamos a manipular o forzar a la neurona para que represente una puerta XOR o su inversa, primero hay que saber cómo se construye una puerta XOR. O, mejor dicho, cómo podemos reducirla a un nivel más básico. A diferencia de lo que muchos creen, una puerta XOR no es una función simple. En realidad, es una función compuesta por varios pasos. No obstante, no voy a entrar en la parte en la que hacemos la representación utilizando componentes discretos, como transistores y otros elementos. No entraré en más detalles sobre esa parte. Es decir, utilizaré otras puertas lógicas para explicar cómo podemos resolver la cuestión de la puerta XOR y de su inversa.
Llegados a este punto, quizá estés pensando: "Pero hacer esto es una locura. ¿Por qué alguien haría algo así?" Bien, mi querido lector, si realmente quieres aprender cómo funcionan las cosas, necesitas entender muchos otros temas. No basta con esperar a que un software te dé la respuesta. Tienes que profundizar. Aquí, sin embargo, nos quedaremos solo en la superficie. Aun así, veremos qué hay detrás de lo que más adelante haremos dentro de la neurona.
Muy bien, existen varias formas de representar una puerta XOR y su inversa utilizando otras puertas lógicas. La gran cuestión es la siguiente: ¿Cómo podemos crear una puerta XOR utilizando puertas como AND, NAND, OR y NOR? Recuerda que también podemos utilizar inversores en esta configuración para crear la puerta XOR. Con esto, podemos reducir todo a puertas AND, OR e inversores, ya que NAND es una puerta AND invertida y NOR es una puerta OR invertida. Esto puede verse en la imagen del tema anterior.
Como el propósito es crear una puerta XOR o, mejor dicho, representar adecuadamente una puerta de este tipo para que la neurona pueda crear una representación de ella, podemos prescindir de llevar la simplificación al extremo y utilizar cualquier otra puerta que la neurona consiga representar. Esta es la parte que nos interesa: utilizar una puerta que nuestra simple neurona pueda representar sin errores. Si entiendes esto, podrás comprender lo que se hará aquí, en este artículo.
Bien, esta es la parte fácil. La parte complicada es que, como dije hace poco, una puerta XOR puede crearse de varias formas diferentes utilizando otras puertas. Pero, para que tú, mi querido lector, puedas entenderlo, no voy a limitarme a decir qué configuración utilizaré en este artículo. Voy a mostrarte qué configuración se utilizará. Esto puede verse en la animación siguiente.

Esta animación se está realizando en LOGISIM, un programa de código abierto escrito en JAVA. Con este programa podemos simular circuitos electrónicos en general para comprender su funcionamiento. Es un programa bastante interesante, aunque antiguo, que nos permite montar y probar distintos circuitos sin utilizar componentes caros y correr el riesgo de quemarlos. Lo que nos interesa, y lo que tú, mi querido lector, necesitas entender, es que la parte en verde claro indica que existe tensión. Es decir, tenemos un nivel lógico uno. O, para los programadores, un valor verdadero.
Las líneas en verde más oscuro representan un nivel lógico cero, es decir, un valor falso. En la entrada del circuito estamos viendo los valores de la tabla de verdad mostrada en el tema anterior. La salida, que es el círculo que unas veces aparece rojo y otras gris, es lo que queremos obtener, en este caso, como salida de una puerta XOR. Ahora presta atención: las dos entradas están conectadas a dos puertas lógicas: la superior es una NOR y la inferior, una AND. La salida de ambas puertas se dirige a otra puerta NOR, y el resultado final es precisamente la salida de esta segunda puerta NOR.
Podríamos implementar este sistema de varias maneras diferentes. Pero aquí decidí utilizar esta configuración por un motivo muy sencillo: todo se reduce a saber cómo funcionan una puerta NOR y una puerta AND. Nuestra simple neurona puede representar adecuadamente ambas puertas. Así que, si utilizamos una implementación adecuada, podemos "hacer trampa" y forzar a la neurona a representar una puerta que, por sí sola, no conseguiría representar. Es decir, podremos implementar una puerta XOR utilizando una sola neurona.
Pero espera un momento. ¿Podemos hacer este tipo de cosas? ¿No es un poco deshonesto? Bien, no existe ninguna regla que diga que no podemos hacerlo. Aunque sea una trampa y procedamos de una forma un tanto controvertida, no veo ningún motivo que justifique que no podamos mostrar cómo se hace este tipo de cosas. Así que ahora pasemos a la parte de programación.
Tonto pedazo de silicio
Muy bien, si entendiste el tema anterior, bienvenido al punto en el que vamos a demostrar que la máquina es lo que nosotros queremos que sea. No irá más allá de lo que deseamos, aunque muchos piensen lo contrario. Una red neuronal no es más que un cálculo muy bien elaborado. Y, como una computadora es simplemente una calculadora algo más potente, podemos decirle al pedazo de silicio que haga exactamente lo que queremos. Y, si no nos obedece, lo forzamos a seguir nuestras órdenes hasta que se someta y aprenda quién manda en todo esto.
En el código que se muestra a continuación podemos ver nuestra neurona primordial. Aquí vamos a utilizar la versión más sencilla, en la que no estamos optimizando en absoluto el número de entradas. Así que puedes añadir tantas entradas como necesites utilizar. Pero no olvides lo que vimos en el artículo anterior.
//+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define macroRandom (rand() / (double)SHORT_MAX) #define macroSigmoid(a) (1.0 / (1 + MathExp(-a))) //+------------------------------------------------------------------+ #define def_Fast //+------------------------------------------------------------------+ double Train[][3] { {0, 0, 0}, {0, 1, 0}, {1, 0, 0}, {1, 1, 1}, }; //+------------------------------------------------------------------+ const uint nTrain = Train.Size() / 3; const double eps = 1e-3; //+------------------------------------------------------------------+ double Cost(const double w0, const double w1, const double b) { double err; err = 0; for (uint c = 0; c < nTrain; c++) err += MathPow((macroSigmoid((Train[c][0] * w0) + (Train[c][1] * w1) + b) - Train[c][2]), 2); return err / nTrain; } //+------------------------------------------------------------------+ void OnStart() { double w0, w1, err, ew0, ew1, eb, bias; ulong count, it0, it1; Print("The Neuron - Tutor..."); MathSrand(512); w0 = (double)macroRandom; w1 = (double)macroRandom; bias = (double)macroRandom; it0 = GetTickCount(); for (count = 0; (count < ULONG_MAX) && ((err = Cost(w0, w1, bias)) > eps); count++) { ew0 = (Cost(w0 + eps, w1, bias) - err) / eps; ew1 = (Cost(w0, w1 + eps, bias) - err) / eps; eb = (Cost(w0, w1, bias + eps) - err) / eps; w0 -= (ew0 * eps); w1 -= (ew1 * eps); bias -= (eb * eps); } it1 = GetTickCount(); Print("Time: ", (it1 - it0) / 1000.0, " seconds."); PrintFormat("%I64u > w0: %.4f %.4f || w1: %.4f %.4f || b: %.4f %.4f || %.4f", count, w0, ew0, w1, ew1, bias, eb, err); Print("w0 = ", w0, " || w1 = ", w1, " || Bias = ", bias); Print("Error Weight 0: ", ew0); Print("Error Weight 1: ", ew1); Print("Error Bias: ", eb); Print("Error: ", err); Print("************************************"); } //+------------------------------------------------------------------+
Bien, esta neurona todavía no puede representar la puerta XOR ni su inversa. Pero ¿qué ocurre si la entrenamos para que aprenda a representar una puerta NOR y una puerta AND, y además le indicamos cómo conectar esas mismas puertas para representar una puerta XOR? ¿Podrá entonces este tonto pedazo de silicio entender cómo puede representarse una puerta XOR? Bien, veamos cómo hacerlo. Recuerda que lo que voy a mostrar es solo una de las muchas alternativas posibles y viables. Tú, mi querido lector, eres libre de probar otras alternativas.
Muy bien, lo primero que debemos entender es que vamos a construir una pequeña red neuronal dentro de una simple neurona. Para ello, necesitamos hacer que el código actúe como si estuviéramos haciendo una cosa cuando, en realidad, estaremos haciendo otra. Como la neurona necesita aprender a representar cada una de las puertas, cada puerta tendrá dos pesos ( w0 y w1 ) y un sesgo ( b ). Esto se debe a que tenemos dos entradas en cada una de las puertas. En la práctica, cada puerta equivaldría a una neurona. Por esta razón, tendremos dos pesos y un sesgo por cada una de las puertas que se implemente internamente. Como utilizamos tres puertas, necesitaremos cuatro pesos y dos sesgos. Pero espera un momento. Si cada puerta necesita dos pesos y un sesgo, ¿no serían seis pesos y tres sesgos la cantidad correcta? No, mi querido lector. Como una de las puertas, en este caso la NOR, se duplicará, no necesitamos volver a contarla. Puede parecer complicado, pero, en la práctica, todo es mucho más sencillo de lo que parece.
Es muy importante que tú, mi querido lector, comprendas bien esto. Si todavía no lo has entendido, revisa los artículos anteriores para saber por qué necesitamos estos pesos y sesgos. Pero, sobre todo, intenta comprender lo explicado en el tema anterior para entender cómo utilizaremos las puertas. Si te queda alguna duda al respecto, no entenderás lo que realmente ocurre dentro de la neurona.
Bien, entonces voy a asumir que tú, mi querido y estimado lector, has entendido lo que debemos hacer. Así que el código anterior se modificará hasta quedar como se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define macroRandom (rand() / (double)SHORT_MAX) 005. #define macroSigmoid(a) (1.0 / (1 + MathExp(-a))) 006. //+------------------------------------------------------------------+ 007. #define def_Fast 008. //+------------------------------------------------------------------+ 009. double _NOR[][3] { 010. {0, 0, 1}, 011. {0, 1, 0}, 012. {1, 0, 0}, 013. {1, 1, 0}, 014. }; 015. double _AND[][3] { 016. {0, 0, 0}, 017. {0, 1, 0}, 018. {1, 0, 0}, 019. {1, 1, 1}, 020. }; 021. struct st_XOR 022. { 023. double Nor_w0, Nor_w1, Nor_bias, Nor_Err, 024. And_w0, And_w1, And_bias, And_Err; 025. }_XOR; 026. //+------------------------------------------------------------------+ 027. const uint nTrain = _NOR.Size() / 3; 028. const double eps = 1e-3; 029. //+------------------------------------------------------------------+ 030. double Cost(const double w0, const double w1, const double b, const double &Train[][]) 031. { 032. double err; 033. 034. err = 0; 035. for (uint c = 0; c < nTrain; c++) 036. err += MathPow((macroSigmoid((Train[c][0] * w0) + (Train[c][1] * w1) + b) - Train[c][2]), 2); 037. 038. return err / nTrain; 039. } 040. //+------------------------------------------------------------------+ 041. void OnStart() 042. { 043. double ew0, ew1, eb; 044. ulong count, it0, it1; 045. 046. Print("The Neuron - Tutor..."); 047. MathSrand(512); 048. 049. _XOR.Nor_w0 = (double)macroRandom; 050. _XOR.Nor_w1 = (double)macroRandom; 051. _XOR.Nor_bias = (double)macroRandom; 052. 053. _XOR.And_w0 = (double)macroRandom; 054. _XOR.And_w1 = (double)macroRandom; 055. _XOR.And_bias = (double)macroRandom; 056. 057. it0 = GetTickCount(); 058. 059. for (count = 0; count < ULONG_MAX; count++) 060. { 061. _XOR.Nor_Err = Cost(_XOR.Nor_w0, _XOR.Nor_w1, _XOR.Nor_bias, _NOR); 062. _XOR.And_Err = Cost(_XOR.And_w0, _XOR.And_w1, _XOR.And_bias, _AND); 063. 064. if ((_XOR.Nor_Err < eps) && (_XOR.And_Err < eps)) 065. break; 066. 067. ew0 = ((Cost(_XOR.Nor_w0 + eps, _XOR.Nor_w1, _XOR.Nor_bias, _NOR) - _XOR.Nor_Err) / eps); 068. ew1 = ((Cost(_XOR.Nor_w0, _XOR.Nor_w1 + eps, _XOR.Nor_bias, _NOR) - _XOR.Nor_Err) / eps); 069. eb = ((Cost(_XOR.Nor_w0, _XOR.Nor_w1, _XOR.Nor_bias + eps, _NOR) - _XOR.Nor_Err) / eps); 070. 071. _XOR.Nor_w0 -= (ew0 * eps); 072. _XOR.Nor_w1 -= (ew1 * eps); 073. _XOR.Nor_bias -= (eb * eps); 074. 075. ew0 = ((Cost(_XOR.And_w0 + eps, _XOR.And_w1, _XOR.And_bias, _AND) - _XOR.And_Err) / eps); 076. ew1 = ((Cost(_XOR.And_w0, _XOR.And_w1 + eps, _XOR.And_bias, _AND) - _XOR.And_Err) / eps); 077. eb = ((Cost(_XOR.And_w0, _XOR.And_w1, _XOR.And_bias + eps, _AND) - _XOR.And_Err) / eps); 078. 079. _XOR.And_w0 -= (ew0 * eps); 080. _XOR.And_w1 -= (ew1 * eps); 081. _XOR.And_bias -= (eb * eps); 082. } 083. 084. it1 = GetTickCount(); 085. Print("Time: ", (it1 - it0) / 1000.0, " seconds."); 086. PrintFormat("Interactions: %I64u", count); 087. Print("NOR port information:"); 088. Print("w0: ", _XOR.Nor_w0, " w1: ", _XOR.Nor_w1, " bias: ", _XOR.Nor_bias, " Error: ", _XOR.Nor_Err); 089. Print("AND port information:"); 090. Print("w0: ", _XOR.And_w0, " w1: ", _XOR.And_w1, " bias: ", _XOR.And_bias, " Error: ", _XOR.And_Err); 091. 092. Print("Testing the neuron..."); 093. for (uchar p0 = 0; p0 < 2; p0++) 094. for (uchar p1 = 0; p1 < 2; p1++) 095. PrintFormat("%d XOR %d IS %f", p0, p1, macroSigmoid((_XOR.Nor_w0 * macroSigmoid((p0 * _XOR.Nor_w0) + (p1 * _XOR.Nor_w1) + _XOR.Nor_bias)) + 096. (_XOR.Nor_w1 * macroSigmoid((p0 * _XOR.And_w0) + (p1 * _XOR.And_w1) + _XOR.And_bias)) + _XOR.Nor_bias)); 097. 098. Print("************************************"); 099. } 100. //+------------------------------------------------------------------+
Y, al ejecutar este código en MetaTrader 5, podrás ver algo muy parecido a la imagen siguiente.

Observa que la representación de la puerta XOR fue validada por la neurona. Es decir, pudo, gracias a nuestra ayuda, entender cómo podía representarse una puerta XOR. Pero, al observar el código anterior, quizá tengas dudas sobre cómo se hizo realmente. Puede parecer algo extremadamente complejo y difícil de comprender. Sin embargo, si examinas el código con calma, notarás enseguida que es casi igual al código que vimos al principio de este tema. Entonces, ¿por qué contiene más código? Y, además, ¿por qué, a diferencia de los casos anteriores, no estamos introduciendo en este código la tabla de verdad de la puerta XOR para entrenar la neurona?
Bien, son preguntas válidas. Para la primera, la respuesta es que necesitamos indicarle a la neurona que aprenda a representar una puerta NOR y una puerta AND. Por esta razón, aparentemente tenemos más elementos aquí. En cuanto a la segunda pregunta, lo correcto es decir que no necesitamos entrenar la neurona con la tabla de la puerta XOR. Vamos a hacer trampa y a forzarla a crear una representación de la puerta basándose en los valores que la propia neurona genere para representar la puerta NOR y la puerta AND. Con esto, crearemos en código el equivalente del circuito digital visto en el tema anterior.
Como necesitamos separar un poco los datos de cada puerta, en la línea 21 creamos una estructura para almacenar los datos necesarios para representar una puerta XOR. Pero observa que allí solo tenemos campos para almacenar valores de tipo double. No hay ningún dato ni información que permita afirmar que realmente se creará una puerta XOR.
La función de costo, en la línea 30, recibió un argumento adicional. Su objetivo es indicar a la función de costo qué datos se utilizarán durante el entrenamiento. El resto de la función de costo permaneció sin cambios. Es decir, la neurona continúa en su forma más básica. Por lo tanto, sigue siendo capaz de hacer lo mismo que hacía antes, en caso de que quieras utilizarla fuera de esta aplicación concreta, cuyo objetivo es obtener una representación de una puerta XOR.
En la línea 41 comenzamos la parte correspondiente a esta aplicación concreta. En este punto, toda la lógica está integrada en un único procedimiento. Pero, aunque pueda parecer complicado, en realidad todo es bastante sencillo y, como he dividido un poco más el código, creo que tú, mi querido lector, podrás entender lo que ocurre.
Empezando por las líneas 49 a 51, allí inicializamos los valores de los pesos y del sesgo de la puerta NOR. Lo mismo ocurre entre las líneas 53 y 55, solo que en este caso inicializamos los valores de la puerta AND. Ten en cuenta que estamos inicializando el sistema con valores pseudoaleatorios. Esto ya ocurría antes, como puedes ver en el código del principio de este mismo tema. La diferencia es que antes solo inicializábamos los valores de una única puerta y ahora lo hacemos para dos puertas.
Muy bien, entonces, en la línea 59 entramos en el bucle for, cuyo objetivo es hacer que la neurona busque una forma de representar las puertas lógicas utilizando los datos presentes en los arrays de entrenamiento. Nuevamente, esto puede parecer complicado. Pero ya llevamos algún tiempo haciendo este mismo tipo de cosas. La diferencia respecto a las veces anteriores es que, ahora estamos entrenando la neurona para representar no una, sino dos puertas lógicas diferentes. Así, lo primero que debemos hacer es obtener el error inicial de cada una de las puertas. Esto se hace en la línea 61, donde obtenemos el error de la puerta NOR, y en la línea 62, donde obtenemos el error de la puerta AND. Como necesitamos comprobar dos valores para finalizar el bucle lo antes posible, en la línea 64 verificamos si el error está por debajo del nivel esperado. Si es así, la línea 65 hará que el bucle termine. De lo contrario, pasaremos al siguiente paso dentro del bucle.
El siguiente paso es utilizar la recta secante para buscar el punto de menor costo. Primero lo hacemos para la puerta NOR. Esto puede verse entre las líneas 67 y 73. En estas líneas calculamos exactamente lo mismo que hacíamos antes, por lo que no debería resultar difícil de comprender para quienes vienen siguiendo los artículos. Una vez realizados los cálculos para la puerta NOR, hacemos lo mismo con la puerta AND. Esto se hace entre las líneas 75 y 81.
Con esto, forzamos a la neurona a generar una representación de aquello que puede entender, que son precisamente las puertas AND y NOR. Observa que, a diferencia de lo que podía imaginarse en un principio, no necesitamos entrenar la tercera puerta. Es decir, primero comprende qué debe hacerse y, después, haz lo que se había planteado inicialmente.Entre las líneas 84 y 90 imprimimos una serie de datos para comprobar los resultados obtenidos. Recuerda que la idea que hay detrás de una red neuronal es crear una ecuación que represente los datos. El hecho de que genere algo que muchos consideran inteligente es un mero detalle. Y, finalmente, entre las líneas 92 y 96 imprimimos la representación que la neurona genera para una puerta XOR.
Entendiendo los resultados
Amigo, he entendido gran parte del código, ya que llevo estudiando tus códigos con bastante atención. Vi que, efectivamente, entrenamos una puerta NOR y una puerta AND, de modo que la neurona aprendiera a representar dichas puertas. Pero, en cuanto a esta última parte, donde imprimimos el resultado y obtenemos exactamente lo que correspondería al resultado necesario para representar una puerta XOR, amigo, no he entendido nada de lo que se hace. ¿Podrías explicar un poco mejor esta parte?
Claro que sí. Por supuesto que puedo explicar lo que está ocurriendo en esta parte final del código. Sé que, a primera vista, puede parecer algo bastante inusual y un tanto complicado. Pero quizá se deba a que esperabas encontrarte con un determinado código. Y, al observar el código utilizado para mostrar la representación de la puerta XOR, te encuentras con algo muy diferente de lo esperado. Para entenderlo con claridad, quiero que sigas la secuencia de imágenes que aparece a continuación junto con lo que ocurre en los fragmentos que se muestran a continuación. La idea es descomponer aún más el código final para que podamos hacer mucho más sencilla la explicación de lo que ocurre, mi querido lector.
Empecemos por lo siguiente: en la imagen siguiente tenemos la puerta NOR, que inicialmente recibe los datos de origen y está pintada de AZUL.

En el código, el fragmento correspondiente a esta misma puerta, pintada en la imagen anterior, también aparece destacado a continuación.
PrintFormat("%d XOR %d IS %f", p0, p1, macroSigmoid((_XOR.Nor_w0 * macroSigmoid((p0 * _XOR.Nor_w0) + (p1 * _XOR.Nor_w1) + _XOR.Nor_bias)) + (_XOR.Nor_w1 * macroSigmoid((p0 * _XOR.And_w0) + (p1 * _XOR.And_w1) + _XOR.And_bias)) + _XOR.Nor_bias));
Perfecto, observa que esta es la primera de las puertas que se está representando. Ahora vamos a ver la siguiente puerta, que es la puerta AND y aparece pintada de verde en la imagen siguiente.

En el código, esta misma puerta AND, que aparece pintada de verde en la imagen anterior, está destacada en verde en el fragmento siguiente.
PrintFormat("%d XOR %d IS %f", p0, p1, macroSigmoid((_XOR.Nor_w0 * macroSigmoid((p0 * _XOR.Nor_w0) + (p1 * _XOR.Nor_w1) + _XOR.Nor_bias)) + (_XOR.Nor_w1 * macroSigmoid((p0 * _XOR.And_w0) + (p1 * _XOR.And_w1) + _XOR.And_bias)) + _XOR.Nor_bias));
Observa que se trata del mismo fragmento. Ahora viene el gran final, que es la puerta NOR de salida, pintada de rojo en la imagen siguiente.

Como en el fragmento todo el código forma parte de esta última puerta, solo voy a destacar dónde se encuentran sus parámetros. Es decir, sus entradas son precisamente las salidas de las demás puertas. Sin embargo, necesita los pesos y el sesgo de la puerta NOR original. Y esto es precisamente lo que se destaca en el fragmento siguiente.
PrintFormat("%d XOR %d IS %f", p0, p1, macroSigmoid((_XOR.Nor_w0 * macroSigmoid((p0 * _XOR.Nor_w0) + (p1 * _XOR.Nor_w1) + _XOR.Nor_bias)) + (_XOR.Nor_w1 * macroSigmoid((p0 * _XOR.And_w0) + (p1 * _XOR.And_w1) + _XOR.And_bias)) + _XOR.Nor_bias));
Con esto, considero que la explicación está completa. Sin embargo, quiero proponerte un desafío, mi querido y estimado lector, para que compruebes por ti mismo que has entendido lo explicado en este artículo.
Un desafío para ti, lector
Hagamos lo siguiente: en el archivo adjunto tendrás acceso tanto al código que vimos en este artículo para representar la puerta XOR como al código con la respuesta al desafío. Sin embargo, quiero que seas honesto e intentes superar el desafío antes de consultar la respuesta o una posible propuesta de solución. Bien, ¿estamos de acuerdo?
Bien, si la respuesta es sí, vamos al desafío. Será algo sencillo y relacionado con lo visto en este artículo. Quiero que intentes diseñar un entrenamiento para que una sola neurona pueda generar una representación de la puerta XOR, pero no la que hemos visto hasta ahora. Quiero que observes el circuito de la animación siguiente y desarrolles una solución que utilice las puertas empleadas en ese circuito. Como muchos de ustedes probablemente no tienen conocimientos de electrónica, voy a mostrar la tabla de verdad de cada una de las puertas. De todas formas, intenta observar primero la animación, ya que muestra exactamente lo que ocurre.

La tabla de verdad que puedes utilizar se muestra en la imagen siguiente.

Un detalle: para resolver este desafío, intenta hacer algo parecido a lo que vimos en este artículo. Sin embargo, tendrás que ajustar algunas cosas de una forma un poco diferente. Ahora bien, si realmente has entendido cómo hacerlo y quieres un desafío un poco más elaborado, intenta crear una neurona que pueda hacer lo que se muestra en la animación siguiente.

En este caso, estamos utilizando únicamente puertas NAND y, aun así, el resultado final es una representación de la puerta XOR. Este sí puede ser un buen desafío para que intentes ajustar la salida de la neurona. Pero no es complicado de hacer. Solo necesitas un poco de paciencia para obtener la salida correcta.
Consideraciones finales
En este artículo intenté mostrarte, mi querido lector, que no todo es como parece. Muchas veces tendemos a pensar que las cosas son de una determinada manera cuando, en realidad, podemos acabar creyendo algo que no necesariamente es cierto. Las redes neuronales son, con diferencia, uno de los temas más interesantes en términos generales. Tanto desde el punto de vista matemático como electrónico o incluso del software. A diferencia de lo que muchos creen o afirman, las redes neuronales no son, ni mucho menos, el problema ni la solución definitiva. Son solo una rama de investigación en la que debemos seguir estudiando e informándonos sobre lo que ocurre entre bastidores.
Cuando se trata de redes neuronales, no debes creer ciegamente en nadie. De hecho, lo que he observado a lo largo de los años es que, cuanto más avanzan las investigaciones sobre las redes neuronales, más mentiras y rumores aparecen. En realidad, las redes neuronales se estudian desde hace años, por no decir décadas. Sin embargo, veo que muchos simplemente ignoran este hecho y creen que el tema ya ha alcanzado su apogeo. Aun así, todavía queda mucho por hacer. Como puedes ver hasta ahora, no existe una forma definitiva de representar cualquier tipo de modelo. Por sencillo que pueda parecer, todavía no existe una forma de representar adecuadamente todas las cosas.
Todavía hay mucho margen para la investigación, las mejoras y los estudios sobre las redes neuronales. Por muy avanzadas que puedan parecer las redes neuronales, capaces de generar imágenes o incluso textos en general, todavía carecen de algo esencial: CRITERIO Y SENTIDO COMÚN. De cualquier forma, aún tenemos muchas otras cosas que ver sobre este tema. Así que nos vemos en el próximo artículo.| Archivo MQ5 | Descripción |
|---|---|
| Adjunto | Demostración básica |
| Desafío 01 | Demostración básica |
| Desafío 02 | Demostración básica |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13782
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: Indicadores técnicos (I)
Del básico al intermedio: Sobrecarga de operadores (VI)
Particularidades del trabajo con números del tipo double en MQL4
Algoritmo de optimización del dingo — Dingo Optimization Algorithm (DOA)
- 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