preview
Rede neural na prática: O caso da porta XOR

Rede neural na prática: O caso da porta XOR

MetaTrader 5Aprendizado de máquina |
19 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Rede neural na prática: A prática leva a perfeição, expliquei como poderíamos fazer para agilizar, o processo de cálculo, ou ajuste dos valores dentro de um singelo neurônio. Porém tudo feito dentro do que está sendo desenvolvido e que que planejo mostrar como implementar. Aqui não tenho de forma ou maneira alguma, intuito de mostrar como você pode criar uma rede neural, cujo objetivo será para tentar operar com ela no mercado financeiro. O objetivo aqui, é explicar da forma mais clara e didática possível como uma rede neural consegue aprender. Mas principalmente como ela funciona por debaixo dos panos.

Naquele mesmo artigo anterior, mostrei uma pequena tabela que pode ser vista logo abaixo:

Esta pequena tabela, que também é conhecida como tabela verdade. Isto para quem procurar, ou vier a estudar eletrônica. Nos mostra como um circuito responde aos dados informados em sua entrada. Ou seja, olhando esta tabela, podemos saber como um dispositivo eletrônico funcionará. Basicamente ali temos as configurações das principais portas lógicas que podem ser criadas. Da esquerda para a direita, temos a porta AND, NAND, OR, NOR, XOR e NXOR.

Muito bem, devido a alguns fatores, nosso singelo neurônio, consegue aprender, como as quatro primeiras portas funcionam. Porém não consegue aprender como as duas últimas funcionam. Se você tentar fazer com que um neurônio artificial, aprenda como as portas XOR e sua inversa, que é a NXOR, funcionam. Ele não conseguirá gerar os valores corretos para representar tais portas. E por que isto acontece? O motivo é que a lógica por debaixo das portas XOR e NXOR são bem mais complicadas do que as demais portas.

Muita gente, acredita, e de forma errônea, de que um neurônio pode aprender como representar qualquer caso dentro de uma grade específica. Porém, como você, meu caro leitor, pode facilmente comprovar, isto não é verdade. Existem casos em que uma rede neural, ou no momento atual, um neurônio, não consegue entender, ou melhor dizendo, aprender sobre algo. Vejam que apesar de parecer estranho, isto de fato acontece. Por isto estou mostrando, este caso específico. Existem outros que são mais ou menos complicados de entender o motivo pelo qual a rede neural, ou neurônio, não conseguem aprender adequadamente como representar os dados. Mas está em que estou usando portas lógicas é a mais simples de explicar. E igualmente a mais simples, para que todos consigam compreender o que está ocorrendo.

Assim acredito que todos concordam com o que está sendo visto e mostrado. Desta forma podemos continuar nossa trajetória. Pois bem, no artigo anterior deixei um pequeno desafio. Onde pedi para que você, meu caro leitor, tenta-se de alguma forma fazer com que um único neurônio, conseguisse aprender como representar uma porta XOR ou sua inversa, a porta NXOR. Para explicar adequadamente como faremos isto. Vamos iniciar um novo tópico.


Um pouco sobre eletrônica digital

O que irei tentar explicar aqui, é parte integrante de cursos de eletrônica digital. Mas também faz parte de cursos de mecatrônica, onde estudamos como criar máquinas e mecanismos, que tem tanto partes mecânicas como eletrônicas. Mas como o curso de mecatrônica é um dos mais avançados devido ao fato de muitas vezes, os alunos também precisarem aprender sobre programação, sistemas operacionais, atuadores, sensores entre outras coisas. Ele de fato nos dá muita base sobre conceitos e conhecimentos necessários para entender mecanismos inteligentes, ou criação de interfaces homem-máquina. Definitivamente é uma das áreas em que você nunca deixa de ser um aluno e de estar sempre estudando. Mas enfim, o que quero mostrar em primeiro lugar, é que nem sempre a solução procurada está no software. Muitas das vezes ela está em saber coisas de outras áreas.

