English Русский 中文 Español Deutsch 日本語
preview
Ciência de Dados e Aprendizado de Máquina — Redes Neurais (Parte 02): Arquitetura das Redes Neurais Feed Forward

Ciência de Dados e Aprendizado de Máquina — Redes Neurais (Parte 02): Arquitetura das Redes Neurais Feed Forward

MetaTrader 5Sistemas de negociação | 24 novembro 2022, 14:30
381 0
Omega J Msigwa
Omega J Msigwa

“Não estou sugerindo que as redes neurais sejam fáceis. Você precisa ser um especialista para fazer essas coisas funcionarem. Mas essa experiência atende a você em um espectro mais amplo de aplicativos. De certa forma, todo o esforço que anteriormente era dedicado ao desenvolvimento dos recursos agora ela é direcionada ao desenvolvimento da arquitetura, da função de perda e do esquema de otimização. O trabalho manual foi elevado a um nível mais alto de abstração.”

--Stefano Soatto


Introdução

No artigo anterior, nós discutimos os fundamentos de uma rede neural e construímos uma MLP muito básica e estática, mas nós sabemos que em aplicações da vida real não precisaremos de apenas 2 entradas e 2 nós de camadas ocultas na rede para a saída, algo que construímos da última vez.

Às vezes, uma rede que funciona melhor para o seu problema pode ser aquela com 10 nós na camada de entrada, 13 nós/neurônios na camada oculta e algo como quatro ou algo assim na camada de saída, sem falar que você terá que ajustar o número de camadas ocultas em toda a rede. 

Meu ponto é que precisamos de algo dinâmico. Um código dinâmico que nós podemos alterar os parâmetros e otimizar sem interromper o programa. Se você usar a biblioteca python-keras para construir uma rede neural, você terá que fazer menos trabalho de configuração e compilação até mesmo de arquiteturas complexas, isso é algo que eu quero que sejamos capazes de alcançar na MQL5.

Assim como eu fiz na Regressão linear parte 3 que é uma leitura obrigatória nesta série de artigos, eu introduzi a forma de matriz/vetor de modelos para poder ter modelos flexíveis com um número ilimitado de entradas.

As matrizes vêm para o resgate

Todos nós sabemos que os modelos de codificação falham quando se trata de otimizar para os novos parâmetros, todo o procedimento é demorado, causa dores de cabeça, dores nas costas etc.(Não vale a pena)

imagem do artigo de rede neural


Se nós olharmos mais de perto as operações por trás de uma rede neural, você notará que cada entrada é multiplicada pelo peso atribuído a ela e, em seguida, sua saída é adicionada ao viés. Isso pode ser bem tratado pelas operações de matriz.

multiplicação de matriz da rede neural

Basicamente, nós encontramos o produto escalar da entrada e a matriz de peso e, finalmente, adicionamos ao viés.

Para construir uma rede neural flexível, eu vou tentar uma arquitetura ímpar de 2 nós na camada de entrada, 4 na primeira camada oculta, 6 na segunda camada oculta e 1 na terceira camada oculta e, finalmente, um nó na camada de saída.

arquitetura da rede neural

Isso é para testar se a nossa lógica da matriz funcionará sem problemas em todos os cenários de caso

  • Quando a camada anterior (entrada) tem poucos nós numéricos que a próxima camada (camada de saída)
  • Quando a camada anterior (entrada) tem muitos nós que a próxima camada
  • Quando há um número igual de nós na camada de entrada e na próxima camada (saída)

Antes de nós codificarmos as operações de matriz e calcularmos os valores, vamos fazer as coisas básicas que tornarão possível toda a operação.

Gerando os pesos aleatórios e os valores de viés.

    //Generate random bias
    for(int i=0; i<m_hiddenLayers; i++)         bias[i] = MathRandom(0,1);
    
    //generate weights 
    int sum_weights=0, L_inputs=inputs;
    double L_weights[];
    
    for (int i=0; i<m_hiddenLayers; i++)
      {
         sum_weights += L_inputs * m_hiddenLayerNodes[i];
         ArrayResize(Weights,sum_weights);
         L_inputs = m_hiddenLayerNodes[i];         
      }
    
    for (int j=0; j<sum_weights; j++) Weights[j] = MathRandom(0,1);

Vimos essa operação na parte anterior, mas uma coisa a notar é que esses valores de peso e viés devem ser gerados uma vez para serem usados no ciclo de épocas.


O que é uma Época?

Uma época é uma passagem completa de todos os dados na rede neural, em um feedforward é uma passagem direta completa de todas as entradas, em uma retropropagação é uma passagem inteira para frente e para trás. Em palavras simples, é quando uma rede neural viu todos os dados.