Para entender como iremos manipular ou forçar o neurônio a representar uma porta XOR ou sua inversa. Primeiro é preciso saber como uma porta XOR é criada. Ou melhor dizendo, como podemos reduzir ela a um nível mais básico. Pois diferente do que muitos acreditam, uma porta XOR não é uma função simples. Ela na verdade é uma função composta por diversos outros passos. Porém não vou entrar na parte, onde fazemos a representação usando componentes discretos, como transistores e outros elementos. Irei me manter na superfície da coisa toda. Ou seja, irei usar outras portas lógicas para explicar como podemos resolver a questão da porta XOR e de sua inversa.

Ok, você neste ponto pode estar pensando: "Mas fazer isto é loucura. Por que alguém faria tal coisa?" Bem, meu caro leitor, se você de fato deseja aprender como as coisas funcionam, precisa entender sobre diversos outros assuntos. Não é simplesmente esperar que um software lhe diga a resposta. Precisa ir a fundo. Mas aqui vamos ficar apenas na superfície. Porém iremos olhar o que existe por traz do que será feito depois, dentro do neurônio.

Muito bem, existem diversas maneiras de representar uma porta XOR e sua inversa, usando para isto, outras portas lógicas. A grande questão é: Como usando portas como AND, NAND, OR e NOR podemos criar uma porta XOR? Lembrando que inversores também podem ser usados nesta configuração, a fim de criar a porta XOR. Com isto podemos resumir a coisa toda a apenas portas AND, OR e inversores. Já que a NAND é uma porta AND invertida e a porta NOR é uma porta OR invertida. Isto pode ser visto na imagem no tópico anterior.

Mas como o propósito é criar uma porta XOR, ou melhor representar de maneira adequada uma porta deste tipo. Isto para que o neurônio consiga criar uma representação da mesma. Podemos abrir mão de um resumo extremo, e usar qualquer outra porta que o neurônio consiga representar. Esta é a parte que nos interessa: Usar uma porta da qual o nosso singelo neurônio, consiga representar sem falhas. Se você conseguir entender isto, conseguirá entender o que será feito aqui, neste artigo.

Legal, esta é a parte fácil. A parte complicada é que, como eu disse a pouco, uma porta XOR pode ser criada de diversas forma diferentes. Usando outras portas para isto. Mas para que você meu caro leitor, consiga entender, não irei simplesmente dizer qual configuração usarei neste artigo. Vou mostrar para você, qual configuração será usada. Isto é visto na animação logo abaixo.

Esta animação está sendo feita no programa LOGISIM, que se trata de um programa de código aberto escrito em JAVA. Neste programa podemos simular circuitos eletrônicos de uma forma geral, a fim de compreender seu funcionamento. É um programa bastante interessante, apesar de antigo, que nos permite criar muitas coisas para fins de testar sem usar componentes caros, correndo o risco de muitas das vezes os queimar. O que nos interessa, e que você meu caro leitor precisa entender é que a parte em verde claro é onde existe tensão. Ou seja, temos um nível lógico um. Ou para programadores, um verdadeiro.

As linhas em verde mais escuro, representam um nível lógico zero, ou seja, um falso. Na entrada do circuito estamos vendo os valores da tabela verdade, mostrada no tópico anterior. Já a saída que é o círculo que hora fica vermelho, hora cinza. É o que queremos, no caso, como sendo a saída de uma porta XOR. Agora preste atenção: As duas entradas são ligadas a duas portas lógicas. Sendo que a superior é uma NOR e a inferior uma AND. A saída de ambas as portas é direcionada para uma outra porta NOR, sendo que o resultado final é justamente a saída desta segunda porta NOR.

Poderíamos implementar este sistema de diversas maneiras diferentes. Mas aqui, decidi usar esta configuração por um simples motivo: Resumimos tudo ao conhecimento sobre como uma porta NOR e uma porta AND funcionam. Ambas as portas, o nosso singelo neurônio consegue representar de forma adequada. Então se usamos uma programação adequada, podemos "trapacear" e forçar o neurônio a representar uma porta que ele não conseguiria representar. Ou seja, conseguiremos implementar uma porta XOR usando apenas e somente um único neurônio.

Mas espere um pouco. Podemos fazer este tipo de coisa? Isto não é um tanto quanto desonesto? Bem, não existe em nenhum lugar, algo que diga que não podemos fazer isto. Mesmo que seja uma trapaça e estamos fazendo as coisas de uma forma um tanto quanto controvérsia. Não vejo nenhum motivo que venha a justificar, que não podemos mostrado como se faz, este tipo de coisa. Então vamos agora para a parte da programação.


Tolo pedaço de sílicio

Muito bem, se você entendeu o tópico anterior, seja bem-vindo ao ponto onde vamos mostrar que a máquina é o que queremos que ela seja. Ela não vai além do que desejamos, mesmo que muitos pensem o contrário. Uma rede neural nada mais é do que um cálculo muito bem elaborado. E como um computador é apenas uma calculadora um tanto quanto mais poderosa. Podemos dizer ao pedaço de sílicio que faça exatamente o que queremos. E se ele não nos obedecer, o forçamos a seguir nossas ordens, até que ele se submeta e aprenda quem é que manda nesta na parada toda.

No código abaixo, podemos ver o nosso neurônio primordial. Aqui vamos usar a versão mais simples, onde não estamos otimizando em nada o número de entradas. Então você pode adicionar tantas quanto forem as entradas, que seu desejo lhe force a usar. Porém não se esqueça do que foi visto no artigo 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("************************************");
}
//+------------------------------------------------------------------+

Bem este neurônio não consegue, ainda representar a porta XOR ou sua inversa. Mas e se o treinamos para que ele saiba como representar uma porta NOR e uma porta AND. Assim como o pegar e o dizer como conectar estas mesmas portas, para que uma porta XOR, venha a ser representada. Será que assim este tolo pedaço de sílicio, conseguirá entender como uma porta XOR pode ser representada? Bem, vejamos então como fazer isto. Lembrando que o que irei mostrar, é apenas uma de tantas alternativas possíveis e viáveis. Você meu caro leitor, é livre para tentar outras alternativas possíveis.

Muito bem, a primeira coisa a fazer, é entender que iremos construir uma pequena rede neural, dentro de um simples neurônio. Com isto precisamos que o código imagine que estamos fazendo algo, quando na verdade estaremos fazendo outra coisa. Como cada uma das portas precisa, ser compreendida pelo neurônio. Então cada uma das portas terá dois pesos ( w0 e w1 ) e um viés ( b ). Isto por que temos duas entradas, em cada uma das portas. E cada porta seria na prática, um neurônio. Por conta disto teremos, dois pesos e um viés, para cada uma das portas que vier a ser implementada internamente. Como fazermos uso de três portas, serão necessários, quatro pesos e dois viés. Mas espere um pouco. Se cada porta precisa de dois pesos e um viés. Não seria seis pesos e três viés, o número correto? Não meu caro leitor. Como uma das portas, no caso a NOR, será duplicada, não precisamos incluir ela na conta. Parece complicado, porém na prática, a coisa toda, é bem mais simples do que parece.

É muito importante que você, meu caro leitor, compreenda muito bem isto. Se você não entendeu isto ainda, veja os artigos anteriores para saber o porquê destes pesos e viés. Mas principalmente procure entender, o que foi explicado no tópico anterior, para entender como usaremos as portas. Se você ficar com qualquer dúvida com relação a isto, não entenderá o que estará acontecendo de fato dentro do neurônio.

Ok, vou então assumir que você, meu caro e estimado leitor conseguiu entender o que deverá ser feito. Então o código visto acima, será modificado para o código visto imediatamente abaixo.

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

E ao executar este código dentro da plataforma MetaTrader 5, você poderá ver algo muito parecido com a imagem abaixo.