Ao contrário da MLP que vimos no artigo anterior desta vez estamos vindo com uma implementação que leva em consideração a Função de Ativação na camada de saída, algo que quem usa a keras provavelmente está familiarizado com isso, basicamente podemos ter diferentes funções de ativação na camada oculta e aquela que leva à saída na camada de saída.

CNeuralNets(fx HActivationFx,fx OActivationFx,int &NodesHL[],int outputs=NULL, bool SoftMax=false);

Preste atenção nas entradas HActivationFx, ela é para a função de ativação nas camadas ocultas, OActivationFx é para a função de ativação na camada de saída, NósHL[] é para o número de nós na camada oculta. Se esse array tiver, digamos, 3 elementos, significa que você terá 3 camadas ocultas e o número de nós nessas camadas será determinado pelos elementos presentes dentro do array, veja o código abaixo.

int hlnodes[3] = {4,6,1};
int outputs = 1;
     
neuralnet = new CNeuralNets(SIGMOID,RELU,hlnodes,outputs);

Esta é a Arquitetura da imagem que acabamos de ver acima. O argumento de saída é opcional, se você deixar NULL a seguinte configuração será aplicada na camada de saída:

if (m_outputLayers == NULL)
{
  if (A_fx == RELU)     m_outputLayers = 1;
  else                  m_outputLayers = ArraySize(MLPInputs);
}

Se você escolheu o RELU como uma função de ativação na camada oculta, a camada de saída terá um nó, caso contrário, o número de saídas na camada final será igual ao número de entradas na primeira camada. As chances são altas de que você esteja usando uma rede neural de classificação se estiver usando outra função de ativação que não seja RELU na camada oculta, portanto, a camada de saída padrão será igual ao número de colunas. Isso não é confiável, embora as saídas devam ser o número de recursos de destino do seu conjunto de dados, se você estiver tentando resolver um problema de classificação, eu encontrarei uma maneira de alterar isso nas próximas atualizações. No momento, você deve escolher manualmente o número de saída dos neurônios.

Agora, vamos chamar a função completa MLP e ver sua saída, depois eu explicarei o que foi feito para tornar as operações possíveis.

LI      0       10:10:29.995    NNTestScript (#NQ100,H1)        CNeural Nets Initialized activation = SIGMOID UseSoftMax = No
IF      0       10:10:29.995    NNTestScript (#NQ100,H1)        biases
EI      0       10:10:29.995    NNTestScript (#NQ100,H1)        0.6283 0.2029 0.1004
IQ      0       10:10:29.995    NNTestScript (#NQ100,H1)        Hidden Layer 1 | Nodes 4 | Bias 0.6283
NS      0       10:10:29.995    NNTestScript (#NQ100,H1)        Inputs 2 Weights 8
JD      0       10:10:29.995    NNTestScript (#NQ100,H1)        4.00000 6.00000
FL      0       10:10:29.995    NNTestScript (#NQ100,H1)        0.954 0.026 0.599 0.952 0.864 0.161 0.818 0.765
EJ      0       10:10:29.995    NNTestScript (#NQ100,H1)        Arr size A 2
EM      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 3.81519 X A[0] = 4.000 B[0] = 0.954
NI      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 9.00110 X A[1] = 6.000 B[4] = 0.864
IE      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 0.10486 X A[0] = 4.000 B[1] = 0.026
DQ      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 1.06927 X A[1] = 6.000 B[5] = 0.161
MM      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 2.39417 X A[0] = 4.000 B[2] = 0.599
JI      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 7.29974 X A[1] = 6.000 B[6] = 0.818
GE      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 3.80725 X A[0] = 4.000 B[3] = 0.952
KQ      0       10:10:29.995    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 8.39569 X A[1] = 6.000 B[7] = 0.765
DL      0       10:10:29.995    NNTestScript (#NQ100,H1)        before rows 1 cols 4
GI      0       10:10:29.995    NNTestScript (#NQ100,H1)        IxWMatrix
QM      0       10:10:29.995    NNTestScript (#NQ100,H1)        Matrix
CH      0       10:10:29.995    NNTestScript (#NQ100,H1)        [ 
HK      0       10:10:29.995    NNTestScript (#NQ100,H1)        9.00110 1.06927 7.29974 8.39569
OO      0       10:10:29.995    NNTestScript (#NQ100,H1)        ] 
CH      0       10:10:29.995    NNTestScript (#NQ100,H1)        rows = 1 cols = 4

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< End of the first Hidden Layer >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

NS      0       10:10:29.995    NNTestScript (#NQ100,H1)        Hidden Layer 2 | Nodes 6 | Bias 0.2029
HF      0       10:10:29.995    NNTestScript (#NQ100,H1)        Inputs 4 Weights 24
LR      0       10:10:29.995    NNTestScript (#NQ100,H1)        0.99993 0.84522 0.99964 0.99988
EL      0       10:10:29.996    NNTestScript (#NQ100,H1)        0.002 0.061 0.056 0.600 0.737 0.454 0.113 0.622 0.387 0.456 0.938 0.587 0.379 0.207 0.356 0.784 0.046 0.597 0.511 0.838 0.848 0.748 0.047 0.282
FF      0       10:10:29.996    NNTestScript (#NQ100,H1)        Arr size A 4
EI      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.00168 X A[0] = 1.000 B[0] = 0.002
QE      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.09745 X A[1] = 0.845 B[6] = 0.113
MR      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.47622 X A[2] = 1.000 B[12] = 0.379
NN      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.98699 X A[3] = 1.000 B[18] = 0.511
MI      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 0.06109 X A[0] = 1.000 B[1] = 0.061
ME      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 0.58690 X A[1] = 0.845 B[7] = 0.622
PR      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 0.79347 X A[2] = 1.000 B[13] = 0.207
KN      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[1] = 1.63147 X A[3] = 1.000 B[19] = 0.838
GI      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 0.05603 X A[0] = 1.000 B[2] = 0.056
GE      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 0.38353 X A[1] = 0.845 B[8] = 0.387
GS      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 0.73961 X A[2] = 1.000 B[14] = 0.356
CO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[2] = 1.58725 X A[3] = 1.000 B[20] = 0.848
KH      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 0.59988 X A[0] = 1.000 B[3] = 0.600
OD      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 0.98514 X A[1] = 0.845 B[9] = 0.456
LS      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 1.76888 X A[2] = 1.000 B[15] = 0.784
KO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[3] = 2.51696 X A[3] = 1.000 B[21] = 0.748
PH      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[4] = 0.73713 X A[0] = 1.000 B[4] = 0.737
FG      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[4] = 1.53007 X A[1] = 0.845 B[10] = 0.938
RS      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[4] = 1.57626 X A[2] = 1.000 B[16] = 0.046
OO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[4] = 1.62374 X A[3] = 1.000 B[22] = 0.047
EH      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[5] = 0.45380 X A[0] = 1.000 B[5] = 0.454
DG      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[5] = 0.95008 X A[1] = 0.845 B[11] = 0.587
PS      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[5] = 1.54675 X A[2] = 1.000 B[17] = 0.597
EO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[5] = 1.82885 X A[3] = 1.000 B[23] = 0.282
KH      0       10:10:29.996    NNTestScript (#NQ100,H1)        before rows 1 cols 6
RL      0       10:10:29.996    NNTestScript (#NQ100,H1)        IxWMatrix
HI      0       10:10:29.996    NNTestScript (#NQ100,H1)        Matrix
NS      0       10:10:29.996    NNTestScript (#NQ100,H1)        [ 
ND      0       10:10:29.996    NNTestScript (#NQ100,H1)        0.98699 1.63147 1.58725 2.51696 1.62374 1.82885
JM      0       10:10:29.996    NNTestScript (#NQ100,H1)        ] 
LG      0       10:10:29.996    NNTestScript (#NQ100,H1)        rows = 1 cols = 6

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< End of second Hidden Layer >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ML      0       10:10:29.996    NNTestScript (#NQ100,H1)        Hidden Layer 3 | Nodes 1 | Bias 0.1004
OG      0       10:10:29.996    NNTestScript (#NQ100,H1)        Inputs 6 Weights 6
NQ      0       10:10:29.996    NNTestScript (#NQ100,H1)        0.76671 0.86228 0.85694 0.93819 0.86135 0.88409
QM      0       10:10:29.996    NNTestScript (#NQ100,H1)        0.278 0.401 0.574 0.301 0.256 0.870
RD      0       10:10:29.996    NNTestScript (#NQ100,H1)        Arr size A 6
NO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.21285 X A[0] = 0.767 B[0] = 0.278
QK      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 0.55894 X A[1] = 0.862 B[1] = 0.401
CG      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 1.05080 X A[2] = 0.857 B[2] = 0.574
DS      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 1.33314 X A[3] = 0.938 B[3] = 0.301
HO      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 1.55394 X A[4] = 0.861 B[4] = 0.256
CJ      0       10:10:29.996    NNTestScript (#NQ100,H1)        AxBMatrix[0] = 2.32266 X A[5] = 0.884 B[5] = 0.870
HF      0       10:10:29.996    NNTestScript (#NQ100,H1)        before rows 1 cols 1
LR      0       10:10:29.996    NNTestScript (#NQ100,H1)        IxWMatrix
NS      0       10:10:29.996    NNTestScript (#NQ100,H1)        Matrix
DF      0       10:10:29.996    NNTestScript (#NQ100,H1)        [ 
NN      0       10:10:29.996    NNTestScript (#NQ100,H1)        2.32266
DJ      0       10:10:29.996    NNTestScript (#NQ100,H1)        ] 
GM      0       10:10:29.996    NNTestScript (#NQ100,H1)        rows = 1 cols = 1

Deixe-me visualizar a rede para que possamos ver o que foi feito apenas na primeira camada, o resto é apenas uma iteração do mesmo procedimento.

operações da rede neural de camada única

A multiplicação de matrizes conseguiu multiplicar com precisão os pesos da primeira camada para as entradas exatamente como deveria, mas codificar a lógica não é tão simples quanto parece, as coisas podem ficar um pouco confusas, veja o código abaixo. Ignora o resto do código, foca apenas na função MatrixMultiply.

void   CNeuralNets::FeedForwardMLP(
                    double &MLPInputs[],
                    double &MLPOutput[])
 {    
//---

    m_hiddenLayers = m_hiddenLayers+1;
    
    ArrayResize(m_hiddenLayerNodes,m_hiddenLayers);    
    m_hiddenLayerNodes[m_hiddenLayers-1] = m_outputLayers;
         
    int HLnodes = ArraySize(MLPInputs); 
    int weight_start = 0;
    
    double Weights[], bias[];
    ArrayResize(bias,m_hiddenLayers);
    
//---
    
    int inputs=ArraySize(MLPInputs);
    int w_size = 0; //size of weights

    int cols = inputs, rows=1;
    
    double IxWMatrix[]; //dot product matrix 
    
    //Generate random bias
    for(int i=0; i<m_hiddenLayers; i++)         bias[i] = MathRandom(0,1);
         
    //generate weights 
    int sum_weights=0, L_inputs=inputs;
    double L_weights[];
    
    for (int i=0; i<m_hiddenLayers; i++)
      {
         sum_weights += L_inputs * m_hiddenLayerNodes[i];
         ArrayResize(Weights,sum_weights);
         L_inputs = m_hiddenLayerNodes[i];         
      }
    
    for (int j=0; j<sum_weights; j++) Weights[j] = MathRandom(0,1);
                        
    for (int i=0; i<m_hiddenLayers; i++)
      {          
          w_size = (inputs*m_hiddenLayerNodes[i]);
          ArrayResize(L_weights,w_size);
                     
            ArrayCopy(L_weights,Weights,0,0,w_size);
            ArrayRemove(Weights,0,w_size); 
             
              MatrixMultiply(MLPInputs,L_weights,IxWMatrix,cols,cols,rows,cols);
               
              
              ArrayFree(MLPInputs); ArrayResize(MLPInputs,m_hiddenLayerNodes[i]);
              inputs = ArraySize(MLPInputs); 
              
              for(int k=0; k<ArraySize(IxWMatrix); k++) MLPInputs[k] = ActivationFx(IxWMatrix[k]+bias[i]); 
               
      } 
 } 

A primeira entrada na rede na camada de entrada vem como uma matriz 1xn, o que significa que é uma linha 1, mas colunas desconhecidas (n). Inicializamos essa lógica fora antes do loop 'for' na linha

  int cols = inputs, rows=1;

para obter o número total de pesos necessários para completar o processo de multiplicação, nós multiplicamos o número de camadas de entrada/camada anterior pelo número de saídas/próxima camada, neste caso, temos 2 entradas e 4 nós na primeira camada oculta, então, nós finalmente precisamos de 2x4 = 8, oito (8) valores de pesos. O truque mais importante de todos é encontrado aqui:

 MatrixMultiply(MLPInputs,L_weights,IxWMatrix,cols,cols,rows,cols);

Para entender isso bem, vamos ver o que a multiplicação de matrizes faz:

void MatrixMultiply(double &A[],double &B[],double &AxBMatrix[], int colsA,int rowsB,int &new_rows,int &new_cols)

As últimas entradas new_rows, new_cols pega os novos valores atualizados para as linhas e colunas para a nova matriz, então os valores são reutilizados como o número de linhas e colunas para a próxima matriz. Lembre-se de que a entrada da próxima camada é a saída da camada anterior?

Isso é ainda mais importante para a matriz porque

  • Na primeira camada, a matriz de entrada é a matriz de pesos 1x2 = 2x4: matriz de saída = 1x4
  • Na segunda camada, a matriz de entrada 1x4 pondera a matriz = 4x6: a matriz de saída = 1x6
  • A terceira camada entra 1x6 pesos, a matriz 6x1 sai uma matriz = 1x1

Nós sabemos que para multiplicar uma matriz, o número de colunas na primeira matriz deve ser igual ao número de linhas na segunda matriz. A matriz resultante ocupará as dimensões do número de linhas da primeira matriz e do número de colunas da segunda matriz.

Das operações acima

A primeira entrada é aquela que tem as dimensões conhecidas, mas a matriz de pesos tem 8 elementos que foram encontrados multiplicando as entradas e o número de nós na camada oculta, então nós podemos finalmente concluir que ela tem as linhas iguais ao número de colunas na camada anterior/a entrada e é basicamente isso. O processo de modificar os valores das novas linhas e novas colunas aos antigos torna esta lógica possível (Função de multiplicação dentro da matriz)

 new_rows = rowsA;  new_cols = colsB;

Para obter mais informações sobre as matrizes, experimentamos a biblioteca padrão de matrizes ou você pode querer tentar algo diferente que foi usado nesta biblioteca vinculada no final do artigo.

Agora que nós temos uma arquitetura flexível, vamos ver como o treinamento da rede e o treinamento e teste podem ser para essa MLP de feedforward.

Processo envolvido

  1. Treinamos a rede por um número x de épocas, nós encontramos o modelo com menos erros.
  2. Armazenamos os parâmetros do modelo no arquivo binário que nós podemos ler em outros programas, por exemplo, dentro de um Expert Advisor.

Espere um segundo, eu acabei de dizer encontramos um modelo com menos erros? Bem, nós não, isso é apenas um feedforward.

Algumas pessoas na comunidade MQL5. preferem otimizar o EA com esses parâmetros nas entradas, isso funciona, mas neste, estamos gerando os pesos e vieses apenas uma vez e os usamos para o resto das épocas da maneira que faremos retropropagação, mas a única coisa aqui é que não atualizamos esses valores depois de definidos, eles não são atualizados --period.

Usamos o número padrão de épocas definido como 1 (um).

void CNeuralNets::train_feedforwardMLP(double &XMatrix[],int epochs=1)

Você pode encontrar uma maneira de modificar o código e colocar os pesos nas entradas do script a partir daí, você pode definir o número de épocas para quaisquer valores, você não está restrito a essa maneira. A propósito, esta é uma demonstração. 


Testando ou usando o modelo em dados nunca vistos

Para poder usar o modelo que treinamos, nós precisamos poder compartilhar os seus parâmetros com outros programas, isso pode ser possível usando os arquivos, já que nossos parâmetros de modelo são valores de arrays do tipo double, arquivos binários são o que nós precisamos. Lemos os arquivos binários que armazenam nossos pesos e viés e os armazenamos em seus respectivos arrays prontos para uso.

Ok, então aqui está a função responsável por treinar a rede neural.

void CNeuralNets::train_feedforwardMLP(double &XMatrix[],int epochs=1)
   {         
      double MLPInputs[]; ArrayResize(MLPInputs,m_inputs);
      double MLPOutputs[]; ArrayResize(MLPOutputs,m_outputLayers);
      
      double Weights[], bias[];
      
      setmodelParams(Weights,bias); //Generating random weights and bias
      
      for (int i=0; i<epochs; i++)
         {
           int start = 0;
           int rows = ArraySize(XMatrix)/m_inputs;
           
               {
                 if (m_debug) printf("<<<< %d >>>",j+1);
                 ArrayCopy(MLPInputs,XMatrix,0,start,m_inputs);
             
                 FeedForwardMLP(MLPInputs,MLPOutputs,Weights,bias);
             
                 start+=m_inputs;
               }
         }
       
       WriteBin(Weights,bias);
   }

A função setmodelParams() é uma função que gera os valores aleatórios de pesos e viés. Depois de treinar o modelo nós obtemos os valores dos pesos e viés, nós armazenamos em um arquivo binário.

WriteBin(Weights,bias);

Para demonstrar como tudo funciona na MLP, vamos usar o conjunto de dados de exemplo da vida real encontrado aqui.

O argumento XMatrix[] é uma matriz de todos os valores de entrada que nós queremos treinar o nosso modelo. Nesse caso, precisamos importar um arquivo CSV para uma matriz.

conjunto de dados nasdaq


Vamos importar o conjunto de dados

Bem, eu te cubro.

     double XMatrix[]; int rows,cols;
     
     CSVToMatrix(XMatrix,rows,cols,"NASDAQ_DATA.csv");
     MatrixPrint(XMatrix,cols,3);

A saída do trecho de código acima:

MN      0       12:02:13.339    NNTestScript (#NQ100,H1)        Matrix
MI      0       12:02:13.340    NNTestScript (#NQ100,H1)        [ 
MJ      0       12:02:13.340    NNTestScript (#NQ100,H1)         4173.800 13067.500 13386.600    34.800
RD      0       12:02:13.340    NNTestScript (#NQ100,H1)         4179.200 13094.800 13396.700    36.600
JQ      0       12:02:13.340    NNTestScript (#NQ100,H1)         4182.700 13108.000 13406.600    37.500
FK      0       12:02:13.340    NNTestScript (#NQ100,H1)         4185.800 13104.300 13416.800    37.100
.....
.....
.....
DK      0       12:02:13.353    NNTestScript (#NQ100,H1)         4332.700 14090.200 14224.600    43.700
GD      0       12:02:13.353    NNTestScript (#NQ100,H1)         4352.500 14162.000 14225.000    47.300
IN      0       12:02:13.353    NNTestScript (#NQ100,H1)         4401.900 14310.300 14226.200    56.100
DK      0       12:02:13.353    NNTestScript (#NQ100,H1)         4405.200 14312.700 14224.500    56.200
EE      0       12:02:13.353    NNTestScript (#NQ100,H1)         4415.800 14370.400 14223.200    60.000
OS      0       12:02:13.353    NNTestScript (#NQ100,H1)        ] 
IE      0       12:02:13.353    NNTestScript (#NQ100,H1)        rows = 744 cols = 4

Agora todo o arquivo CSV é armazenado dentro de XMatrix[]. Felicidades!

O bom dessa matriz resultante é que você não precisa mais se preocupar com as entradas de uma rede neural porque a variável cols obtém o número de colunas de um arquivo CSV. Estas serão as entradas de uma rede neural. Então, finalmente, aqui está como o script inteiro se parece:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

#include "NeuralNets.mqh";
CNeuralNets *neuralnet; 

//+------------------------------------------------------------------+
void OnStart()
  {
     
     int hlnodes[3] = {4,6,1};
     int outputs = 1;
     int inputs_=2;
     
     double XMatrix[]; int rows,cols;
     
     CSVToMatrix(XMatrix,rows,cols,"NASDAQ_DATA.csv");
     MatrixPrint(XMatrix,cols,3);
     
     neuralnet = new CNeuralNets(SIGMOID,RELU,cols,hlnodes,outputs);     
     
     neuralnet.train_feedforwardMLP(XMatrix);
     
     delete(neuralnet);
    
  }

Simples certo? Mas ainda há algumas linhas de código que temos que corrigir, dentro do train_feedforwardMLP, nós adicionamos as iterações de todo o conjunto de dados em uma única iteração de época, isso é para fazer um significado completo de uma época.

       for (int i=0; i<epochs; i++)
         {
           int start = 0;
           int rows = ArraySize(XMatrix)/m_inputs;
           
             for (int j=0; j<rows; j++) //iterate the entire dataset in a single epoch
               {
                 if (m_debug) printf("<<<< %d >>>",j+1);
                 ArrayCopy(MLPInputs,XMatrix,0,start,m_inputs);
             
                 FeedForwardMLP(MLPInputs,MLPOutputs,Weights,bias);
             
                 start+=m_inputs;
               }
         }

Agora vamos ver os logs, quando executamos este programa no modo de depuração.

bool m_debug = true; 

O modo de depuração pode preencher o espaço do seu disco rígido, a menos que você esteja depurando a rede neural, defina-o como false, eu executei o programa uma vez e tive registros levando até 21Mb de espaço.

Uma breve visão geral das duas iterações:

MR      0       12:23:16.485    NNTestScript (#NQ100,H1)        <<<< 1 >>>
DE      0       12:23:16.485    NNTestScript (#NQ100,H1)        Hidden layer nodes plus the output
FS      0       12:23:16.485    NNTestScript (#NQ100,H1)        4 6 1 1
KK      0       12:23:16.485    NNTestScript (#NQ100,H1)        Hidden Layer 1 | Nodes 4 | Bias 0.3903
IN      0       12:23:16.485    NNTestScript (#NQ100,H1)        Inputs 4 Weights 16
MJ      0       12:23:16.485    NNTestScript (#NQ100,H1)         4173.80000 13067.50000 13386.60000    34.80000
DF      0       12:23:16.485    NNTestScript (#NQ100,H1)        0.060 0.549 0.797 0.670 0.420 0.914 0.146 0.968 0.464 0.031 0.855 0.240 0.717 0.288 0.372 0.805
....
PD      0       12:23:16.485    NNTestScript (#NQ100,H1)        MLP Final Output
LM      0       12:23:16.485    NNTestScript (#NQ100,H1)        1.333
HP      0       12:23:16.485    NNTestScript (#NQ100,H1)        <<<< 2 >>>
PG      0       12:23:16.485    NNTestScript (#NQ100,H1)        Hidden layer nodes plus the output
JR      0       12:23:16.485    NNTestScript (#NQ100,H1)        4 6 1 1
OH      0       12:23:16.485    NNTestScript (#NQ100,H1)        Hidden Layer 1 | Nodes 4 | Bias 0.3903
EI      0       12:23:16.485    NNTestScript (#NQ100,H1)        Inputs 4 Weights 16
FM      0       12:23:16.485    NNTestScript (#NQ100,H1)         4179.20000 13094.80000 13396.70000    36.60000
II      0       12:23:16.486    NNTestScript (#NQ100,H1)        0.060 0.549 0.797 0.670 0.420 0.914 0.146 0.968 0.464 0.031 0.855 0.240 0.717 0.288 0.372 0.805
GJ      0       12:23:16.486    NNTestScript (#NQ100,H1)        

Tudo está configurado e funcionando bem, conforme o esperado. Agora vamos armazenar o parâmetro do modelo nos arquivos binários.

Armazenando os parâmetros do modelo em um arquivo binário

bool CNeuralNets::WriteBin(double &w[], double &b[])
 {
      string file_name_w = NULL, file_name_b=  NULL;
      int handle_w, handle_b;
      
      file_name_w = MQLInfoString(MQL_PROGRAM_NAME)+"\\"+"model_w.bin";
      file_name_b =  MQLInfoString(MQL_PROGRAM_NAME)+"\\"+"model_b.bin"; 
      
      FileDelete(file_name_w); FileDelete(file_name_b);
      
       handle_w = FileOpen(file_name_w,FILE_WRITE|FILE_BIN);       
       if (handle_w == INVALID_HANDLE)   {  printf("Invalid %s Handle err %d",file_name_w,GetLastError());  }
       else                                 FileWriteArray(handle_w,w);
      
       FileClose(handle_w);    
       
       handle_b = FileOpen(file_name_b,FILE_WRITE|FILE_BIN);
       if (handle_b == INVALID_HANDLE)   {  printf("Invalid %s Handle err %d",file_name_b,GetLastError());  }
       else                                 FileWriteArray(handle_b,b);
     
       FileClose(handle_b);
     
     return(true);
 }


Essa etapa é super importante. Como dito anteriormente, ela ajuda a compartilhar os parâmetros do modelo com outros programas, todos usando a mesma biblioteca. Os arquivos binários serão armazenados em um subdiretório com o nome do seu arquivo de script:

modelo de arquivos binários mql5 de rede neural

Um exemplo de como acessar os parâmetros do modelo em outros programas:

     double weights[], bias[];
     
     int handlew = FileOpen("NNTestScript\\model_w.bin",FILE_READ|FILE_BIN);
     FileReadArray(handlew,weights);
     FileClose(handlew);
     
     int handleb = FileOpen("NNTestScript\\model_b.bin",FILE_READ|FILE_BIN);
     FileReadArray(handleb,bias);
     FileClose(handleb);
     
     Print("bias"); ArrayPrint(bias,4);
     Print("Weights"); ArrayPrint(weights,4);

Saídas:

HR      0       14:14:02.380    NNTestScript (#NQ100,H1)        bias
DG      0       14:14:02.385    NNTestScript (#NQ100,H1)        0.0063 0.2737 0.9216 0.4435
OQ      0       14:14:02.385    NNTestScript (#NQ100,H1)        Weights
GG      0       14:14:02.385    NNTestScript (#NQ100,H1)        [ 0] 0.5338 0.6378 0.6710 0.6256 0.8313 0.8093 0.1779 0.4027 0.5229 0.9181 0.5449 0.4888 0.9003 0.2870 0.7107 0.8477
NJ      0       14:14:02.385    NNTestScript (#NQ100,H1)        [16] 0.2328 0.1257 0.4917 0.1930 0.3924 0.2824 0.4536 0.9975 0.9484 0.5822 0.0198 0.7951 0.3904 0.7858 0.7213 0.0529
EN      0       14:14:02.385    NNTestScript (#NQ100,H1)        [32] 0.6332 0.6975 0.9969 0.3987 0.4623 0.4558 0.4474 0.4821 0.0742 0.5364 0.9512 0.2517 0.3690 0.4989 0.5482
Ótimo, agora você pode acessar este arquivo de qualquer lugar, desde que saiba os nomes e onde encontrá-los.

Usando o modelo

Esta é a parte fácil. A função MLP feed forward foi modificada, novos pesos de entrada e viés foram adicionados, isso ajudará na execução do modelo para os dados de preços recentes ou algo assim.

void   CNeuralNets::FeedForwardMLP(double &MLPInputs[],double &MLPOutput[],double &Weights[], double &bias[])

Código completo sobre como extrair os pesos e vieses e usar o modelo ao vivo. Primeiro nós lemos os parâmetros, depois nós inserimos os valores de entrada não é uma matriz de entrada porque desta vez usamos o modelo treinado para prever o resultado dos valores de entrada. A MLPOutput[] fornecerá a matriz de saída:

     double weights[], bias[];
     
     int handlew = FileOpen("NNTestScript\\model_w.bin",FILE_READ|FILE_BIN);
     FileReadArray(handlew,weights);
     FileClose(handlew);
     
     int handleb = FileOpen("NNTestScript\\model_b.bin",FILE_READ|FILE_BIN);
     FileReadArray(handleb,bias);
     FileClose(handleb);
     
     double Inputs[]; ArrayCopy(Inputs,XMatrix,0,0,cols); //copy the four first columns from this matrix
     double Output[];
     
     neuralnet = new CNeuralNets(SIGMOID,RELU,cols,hlnodes,outputs);     
     
     neuralnet.FeedForwardMLP(Inputs,Output,weights,bias);
     
     Print("Outputs");
     ArrayPrint(Output);
     
     delete(neuralnet);

Isso deve funcionar bem.

Agora, você pode explorar vários tipos de arquitetura e diferentes opções para ver a que funciona melhor para você novamente.

A rede neural feedforward foi a primeiro e o tipo de rede neural artificial mais simples desenvolvido. Nessa rede, a informação se move em apenas uma direção: para frente dos nós de entrada, através dos nós ocultos (se houver) e para os nós de saída. Não há ciclos ou loops na rede

Este modelo que acabamos de codificar é básico e pode não necessariamente fornecer os resultados desejados, a menos que ele seja otimizado (tenho 100% de certeza) espero que você seja criativo e faça algo com isso.

Pensamentos finais

É importante entender a teoria e tudo nos bastidores de cada técnica de aprendizado de máquina, porque nós não temos pacotes de ciência de dados em MQL5, pelo menos, temos as estruturas python, mas há momentos em que podemos precisar trabalhar na MetaTrader, sem ter uma compreensão sólida da teoria por trás desses tipos de coisas, será difícil descobrir as coisas e tirar o máximo proveito do aprendizado de máquina, à medida que avançamos na importância da teoria e das coisas que discutimos anteriormente na série de artigos estão se mostrando de muita importância.

Cumprimentos.

Repositório GitHub: https://github.com/MegaJoctan/NeuralNetworks-MQL5

Leia mais sobre a minha biblioteca de Matrizes e Vetores


Leitura adicional | Livros | Referências

Referência dos artigos:

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/11334

Arquivos anexados |
NeuralNets_Lib.zip (24.33 KB)
Crie o seu próprio Indicador técnico Crie o seu próprio Indicador técnico
Neste artigo, eu abordarei os algoritmos que permitem que você crie o seu próprio indicador técnico. Você aprenderá como obter resultados bem complexos e interessantes com suposições iniciais muito simples.
A matemática do mercado: lucro, prejuízo e custos A matemática do mercado: lucro, prejuízo e custos
Neste artigo, eu mostrarei como calcular o lucro ou prejuízo total de qualquer negociação, incluindo comissão e swap. Eu fornecerei o modelo matemático mais preciso e o usarei para escrever o código e compará-lo com o padrão. Além disso, eu também tentarei entrar na função principal da MQL5 para calcular o lucro e chegar ao fundo de todos os valores necessários da especificação.
Como desenvolver um sistema de negociação baseado no indicador VIDYA Como desenvolver um sistema de negociação baseado no indicador VIDYA
Bem-vindo a um novo artigo da nossa série sobre como desenvolver um sistema de negociação pelos indicadores técnicos mais populares, neste artigo aprenderemos sobre uma nova ferramenta técnica e aprenderemos como desenvolver um sistema de negociação pelo Variable Index Dynamic Average (VIDYA).
Aprendendo a construindo um EA que opera de forma automática (Parte 07): Tipos de Contas (II) Aprendendo a construindo um EA que opera de forma automática (Parte 07): Tipos de Contas (II)
Aprenda como criar um EA que opera de forma automática, isto de forma simples e o mais seguro possível. É preciso sempre ficar atento, ao que um EA automatizado, esta fazendo, e se ele sair da linha, removê-lo o mais rápido possível do gráfico, encerrando o que ele estava fazendo, a fim de evitar que as coisas fugam do controle.