Veja que a porta XOR de fato foi atestada pelo neurônio. Ou seja, ele conseguiu, e graças a nossa ajuda, entender como uma porta XOR poderia ser representada. Mas olhando o código acima, você pode ficar na dúvida de como isto foi de fato feito. Parece ser algo extremamente complexo e de difícil compreensão. Mas se você olhar o código com calma, irá de forma bem direta notar que ele é quase igual ao código visto no começo deste tópico. Mas então por que de ele ter mais coisas? E outra, por que diferente dos casos anteriores, não estamos colocando a tabela verdade da porta XOR, neste código, a fim de treinar o neurônio?

Ok, são perguntas válidas. Para a primeira, a resposta é que: Precisamos dizer ao neurônio, para que ele aprenda a representar uma porta NOR e uma porta AND. Por isto aparentemente temos mais coisas aqui. Já a resposta para a segunda pergunta. O correto é dizer que não precisamos treinar o neurônio com a tabela da porta XOR. Iremos trapacear e forcar ele a criar uma representação da porta, baseando-se nos dados, que ele, neurônio, criar para representar a porta NOR e a porta AND. Com isto criaremos o equivalente em código, do circuito digital visto no tópico anterior.

Mas como precisamos separar um pouco as coisas entre as portas. Criamos na linha 21 uma estrutura, para conter os dados que possam representar uma porta XOR. Mas observe que ali temos apenas campos para conter valores do tipo double. Não temos nenhum dado, ou informação para atestar que a porta XOR será criada de fato.

Já a função de custo, presente na linha 30, recebeu um argumento extra. Este tem como objetivo, dizer a função de custo, quais os dados estarão sendo usados para o treinamento. Todo o restante da função de custo permaneceu inalterado. Ou seja, o neurônio ainda continua na sua forma mais básica. Assim sendo ele ainda é capaz de executar, as mesmas coisas que eram feitas antes. Isto caso você o queira usar fora do que está sendo feito dentro desta aplicação em particular, que visa obter uma porta XOR.

Já na linha 41, começamos a parte referente a esta aplicação em particular. Neste ponto, está tudo sendo ligado e feito dentro de um único procedimento. Mas apesar de parecer complicado, a coisa toda é bem simples e como deixei o código um pouco mais subdividido. Acredito que você, meu caro leitor, conseguirá entender o que está acontecendo.

Começando pelas linhas 49 a 51. Ali inicializamos os valores para os pesos e viés da porta NOR. A mesma coisa acontece entre as linhas 53 e 55, só que nestas, inicializamos os valores para a porta AND. Atenção ao fato de que estamos inicializando o sistema com valores pseudo aleatórios. Isto já acontecia antes, como você pode ver no código presente no começo deste mesmo tópico. O detalhe é que antes, apenas inicializamos os valores para uma única porta, e agora estamos fazendo isto para duas portas.

Muito bem, então na linha 59 entramos no laço for, cujo objetivo é fazer com que o neurônio procure uma forma de representar as portas lógicas. Fazendo uso dos dados presentes nos arrays de treinamento. Novamente isto parece ser algo complicado. Mas estamos já fazendo este mesmo tipo de coisa já a algum tempo. O detalhe é que diferente das vezes anteriores, agora estamos treinando o neurônio, para representar não uma, mas duas portas lógicas diferentes. Assim a primeira coisa a ser feita é procurar o erro inicial para cada uma das portas. Isto é feito na linha 61, onde buscamos o erro para a porta NOR, e na linha 62 o valor de erro da porta AND. Como precisamos testar dois valores, a fim de encerrar o laço o quanto antes. Na linha 64, checamos se o erro está abaixo do esperado pelo neurônio. Se este for o caso, a linha 65 irá fazer com que o laço seja encerrado. Caso contrário, iremos para o próximo passo dentro do laço.

O próximo passo, é fazer uso da reta secante a fim de procurar o ponto de menor custo. Primeiro fazemos isto para a porta NOR. Isto pode ser visto entre as linhas 67 a 73. Nestes pontos calculamos exatamente a mesma coisa que fazíamos antes. Não sendo de fato um problema ser compreendido para quem vem acompanhando os artigos. Uma vez feitos os cálculos para a porta NOR, fazemos a mesma coisa para a porta AND. Isto se dá entre as linhas 75 e 81.

Com isto forçamos o neurônio a gerar uma representação daquilo que ele consegue entender. Que são justamente as portas AND e NOR. Veja que diferente do que podia ser imaginando antes. Não precisamos de fato treinar a terceira porta. Ou seja, primeiro compreenda o que precisa ser feito, depois faça o que foi imaginado inicialmente.

Entre as linhas 84 e 90, imprimimos uma série de informações para checar os resultados obtidos. Lembre-se de que a ideia por trás de uma rede neural é criar uma equação que represente dos dados. O fato de que ela gera algo que muitos acreditam ser inteligente, é um mero detalhe. E no final entre as linhas 92 e 96, imprimimos o que o neurônio nos diz ser uma porta XOR.


Entendendo os resultados

Cara, eu entendi grande parte do código. Já que venho estudando seus códigos com bastante atenção. Vi que de fato, fizemos o treinamento de uma porta NOR e de uma porta AND. Fazendo assim com que o neurônio compreendesse como representar tais portas. Mas com relação a esta última parte, onde imprimimos o resultado e obtemos exatamente, o que seria o treinamento para representar uma porta XOR. Cara não entendi nada do que está sendo feito. Poderia explicar um pouco melhor esta parte?

Mas é claro que sim. Claro que posso explicar o que está acontecendo nesta parte final do código. Sei que à primeira vista parece ser algo bem inusitado e um tanto quanto complicado. Mas isto talvez se deve, ao fato de que você estaria esperando um dado código. E ao olhar o código presente para reportar a representação da porta XOR. Você de fato está se deparando com algo muito diferente do esperado. Mas para entender isto de forma bem clara. Quero acompanhe as sequencias de imagens abaixo, junto com o que acontece nos fragmentos mostrados logo em seguida. A ideia aqui é desmembrar ainda mais o código final. Isto para que possamos tornar a explicação do que está acontecendo, ainda bem mais simples, para você meu caro leitor.

Vamos começar pelo seguinte: Na imagem abaixo, temos a porta NOR, que recebe inicialmente os dados de origem. Sendo pintada de AZUL.

No código, esta mesma porta pintada na imagem acima. Também tem seu fragmento destacado logo abaixo.

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));

Perfeito, note que está é a primeira das portas que está sendo representada. Agora vamos ver a próxima porta, que é a porta AND, que se encontra pintada de verde na imagem logo abaixo.

No código, esta mesma porta AND que foi pintada de verde na imagem acima. Recebe destaque em verde no fragmento logo abaixo.

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));

Note que é o mesmo fragmento. Agora vem o grande final. Que é a porta NOR de saída, que está pintada de vermelho na imagem abaixo.

Para já que no fragmento todo o código faz parte desta última porta, vou apenas destacar onde estão os parâmetros da mesma. Ou seja, as entradas dela é justamente as saídas das demais. Porém ela precisa dos parâmetros de peso e viés da porta NOR original. E é justamente isto que está sendo destacada no fragmento abaixo.

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));

Com isto considero a explicação dada e encerrada. Porém quero propor um desafio a você meu caro e estimado leitor. Isto para que você prove a si mesmo, de que conseguiu entender o que foi explicado neste artigo.


Um desafio para você leitor

Vamos fazer o seguinte: No anexo, você terá acesso tanto ao código visto neste artigo, para representar a porta XOR. Assim como também o código para a resposta do desafio. Porém quero que seja honesto, e tente vencer o desafio, antes de ver a resposta. Ou uma provável proposta para a resposta. Ok, estamos combinados?

Bem, se a resposta for sim. Vamos ao desafio. Será algo simples e ligado com o que foi visto neste artigo. Quero que você tente desenvolver um treinamento, para que um único neurônio consiga gerar uma representação para a porta XOR. Mas não a que foi vista até este momento. Quero que você, olhe o circuito na animação logo abaixo, e produza algo que use as portas que estão sendo usadas no circuito. Como muitos de vocês, muito provavelmente não tem conhecimento em eletrônica, vou mostrar a tabela verdade de cada uma das portas. Mas de qualquer forma, tente apenas olhar a animação, pois ela mostra exatamente o que está acontecendo.

A tabela verdade, para você poder usar, é vista logo abaixo no imagem.

Um detalhe, para resolver este desafio, tente fazer algo parecido com o que foi mostrado neste artigo. Porém existe o detalhe de que você precisará ajustar as coisas de uma forma um pouco diferente. Agora se você de fato conseguiu entender como fazer as coisas, e quer um desafio um pouco mais elaborado. Tente criar um neurônio que consiga fazer o que é visto na animação abaixo.

Neste caso estamos apenas usando portas NAND e mesmo assim o resultado final é uma representação da porta XOR. Este sim pode ser um desafio para você tentar ajustar as coisas na saída do neurônio. Mas não é complicado de fazer. Apenas precisa de um pouco de paciência para criar a saída correta.


Considerações finais

Neste artigo tentei mostrar a você meu caro leitor que nem tudo é como parece. Muitas das vezes somos levados a pensar que as coisas são de uma dada maneira, quando na verdade, podemos estar sendo levados a pensar algo que não necessariamente é verdade. Redes neurais, são de longe um dos assuntos mais interessantes em termos gerais. Tanto pelo ponto de vista matemático, eletrônico ou mesmo de software. Porém, diferente do que muitos acreditam ou pregam. Redes neurais não são nem de longe, a questão e solução definitiva. São apenas um ramo de pesquisa, no qual devemos sempre estar estudando e procurando nos informar sobre o que acontece nos bastidores.

Quando o assunto é redes neurais, você não deve acreditar em ninguém. Na verdade, o que eu tenho sentido ao logo dos anos. Quanto mais avançadas ficam as pesquisas a respeito do tema, redes neurais. Mais mentiras e boatos surgem. Bem lá no fundo, redes neurais estão sendo estudadas a anos, para não dizer décadas. Porém, vejo muitos simplesmente ignorarem tal fato, e acharem que o tema já alcançou o seu apogeu. Porém ainda há muito a ser feito sobre o tema. Como você pode ver até neste ponto. Não existe uma forma definitiva de representar qualquer tipo de modelo. Por mais simples que o mesmo possa parecer, não existe ainda uma forma de representar adequadamente todas as coisas.

Ainda existe muito espaço para pesquisas, melhorias e estudos sobre o tema redes neurais. Por mais que a coisa pareça ser avançada, conseguindo gerar imagens, ou até mesmo textos em geral. Ela ainda carece de uma coisa essencial: CRITÉRIO E BOM SENSO. De qualquer forma, ainda temos diversas outras coisas a serem vistas sobre o tema. Então nos vemos no próximo artigo.
Arquivo MQ5Descrição
Anexo Demonstração básica
Desafio 01 Demonstração básica
Desafio 02 Demonstração básica
Arquivos anexados |
Anexo.zip (3.25 KB)
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Do básico ao intermediário: Sobrecarga de operadores (VI) Do básico ao intermediário: Sobrecarga de operadores (VI)
Aqui iremos implementar a exclusão via sobrecarga de operador. E este com toda a certeza será um artigo, no qual muitos precisaram estudar por um longo período. Isto a fim de conseguir assimilar tudo o que será mostrado aqui. Mas quero lembrar a você, que o que veremos aqui, será apenas uma pequena e insignificante parte de tudo aquilo que é chamado programação.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Modelo matricial de previsão baseado em cadeia de Markov Modelo matricial de previsão baseado em cadeia de Markov
Criamos um modelo matricial de previsão baseado em uma cadeia de Markov. O que são cadeias de Markov e como uma cadeia de Markov pode ser usada para trading no Forex.