English Русский Deutsch 日本語
preview
Modelo GRU de Deep Learning com Python para ONNX com EA, e comparação entre modelos GRU e LSTM

Modelo GRU de Deep Learning com Python para ONNX com EA, e comparação entre modelos GRU e LSTM

MetaTrader 5Testador | 5 agosto 2024, 11:25
21 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Introdução

Esta é a continuação de Previsão de Deep Learning e Colocação de Ordens usando Python, o pacote MetaTrader5 Python e um arquivo de modelo ONNX, mas você pode continuar este sem precisar do anterior. Tudo será explicado. Tudo o que usaremos está incluído neste artigo. Nesta seção, iremos guiá-lo por todo o processo, culminando na criação de um Expert Advisor (EA) para negociação e posterior teste.

Machine learning é um subconjunto da inteligência artificial (IA) que foca no desenvolvimento de algoritmos e modelos estatísticos que permitem que os computadores realizem tarefas sem serem explicitamente programados. O objetivo principal do machine learning é permitir que os computadores aprendam com os dados e melhorem seu desempenho ao longo do tempo.

Como os modelos funcionam

Vamos usar os princípios básicos subjacentes ao funcionamento e aplicação dos modelos de machine learning. Embora isso possa parecer elementar para aqueles que já têm experiência com modelagem estatística ou machine learning, fique tranquilo, pois rapidamente passaremos para o desenvolvimento de modelos sofisticados e robustos.

Inicialmente, vamos focar em um modelo conhecido como árvore de decisão. Embora existam modelos mais complicados que oferecem maior precisão preditiva, as árvores de decisão servem como um ponto de entrada acessível devido à sua simplicidade e papel fundamental na construção de alguns dos modelos mais avançados no campo da ciência de dados.

Para simplificar as coisas, comecemos com a forma mais rudimentar de uma árvore de decisão.

Árvore

Aqui categoriza-se casas em apenas dois grupos diferentes. O preço esperado para cada casa elegível é derivado do preço médio histórico de casas dentro da mesma categoria.

Utiliza dados para determinar o método ótimo de categorizar as casas nesses dois grupos e então determina o preço esperado para cada grupo. Esta etapa crucial, na qual o modelo captura padrões dos dados, é conhecida como ajuste ou treinamento do modelo. O conjunto de dados utilizado para esse fim é chamado de dados de treinamento.

As complexidades do ajuste do modelo, incluindo decisões de segmentação de dados, são suficientemente complexas e serão cobertas com mais detalhes mais adiante. Uma vez que o modelo tenha sido ajustado, ele pode ser aplicado a novos dados para prever preços para casas adicionais.


Melhorando a árvore de decisão

Qual dos dois modelos de árvore de decisão mostrados abaixo é mais provável de surgir do processo de ajuste dos dados de treinamento para imóveis?

Duas árvores de decisão

A árvore de decisão à esquerda (árvore de decisão 1) é provavelmente mais alinhada com a realidade, pois reflete a correlação entre o número de quartos e os preços mais altos das casas. No entanto, sua principal desvantagem é que ela não leva em consideração muitos outros fatores que afetam os preços das casas, como o número de banheiros, o tamanho do lote, a localização, etc.

Para levar em conta uma gama mais ampla de fatores, pode-se usar uma árvore com "divisões" adicionais chamadas de árvore "mais profunda". Por exemplo, uma árvore de decisão que leva em conta o tamanho total do lote de cada casa pode ser assim:

Árvore 3

Para estimar o preço de uma casa, siga os ramos da árvore de decisão, sempre escolhendo o caminho que corresponde às características específicas da casa. O preço previsto para a casa está no final da árvore. Este ponto específico onde uma previsão é feita é chamado de "folha".

As divisões e valores nessas folhas são influenciados pelos dados, levando você a observar o conjunto de dados com o qual está trabalhando.


Usando Pandas para dados

Na fase inicial de qualquer projeto de machine learning, você precisa se familiarizar com o conjunto de dados. É aqui que a biblioteca Pandas se torna indispensável. Cientistas de dados normalmente usam o Pandas como sua principal ferramenta para examinar e processar dados. No código, geralmente é abreviado como "pd".

import pandas as pd

Selecionando dados para modelagem

O conjunto de dados possui um número esmagador de variáveis que torna difícil entendê-lo ou até mesmo apresentá-lo claramente. Como podemos organizar essa vasta quantidade de dados em uma forma mais gerenciável para compreendê-la melhor?

Nossa primeira abordagem é selecionar um subconjunto de variáveis com base na intuição. Adiante, apresentaremos técnicas estatísticas que permitem a priorização automática das variáveis.

Para identificar as variáveis ou colunas a serem selecionadas, primeiro precisamos examinar uma lista abrangente de todas as colunas no conjunto de dados.

Importamos esses dados, com:

mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)


Construindo seu modelo

Para criar seus modelos, o melhor recurso para você é a biblioteca scikit-learn, frequentemente abreviada como sklearn no código. Scikit-learn é a escolha preferida para modelar os tipos de dados tipicamente armazenados em DataFrames.

Aqui estão os passos principais para criar e usar um modelo:

  • Definir: Determine o tipo de modelo que deseja criar. É uma árvore de decisão ou você escolherá um modelo diferente? Você também define parâmetros específicos para o tipo de modelo selecionado.
  • Personalizar: Esta fase é o núcleo da modelagem, onde seu modelo aprende e captura padrões dos dados fornecidos. Envolve o treinamento do modelo em seu conjunto de dados.
  • Prever: Por mais simples que pareça, esta etapa é onde seu modelo treinado é usado para fazer previsões sobre novos dados ou dados não vistos. O modelo generaliza o que aprendeu para fazer previsões fundamentadas.
  • Avaliar: Avalie a precisão das previsões do seu modelo. Esta etapa crucial compara a saída do modelo com os resultados reais para que você possa avaliar seu desempenho e confiabilidade.

Usando o scikit-learn, esses passos fornecem uma estrutura organizada para construir, treinar e avaliar modelos de forma eficiente, adaptados aos diferentes dados encontrados tipicamente em DataFrames.


A unidade recorrente de portão (GRU)

A Wikipedia diz:

Unidades recorrentes com portas (GRUs) são um mecanismo de portas em redes neurais recorrentes, introduzido em 2014 por Kyunghyun Cho. O GRU é como uma memória de curto e longo prazo (LSTM) com um mecanismo de portas para entrada ou esquecimento de certas características, mas não possui um vetor de contexto ou porta de saída, resultando em menos parâmetros do que o LSTM. O desempenho do GRU em certas tarefas de modelagem de música polifônica, modelagem de sinal de fala e processamento de linguagem natural foi considerado semelhante ao do LSTM. Os GRUs mostraram que as portas são de fato úteis em geral, e a equipe de Bengio não chegou a nenhuma conclusão concreta sobre qual das duas unidades de portas era melhor.

GRU, uma sigla para Unidade Recorrente com Portas, representa uma variante da arquitetura de rede neural recorrente (RNN) semelhante ao LSTM (Memória de Curto e Longo Prazo).

Assim como o LSTM, o GRU é projetado para modelar dados sequenciais, permitindo a retenção ou omissão seletiva de informações ao longo do tempo. Notavelmente, o GRU possui uma arquitetura mais simplificada em relação ao LSTM, apresentando menos parâmetros. Essa característica melhora a facilidade de treinamento e a eficiência computacional.

A principal distinção entre o GRU e o LSTM reside na forma como lidam com o estado da célula de memória. No LSTM, o estado da célula de memória é distinto do estado oculto e é atualizado através de três portas: a porta de entrada, porta de saída e porta de esquecimento. Por outro lado, o GRU substitui o estado da célula de memória por um "vetor de ativação candidato", atualizado por meio de duas portas: a porta de reset e a porta de atualização.

Em resumo, o GRU emerge como uma alternativa preferida ao LSTM para modelagem de dados sequenciais, especialmente em cenários onde existem restrições computacionais ou uma arquitetura mais simples é preferida.


Como o GRU opera:

Semelhante a outras arquiteturas de rede neural recorrente, o GRU processa dados sequenciais elemento por elemento, ajustando seu estado oculto com base na entrada atual e no estado oculto anterior. Em cada passo de tempo, o GRU calcula um "vetor de ativação candidato" amalgamando informações da entrada e do estado oculto anterior. Esse vetor então atualiza o estado oculto para o próximo passo de tempo.


O vetor de ativação candidato é calculado usando duas portas: a porta de reset e a porta de atualização. A porta de reset determina o grau de esquecimento do estado oculto anterior, enquanto a porta de atualização influencia a integração do vetor de ativação candidato no novo estado oculto.

Este é o modelo (GRU) que escolhemos para este artigo.

model.add(Dense(128, activation='relu', input_shape=(inp_history_size,1), kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(1, activation='linear'))

Primeiro, selecionamos a entrada em características e a variável alvo.

if 'Close' in data.columns:
    data['target'] = data['Close']
else:
    data['target'] = data.iloc[:, 0]

# Extract OHLC columns
x_features = data[[0]]
# Target variable
y_target = data['target']

Dividimos os dados em conjuntos de treinamento e testes

x_train, x_test, y_train, y_test = train_test_split(x_features, y_target, test_size=0.2, shuffle=False)

Aqui, o tamanho do teste é de 20%, geralmente, os tamanhos de teste são escolhidos para ser menos de 30% (para evitar overfitting)

Inicialização do Modelo Sequencial:

model = Sequential()

Esta linha cria um modelo sequencial vazio, que permite adicionar camadas de forma incremental.

Adicionando Camadas Densas:

model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))

A camada densa é uma camada totalmente conectada em uma rede neural.

Os números entre parênteses indicam o número de neurônios em cada camada. A primeira camada, portanto, consiste em 128 neurônios, a segunda em 256, a terceira em 128 e a quarta em 64.

A função de ativação 'relu' (Rectified Linear Unit) é usada para introduzir uma não-linearidade após cada camada, o que ajuda o modelo a aprender padrões complexos.

O parâmetro input_shape é especificado apenas na primeira camada e define a forma dos dados de entrada. Neste caso, corresponde ao número de características nos dados de entrada.

kernel_regularizer=l2(k_reg) aplica regularização L2 aos pesos da camada e ajuda a prevenir overfitting penalizando valores grandes de peso.

Camada de Saída:

model.add(Dense(1, activation='linear'))

  • A última camada consiste em um único neurônio, o que é típico para uma tarefa de regressão (previsão de um valor contínuo).
  • A função de ativação 'linear' é usada, o que significa que a saída é uma combinação linear das entradas sem transformação adicional.
  • Para resumir, este modelo consiste em várias camadas densas com funções de ativação de unidade linear retificada, seguidas por uma camada de saída linear. Para regularização, a regularização L2 é aplicada aos pesos. Esta arquitetura é tipicamente usada para tarefas de regressão onde o objetivo é prever um valor numérico.

Agora, compilamos o modelo

# Compile the model[]
model.compile(optimizer='adam', loss='mean_squared_error')

Otimizador:

O otimizador é um componente crucial do processo de treinamento. Ele determina como os pesos do modelo são atualizados durante o treinamento para minimizar a função de perda. 'adam' é um algoritmo de otimização popular conhecido por sua eficiência no treinamento de redes neurais. Ele ajusta as taxas de aprendizado para cada parâmetro individualmente e, portanto, é adequado para uma ampla gama de problemas.

Função de Perda:

O parâmetro de perda define o alvo que o modelo tenta minimizar durante o treinamento. Neste caso, 'mean_squared_error' é usado como função de perda. O erro quadrático médio (MSE) é uma escolha comum para problemas de regressão onde o objetivo é minimizar a diferença quadrática média entre os valores previstos e os valores reais. É adequado para problemas onde a saída é um valor contínuo. MAE calcula a média das diferenças absolutas entre os valores previstos e os valores reais.

mae e mse

Todos os erros são tratados com a mesma significância, independentemente da sua direção. Um MAE mais baixo também significa melhor desempenho do modelo.

Para resumir, a instrução model.compile configura o modelo de rede neural para treinamento. Especifica o otimizador ('adam') para atualizar os pesos durante o treinamento e a função de perda ('mean_squared_error') que deve minimizar o modelo. Este passo de compilação é uma etapa preliminar necessária para treinar o modelo com dados.

E treinamos o modelo de rede neural previamente definido.

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2, verbose=1)
Dados de treinamento:

X_train_scaled : Esses são os dados de entrada das características para treinamento, presumivelmente escalados ou pré-processados para garantir a estabilidade numérica.

y_train : Esses são os valores alvo ou rótulos correspondentes aos dados de treinamento.

Configuração de treinamento:

epochs=int(epoch) : Esse parâmetro especifica quantas vezes o conjunto de dados de treinamento é passado para frente e para trás pela rede neural.

int(epoch) especifica que o número de épocas é determinado pela variável epoch.

batch_size=256 : Durante cada época, os dados de treinamento são divididos em lotes, e os pesos do modelo são atualizados após cada lote ser processado. Aqui, cada lote consiste em 256 pontos de dados.

Dados de validação:

validation_split=0.2 : Esse parâmetro especifica que 20% dos dados de treinamento são usados como conjunto de validação O desempenho do modelo nesse conjunto é monitorado durante o treinamento, mas não é usado para atualizar os pesos.

Verbosidade:
verbose=1 : Esse parâmetro controla a verbosidade da saída de treinamento. Um valor de 1 significa que o progresso do treinamento é exibido no console.

Durante o processo de treinamento, o modelo aprende a fazer previsões ajustando seus pesos com base nos dados de entrada fornecidos (X_train_scaled) e valores alvo (y_train). A divisão de validação ajuda a avaliar o desempenho do modelo em dados não vistos e o progresso do treinamento é exibido com base na configuração de verbosidade.


Desvendando a Unidade Linear

Quando olhamos para redes neuronais, começamos com o bloco de construção básico: o neurônio individual. Diagramaticamente, um neurônio, também conhecido como unidade, se parece com isso quando configurado com uma única entrada:

y = x*w + b

Desvendando a Mecânica da Unidade Linear

Vamos explorar as complexidades do componente central de uma rede neural: o neurônio único. Visualmente, um neurônio com uma única entrada x é representado da seguinte maneira:

A entrada, rotulada como x, forma uma conexão com o neurônio, e essa conexão tem um peso, rotulado como w, anexado a ela. Quando a informação passa por essa conexão, o valor é multiplicado pelo peso atribuído à conexão No caso da entrada x, o que eventualmente chega ao neurônio é o produto w * x. Ajustando esses pesos, uma rede neural "aprende" ao longo do tempo.

Agora introduzimos b, uma forma especial de ponderação chamada bias. Ao contrário de outros pesos, o bias não tem dados de entrada associados a ele. Em vez disso, um valor de 1 é inserido no gráfico para garantir que o valor que chega ao neurônio seja simplesmente b (já que 1 * b é igual a b). Ao introduzir o bias, o neurônio é habilitado a alterar sua saída independentemente de suas entradas.

Y = X*W + b*1


Abraçando Múltiplas Entradas

E se quisermos incluir mais fatores? Não se preocupe, porque a solução é bastante simples. Ampliando nosso modelo, podemos adicionar de forma contínua conexões de entrada adicionais ao neurônio, cada uma correspondendo a uma característica específica.

Para derivar a saída, realizamos um processo direto. Cada entrada é multiplicada pelo peso de conexão apropriado e os resultados são combinados de forma abundante. O resultado é uma representação holística onde o neurônio processa habilmente várias entradas, tornando o modelo mais detalhado e refletindo a interação complexa de diferentes características. Este método permite que nossa rede neural capture uma gama mais ampla de informações, aprimorando sua capacidade de reconhecer padrões de forma abrangente.

y = w0*x0 + w1*x1 + w2*x2

Expressa matematicamente, a operação desse neurônio é capturada sucintamente pela fórmula:

y = w 0 ⋅ x 0 + w 1 ⋅ x 1 + w 2 ⋅ x 2 + b =  y=w0​⋅x0​+w1​⋅x1​+w2​⋅x2​+b

Nesta equação:

  • y representa a saída do neurônio.
  • w 0 , w 1 , w 2 ​ denotam os pesos associados às respectivas entradas x 0 , x 1 , x 2 ​.
  • b representa o termo de bias.

Esta unidade linear, equipada com duas entradas, possui a capacidade de modelar um plano em um espaço tridimensional. À medida que o número de entradas supera dois, a unidade se torna apta a ajustar hiperplanos—superfícies multidimensionais que capturam de forma intrincada os relacionamentos entre múltiplas características de entrada. Essa flexibilidade permite que a rede neural navegue e compreenda padrões complexos nos dados que vão além de simples relacionamentos lineares.

Unidades Lineares no Keras

Criar uma rede neural no Keras é alcançado de forma contínua através do 'keras.Sequential()'. Esta utilidade monta uma rede neural empilhando camadas, oferecendo uma abordagem direta para a criação de modelos. As camadas encapsulam a essência da arquitetura da rede, e entre elas, a camada dense se torna particularmente pertinente para construir modelos semelhantes aos explorados anteriormente.

model = Sequential()

No futuro, vamos nos aprofundar nas complexidades da camada densa, descobrindo suas capacidades e seu papel na construção de arquiteturas de redes neurais robustas e expressivas.


Redes Neurais Profundas

Aprimore a profundidade e a capacidade expressiva da sua rede integrando camadas ocultas. Essas camadas escondidas desempenham um papel fundamental em desvendar relações complexas dentro dos dados, capacitando sua rede neural a discernir e capturar padrões complexos. Eleve a sofisticação do seu modelo adicionando estrategicamente camadas ocultas, permitindo que ele aprenda e represente características sutis para previsões mais abrangentes e precisas.

Explorando a Construção de Redes Neurais Complexas

Agora embarcamos em uma jornada para construir redes neurais com a capacidade de compreender as relações intrincadas que caracterizam o poder das redes neurais profundas.

O conceito central da nossa abordagem é a modularidade—uma estratégia que envolve montar uma rede sofisticada a partir de unidades funcionais elementares. Tendo explorado anteriormente como uma unidade linear computa uma função linear, nosso foco agora se volta para a fusão e adaptação dessas unidades individuais. Ao combinar e modificar estrategicamente esses componentes fundamentais, desbloqueamos o potencial de modelar e entender relações mais complexas e multifacetadas inerentes a conjuntos de dados complexos. Isso serve como um portal para criar redes neurais que podem navegar e compreender os padrões sutis que definem o domínio do aprendizado profundo.

Camadas Reveladas

Na arquitetura intrincada das redes neurais, os neurônios são sistematicamente organizados em camadas. Uma configuração notável que emerge é a camada densa—uma consolidação de unidades lineares compartilhando um conjunto comum de entradas.

Essa disposição facilita uma estrutura poderosa e interconectada, permitindo que os neurônios dentro da camada processem e interpretem informações coletivamente. À medida que exploramos as complexidades das camadas, a camada densa se destaca como um construto fundamental, ilustrando como os neurônios podem colaborar para contribuir com a capacidade da rede de compreender e aprender relações complexas dentro dos dados.

input, dense output

 


Camadas Diversas no Keras

No domínio do Keras, uma "camada" engloba uma entidade notavelmente versátil. Essencialmente, manifesta-se como qualquer forma de transformação de dados. Numerosas camadas, exemplificadas por camadas convolucionais e recorrentes, aproveitam os neurônios para metamorfosear os dados, distinguindo-se principalmente pelos padrões intrincados de conexões que forjam. Por outro lado, outras camadas servem a propósitos que vão desde a engenharia de características até aritmética elementar, mostrando a ampla gama de transformações que podem ser orquestradas dentro da estrutura modular de uma rede neural. A diversidade de camadas sublinha a adaptabilidade e as capacidades expansivas que contribuem para o rico tecido das arquiteturas de redes neurais.

Potencializando Redes Neurais com Funções de Ativação

Surpreendentemente, a incorporação de duas camadas densas sem quaisquer elementos intermediários não supera a eficácia de uma única camada densa. Camadas densas em isolamento nos confinam dentro do domínio das estruturas lineares, incapazes de transcender os limites de linhas e planos. Para quebrar essa linearidade, introduzimos um elemento crucial: a não linearidade. Este ingrediente fundamental é incorporado pelas funções de ativação.

As funções de ativação servem como a força transformadora, injetando não linearidade na rede neural. Elas fornecem a ferramenta essencial para navegar além das restrições lineares, permitindo que o modelo discirna padrões e relações intrincadas dentro dos dados. Essencialmente, as funções de ativação são os catalisadores que impulsionam as redes neurais para domínios de complexidade, desbloqueando sua capacidade de capturar as características sutis inerentes a diversos conjuntos de dados.

Quando amalgamamos a função retificadora com uma unidade linear, o resultado é uma entidade formidável conhecida como unidade linear retificada ou ReLU. Na linguagem comum, a função retificadora é frequentemente referida como a "função ReLU" por esta razão. A aplicação da ativação ReLU a uma unidade linear transforma a saída em max(0, w * x + b), uma representação que pode ser ilustrada em um diagrama da seguinte maneira:

w*x + b

Camadas Estratégicas com Redes Densas

Armados com a nova não linearidade, vamos explorar o poder do empilhamento de camadas e como isso nos permite orquestrar transformações complexas de dados.

input, hidden & output

Revelando Camadas Ocultas em Redes Neurais

Precedendo a camada de saída, as camadas intermediárias são frequentemente chamadas de "camadas ocultas" já que suas saídas permanecem ocultas da observação direta.

Observe que a camada final (de saída) adota a aparência de uma unidade linear, sem nenhuma função de ativação. Essa escolha arquitetônica se alinha com tarefas de natureza regressiva, onde o objetivo é prever um valor numérico. No entanto, tarefas como classificação podem necessitar da incorporação de uma função de ativação na camada de saída para melhor atender aos requisitos da tarefa específica em questão.


Construindo Modelos Sequenciais

O modelo Sequential, como utilizado até agora, conecta uma série de camadas de maneira sequencial—da camada inicial à camada final. Nesta orquestração estrutural, a primeira camada serve como a receptora da entrada, enquanto a camada final culmina gerando a saída desejada. Esta montagem sequencial espelha o modelo representado na ilustração acima:

model = keras.Sequential([
    # the hidden ReLU layers
    layers.Dense(units=4, activation='relu', input_shape=[2]),
    layers.Dense(units=3, activation='relu'),
    # the linear output layer 
    layers.Dense(units=1),
])


Garanta uma camadagem coesa apresentando todas as camadas juntas dentro de uma lista, semelhante a [camada, camada, camada, ...], em vez de listá-las separadamente. Para incorporar perfeitamente uma função de ativação em uma camada, basta especificar seu nome no argumento de ativação. Essa abordagem simplificada garante uma representação concisa e organizada da sua arquitetura de rede neural.

Selecionando o Número de Unidades em uma Camada Densa

A decisão sobre o número de unidades em uma camada Dense (por exemplo, layers.Dense(units=4, ...)) depende das características únicas do seu problema e da complexidade dos padrões que você deseja descobrir nos seus dados. Considere os seguintes fatores:

Complexidade do Problema:

  • Para problemas mais simples com relações menos complexas nos dados, um número menor de unidades, como 4, pode ser um ponto de partida adequado.
  • Em cenários mais complexos, caracterizados por relações nuançadas e multifacetadas, optar por um número maior de unidades é frequentemente benéfico.

Tamanho dos Dados:

  • O tamanho do conjunto de dados desempenha um papel; conjuntos de dados maiores podem acomodar um maior número de unidades para que o modelo aprenda.
  • Conjuntos de dados menores exigem uma abordagem mais cautelosa para evitar overfitting e o potencial do modelo aprender ruídos.

Capacidade do Modelo:

  • O número de unidades influencia a capacidade do modelo de capturar padrões complexos, com um aumento geralmente melhorando o poder expressivo.
  • Recomenda-se cautela para evitar a superparametrização, especialmente ao lidar com dados limitados, pois isso poderia levar ao overfitting.

Experimentação:

  • Experimente diferentes configurações, começando com um número modesto de unidades, treinando o modelo e refinando com base nas métricas de desempenho e observações.
  • Técnicas como a validação cruzada fornecem insights sobre o desempenho de generalização do modelo em diferentes subconjuntos de dados.

Lembre-se, a escolha de unidades não é universalmente fixa e pode exigir alguns ajustes. Monitorar o desempenho do modelo em um conjunto de validação e ajustar iterativamente a arquitetura é uma parte valiosa do processo de desenvolvimento do modelo.

Escolhemos isso:

model = Sequential()
model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(1, activation='linear'))

Primeira Camada (Camada de Entrada):

Unidades (128): Um número relativamente alto de unidades, 128, na primeira camada permite que o modelo capture padrões diversos e complexos nos dados de entrada. Isso pode ser vantajoso para extrair características intrincadas nas etapas iniciais da rede. Ativação ('relu'): A ativação de Unidade Linear Retificada (ReLU) introduz não linearidade, permitindo que o modelo aprenda com relacionamentos e padrões complexos. Regularização (L2): O termo de regularização L2 ( kernel_regularizer=l2(k_reg) ) ajuda a evitar o sobreajuste penalizando pesos grandes na camada.

Segunda e Terceira Camadas:

Unidades (256 e 128): Manter um número maior de unidades nas camadas subsequentes (256 e 128) continua permitindo que o modelo capture e processe informações complexas. A redução gradual no número de unidades ajuda a criar uma hierarquia de características. Ativação ('relu'): A ativação ReLU persiste, promovendo não linearidade em cada camada. Regularização (L2): A aplicação consistente da regularização L2 nas camadas auxilia na prevenção do sobreajuste.

Quarta Camada: 

Unidades (64): Uma redução nas unidades refina ainda mais a representação das características, ajudando a destilar informações essenciais, mantendo um equilíbrio entre complexidade e simplicidade. Ativação ('relu'): A ativação ReLU continua, garantindo a preservação das propriedades não lineares. Regularização (L2): O termo de regularização é aplicado consistentemente para estabilidade.

Quinta Camada (Camada de Saída):

Unidades (1): A camada final com uma única unidade é bem adequada para tarefas de regressão, onde o objetivo é prever um valor numérico contínuo. Ativação ('linear'): A ativação linear é apropriada para regressão, permitindo que o modelo saia diretamente o valor previsto sem qualquer transformação adicional.

No geral, a arquitetura escolhida parece adaptada para uma tarefa de regressão, com um equilíbrio cuidadoso entre capacidade expressiva e regularização para evitar o sobreajuste. A redução gradual no número de unidades facilita a extração de características hierárquicas. Este design sugere uma compreensão abrangente da complexidade da tarefa e um esforço para construir um modelo que possa generalizar bem para dados não vistos.

Em resumo, a diminuição gradual no número de unidades nas camadas ocultas, juntamente com as escolhas específicas para cada camada, sugere um design voltado para capturar representações hierárquicas e abstratas dos dados de entrada. A arquitetura parece equilibrar a complexidade do modelo com a necessidade de evitar o sobreajuste, e a escolha das unidades alinha-se com a natureza da tarefa de regressão em questão. Os números específicos podem ter sido determinados através de experimentação e ajuste com base no desempenho do modelo nos dados de validação.


Compilar modelo

# Compile the model[]
model.compile(optimizer='adam', loss='mean_squared_error')

Já exploramos a construção de redes totalmente conectadas usando pilhas de camadas densas. Na fase inicial de criação, os pesos da rede são definidos aleatoriamente, significando que a rede não possui nenhum conhecimento prévio. Agora, nosso foco se volta para o processo de treinamento de uma rede neural, desvendando a essência de como essas redes aprendem.

Como é costume em empreendimentos de aprendizado de máquina, começamos com um conjunto selecionado de dados de treinamento. Cada exemplo dentro deste conjunto de dados compreende características (entradas) juntamente com um alvo antecipado (saída). O cerne do treinamento da rede reside no ajuste de seus pesos para transformar proficientemente as características de entrada em previsões precisas para a saída alvo.

O treinamento bem-sucedido de uma rede para tal tarefa implica que seus pesos encapsulam, em certa medida, a relação entre essas características e o alvo, conforme manifestado nos dados de treinamento.

Além dos dados de treinamento, dois componentes críticos entram em jogo:

  1. Uma "função de perda" que mede a eficácia das previsões da rede.
  2. Um "otimizador" encarregado de instruir a rede sobre como ajustar iterativamente seus pesos para um desempenho aprimorado.

À medida que avançamos no processo de treinamento, entender as complexidades desses componentes torna-se fundamental para nutrir a capacidade de uma rede neural de generalizar e fazer previsões precisas em dados não vistos.

Função de Perda:

Embora já tenhamos abordado o design arquitetônico de uma rede, o aspecto crucial de instruir uma rede sobre o problema específico que deve resolver ainda não foi explorado. Essa responsabilidade recai sobre a função de perda.

Em essência, a função de perda quantifica a diferença entre o valor verdadeiro do alvo e o valor previsto pelo modelo. Serve como parâmetro para avaliar a eficácia com que o modelo alinha suas previsões com os resultados reais.

Uma função de perda frequentemente usada em problemas de regressão é o erro absoluto médio (MAE). No contexto de cada previsão, denotada como y_pred, o MAE avalia a diferença em relação ao alvo verdadeiro, y_true, calculando a diferença absoluta, abs(y_true - y_pred).

A perda cumulativa de MAE em um conjunto de dados é calculada como a média de todas essas diferenças absolutas. Essa métrica fornece uma medida abrangente da magnitude média dos erros de previsão, orientando o modelo a minimizar a discrepância geral entre suas previsões e os alvos verdadeiros.

mae

O erro absoluto médio representa a distância média entre a curva ajustada e os pontos de dados reais.

Além do MAE, funções de perda alternativas comumente encontradas em problemas de regressão incluem o erro quadrático médio (MSE) e a perda de Huber, ambas acessíveis no Keras.

Ao longo do processo de treinamento, o modelo depende da função de perda como um guia de navegação para determinar os valores ótimos para seus pesos—visando a menor perda possível. Essencialmente, a função de perda comunica o objetivo da rede, orientando-a a aprender e refinar seus parâmetros para melhorar a precisão das previsões.

O Otimizador - Gradiente Descendente Estocástico

Definido o problema que a rede deve resolver, o próximo passo crucial é delinear como resolvê-lo. Essa responsabilidade é assumida pelo otimizador—um algoritmo dedicado a ajustar os pesos com o objetivo de minimizar a perda.

No domínio do aprendizado profundo, a maioria dos algoritmos de otimização se enquadra na categoria de gradiente descendente estocástico. São algoritmos iterativos que treinam uma rede incrementalmente. Cada etapa de treinamento segue esta sequência:

  1. Amostre alguns dados de treinamento e insira-os na rede para gerar previsões.
  2. Avalie a perda comparando as previsões com os valores reais.
  3. Ajuste os pesos em uma direção que reduza a perda.

Esse processo é repetido iterativamente até que o nível desejado de redução de perda seja alcançado ou até que a redução adicional se torne impraticável. Essencialmente, o otimizador guia a rede através das complexidades dos ajustes de peso, direcionando-a para a configuração que minimiza a perda e melhora a precisão das previsões.

Cada conjunto de dados de treinamento amostrado em cada iteração é denominado minibatch, frequentemente referido simplesmente como "batch". Por outro lado, uma varredura completa pelos dados de treinamento é conhecida como epoch. O número de épocas especificado determina quantas vezes a rede processa cada exemplo de treinamento.

Taxa de Aprendizado e Tamanho do Lote

A linha sofre apenas uma mudança modesta na direção de cada lote, em vez de uma reformulação completa. A magnitude dessas mudanças é governada pela taxa de aprendizado. Uma taxa de aprendizado menor implica que a rede precisa ser exposta a mais minibatches antes que seus pesos se estabilizem em seus valores ótimos.

A taxa de aprendizado e o tamanho dos minibatches são os dois principais parâmetros que influenciam a trajetória do treinamento com SGD. Navegar pela interação entre eles pode ser sutil, e a seleção ideal nem sempre é aparente.

Felizmente, para a maioria das tarefas, uma busca exaustiva por hiperparâmetros ótimos não é imperativa para resultados satisfatórios. Adam, um algoritmo de SGD com uma taxa de aprendizado adaptativa, elimina a necessidade de ajustes extensivos de parâmetros. Sua natureza autossintonizável o torna um excelente otimizador multifuncional adequado para uma ampla variedade de problemas.

Para este exemplo, escolhemos ADAM como SGD e MSE como perda.

model.compile(optimizer='adam', loss='mean_squared_error')

Ao ajustar,

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2, verbose=1)

veremos algo assim:

44241/44241 [==============================] - 247s 6ms/step - loss: 0.0021 - val_loss: 8.0975e-04
Epoch 2/30
44241/44241 [==============================] - 247s 6ms/step - loss: 2.3062e-04 - val_loss: 0.0010
Epoch 3/30
44241/44241 [==============================] - 288s 7ms/step - loss: 2.3019e-04 - val_loss: 8.5903e-04
Epoch 4/30
44241/44241 [==============================] - 248s 6ms/step - loss: 2.3003e-04 - val_loss: 7.6378e-04
Epoch 5/30
44241/44241 [==============================] - 257s 6ms/step - loss: 2.2993e-04 - val_loss: 9.5630e-04
Epoch 6/30
44241/44241 [==============================] - 247s 6ms/step - loss: 2.2988e-04 - val_loss: 7.3110e-04
Epoch 7/30
44241/44241 [==============================] - 224s 5ms/step - loss: 2.2985e-04 - val_loss: 8.7191e-04


erfitting e Underfitting

Keras mantém um registro da perda de treinamento e validação ao longo das épocas enquanto o modelo está sendo treinado. Vamos nos aprofundar na interpretação dessas curvas de aprendizado e explorar como aproveitá-las para aprimorar o desenvolvimento do modelo. Especificamente, analisaremos as curvas de aprendizado para identificar sinais de underfitting e overfitting e exploraremos algumas estratégias para resolver esses problemas.

Interpretando Curvas de Aprendizado:

Ao considerar informações nos dados de treinamento, elas podem ser categorizadas em dois componentes: sinal e ruído. O sinal representa a parte que generaliza, ajudando nosso modelo a fazer previsões em novos dados. Por outro lado, o ruído compreende flutuações aleatórias decorrentes de dados do mundo real e padrões não informativos que não contribuem para as capacidades preditivas do modelo. Identificar e entender essa distinção é crucial.

Durante o treinamento do modelo, buscamos selecionar pesos ou parâmetros que minimizem a perda em um conjunto de treinamento. No entanto, para uma avaliação abrangente do desempenho de um modelo, é imperativo avaliá-lo em um novo conjunto de dados – os dados de validação.

Interpretar essas curvas de forma eficaz (ao plotá-las) é essencial para treinar modelos de aprendizado profundo com sucesso.

curva de aprendizado

Agora, a perda de treinamento diminui quando o modelo adquire sinal ou ruído. No entanto, a perda de validação diminui apenas quando o modelo aprende sinal, pois qualquer ruído adquirido do conjunto de treinamento não consegue generalizar para novos dados. Consequentemente, quando o modelo aprende sinal, ambas as curvas exibem um declínio, enquanto aprender ruído cria um desvio entre elas. A magnitude desse desvio indica a extensão do ruído que o modelo adquiriu.

over_under_fitting



Em um cenário ideal, buscaríamos construir modelos que aprendam todo o sinal e nenhum ruído. No entanto, alcançar esse estado ideal é praticamente improvável. Em vez disso, navegamos em um trade-off. Podemos incentivar o modelo a aprender mais sinal à custa de adquirir mais ruído. Enquanto esse trade-off nos favorecer, a perda de validação continuará a diminuir. No entanto, chega um ponto em que o trade-off se torna desfavorável, o custo supera o benefício e a perda de validação começa a aumentar.


Esse trade-off destaca dois desafios potenciais no treinamento do modelo: sinal insuficiente ou ruído excessivo Underfitting do conjunto de treinamento ocorre quando a perda não é minimizada porque o modelo não aprendeu sinal suficiente. Por outro lado, overfitting do conjunto de treinamento acontece quando a perda não é minimizada porque o modelo absorveu muito ruído. A chave para treinar modelos de aprendizado profundo está em descobrir o equilíbrio ideal entre esses dois cenários.

O outro gráfico agora ficará assim:

erfitting e Underfitting

Capacidade do Modelo:

A capacidade de um modelo denota sua habilidade de captar e compreender padrões intrincados. No contexto de redes neurais, isso é predominantemente influenciado pelo número de neurônios e suas interconexões. Se parecer que sua rede está capturando inadequadamente a complexidade dos dados (underfitting), considere aumentar sua capacidade.

A capacidade de uma rede pode ser aumentada ampliando-a (adicionando mais unidades às camadas existentes) ou aprofundando-a (incorporando mais camadas). Redes mais amplas se destacam em aprender mais relacionamentos lineares, enquanto redes mais profundas tendem a capturar mais padrões não lineares. A escolha entre as duas depende da natureza do conjunto de dados.

Parada Antecipada:

Conforme discutido anteriormente, quando um modelo está incorporando ruído excessivamente durante o treinamento, a perda de validação pode começar a aumentar. Para contornar esse problema, podemos implementar a parada antecipada, uma técnica em que interrompemos o processo de treinamento assim que fica evidente que a perda de validação não está mais diminuindo. Essa intervenção proativa ajuda a evitar o overfitting e garante que o modelo generalize bem para novos dados.

Uma vez que observamos um aumento na perda de validação, podemos redefinir os pesos para o ponto em que ocorreu o mínimo. Esse passo de precaução garante que o modelo não persista em aprender ruído, evitando assim o overfitting.

Implementar o treinamento com parada antecipada também mitiga o risco de interromper prematuramente o processo de treinamento antes que a rede tenha compreendido completamente o sinal. Além de evitar o overfitting devido ao treinamento excessivamente prolongado, a parada antecipada atua como um guarda contra o underfitting causado pela duração insuficiente do treinamento. Basta configurar suas épocas de treinamento para um número suficientemente grande (mais do que o necessário) e a parada antecipada gerenciará a terminação com base nas tendências de perda de validação.

Integrando Parada Antecipada:

No Keras, incorporar a parada antecipada ao nosso treinamento é feito através de um callback. Um callback é essencialmente uma função que é executada em intervalos regulares durante o processo de treinamento da rede. O callback de parada antecipada, especificamente, é acionado após cada época. Enquanto o Keras fornece uma variedade de callbacks predefinidos para conveniência, ele também permite a criação de callbacks personalizados para atender a requisitos específicos.

É isso que escolhemos:

from tensorflow import keras
from tensorflow.keras import layers, callbacks

early_stopping = callbacks.EarlyStopping(
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=20, # how many epochs to wait before stopping
    restore_best_weights=True,
)

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2,callbacks=[early_stopping], verbose=1)


E também adicionamos mais unidades e uma camada oculta a mais (o modelo acaba ficando mais complexo no .py após o ajuste fino)

model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(1, activation='linear'))

Esses parâmetros transmitem a seguinte instrução: "Se não houver uma melhoria de pelo menos 0.001 na perda de validação nas últimas 20 épocas, interrompa o treinamento e retenha o melhor modelo identificado até agora." Determinar se a perda de validação está aumentando devido ao overfitting ou à mera variação aleatória do lote pode ser desafiador às vezes. Os parâmetros especificados nos permitem estabelecer certas tolerâncias, orientando o sistema sobre quando interromper o processo de treinamento.

Inicialmente, configuramos o número de épocas para 300, esperando uma interrupção antecipada do processo de treinamento.

Lidando com valores ausentes

Várias circunstâncias podem levar à presença de valores ausentes em um conjunto de dados.

Ao trabalhar com bibliotecas de aprendizado de máquina como o scikit-learn, tentar construir um modelo usando dados contendo valores ausentes geralmente resulta em um erro. Consequentemente, você deve adotar uma das seguintes estratégias para resolver esse problema.

Três Abordagens

  1. Solução Simplificada (drop nan): Eliminar Colunas com Valores Ausentes Uma abordagem descomplicada envolve descartar colunas que contêm valores ausentes.
df2 = df2.dropna()

No entanto, a menos que uma parte substancial dos valores nas colunas descartadas esteja ausente, optar por essa abordagem resulta na perda de acesso a uma quantidade significativa de informações potencialmente valiosas pelo modelo. Para ilustrar, imagine um conjunto de dados com 10.000 linhas onde uma coluna crucial tem apenas uma entrada ausente. Empregar essa estratégia implicaria remover a coluna inteira.

2) Uma Alternativa Melhorada: Imputação

A imputação envolve preencher os valores ausentes com valores numéricos específicos. Por exemplo, podemos optar por preencher com o valor médio ao longo de cada coluna.

Embora o valor imputado possa não ser precisamente preciso na maioria dos casos, esse método geralmente resulta em modelos mais precisos em comparação com descartar completamente a linha.


3) Aprimorando Técnicas de Imputação

A imputação é a abordagem convencional, frequentemente mostrando-se eficaz. No entanto, os valores imputados podem sistematicamente desviar-se de seus valores verdadeiros (indisponíveis no conjunto de dados). Alternativamente, linhas com valores ausentes podem exibir características distintas. Nesses casos, refinar seu modelo para considerar a originalidade dos valores ausentes pode melhorar a precisão das previsões.

Nessa metodologia, continuamos com a imputação de valores ausentes conforme descrito anteriormente. Além disso, para cada coluna com entradas ausentes no conjunto de dados inicial, introduzimos uma nova coluna indicando as posições das entradas imputadas.

Embora essa técnica possa melhorar significativamente os resultados em certos cenários, sua eficácia pode variar, e em alguns casos, pode não resultar em nenhuma melhoria.

Exportando o modelo ONNX

1 Carregando os dados.

Agora que temos uma compreensão básica do arquivo .py que criamos para treinar o modelo, vamos prosseguir para treiná-lo. 

Devemos escrever nossos caminhos aqui:

# get rates
eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)

# create dataframe
df = pd.DataFrame(eurusd_rates)

É assim que o código acaba ficando ( GRU_create_model.py ):

Ao treinar, obtemos estes resultados:

Mean Squared Error: 0.0031695919830203693

Mean Absolute Error: 0.05063149001883482

R2 Score: 0.9263800140852619
Baseline MSE: 0.0430534174061265
Baseline MAE: 0.18048216851868318
Baseline R2 Score: 0.0

Como este artigo diz: Previsão da taxa de câmbio Forex usando redes neurais recorrentes profundas, os resultados para GRU e LSTM são semelhantes. 

artigo

tabela_do_artigo


Uma vez que executamos o ONNX_GRU.py, obteremos um modelo ONNX na mesma pasta em que temos o arquivo de treinamento python (ONNX_GRU.py). Esse modelo ONNX deve ser salvo na pasta Arquivos do MQL5, para ser chamado pelo EA.

É assim que o EA é adicionado ao artigo.


Agora podemos testar o modelo com o testador de estratégias ou negociar.


LSTM vs GRU topo


Comparando GRU vs LSTM

A célula LSTM mantém um estado de célula, do qual lê e escreve. Ela abrange quatro portas que governam os processos de leitura, escrita e saída de valores para e do estado da célula, dependendo dos valores de entrada e do estado da célula. A primeira porta dita as informações que o estado oculto deve esquecer. A porta subsequente é responsável por identificar a parte do estado da célula a ser escrita. A terceira porta determina o conteúdo a ser inscrito. Por fim, a última porta recupera informações do estado da célula para gerar uma saída.

LSTM


A célula GRU apresenta semelhanças com a célula LSTM, mas incorpora algumas distinções significativas. Primeiramente, ela não possui um estado oculto, pois a funcionalidade do estado oculto no design da célula LSTM é assumida pelo estado da célula. Subsequentemente, os processos de decidir o que o estado da célula esquece e qual parte do estado da célula é escrita são amalgamados em uma única porta. Apenas a seção do estado da célula que foi apagada é então inscrita. Por fim, todo o estado da célula serve como uma saída, diferentemente da célula LSTM, que lê seletivamente do estado da célula para gerar uma saída. Essas modificações coletivas resultam em um design mais simplificado com menos parâmetros comparado à LSTM. No entanto, a redução de parâmetros pode potencialmente levar a uma diminuição na expressividade.

GRU


Comparação Experimental

GRU

# Split the data into training and testing sets
x_train, x_test, y_train, y_test = train_test_split(x_features, y_target, test_size=0.2, shuffle=False)


# Standardize the features StandardScaler()
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(x_train)
X_test_scaled = scaler.transform(x_test)

scaler_y = StandardScaler()
y_train_scaled = scaler_y.fit_transform(np.array(y_train).reshape(-1, 1))
y_test_scaled = scaler_y.transform(np.array(y_test).reshape(-1, 1))

# Define parameters

learning_rate = 0.001
dropout_rate = 0.5
batch_size = 1024
layer_1 = 256
epochs = 1000
k_reg = 0.001
patience = 10
factor = 0.5
n_splits = 5  # Number of K-fold Splits
window_size = days  # Adjust this according to your needs

def create_windows(data, window_size):
    return [data[i:i + window_size] for i in range(len(data) - window_size + 1)]

custom_optimizer = Adam(learning_rate=learning_rate)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=factor, patience=patience, min_lr=1e-26)

def build_model(input_shape, k_reg):
    model = Sequential()
    
    layer_sizes = [ 512,1024,512, 256, 128, 64]
    model.add(Dense(layer_1, kernel_regularizer=l2(k_reg), input_shape=input_shape))
    for size in layer_sizes:
        model.add(Dense(size, kernel_regularizer=l2(k_reg)))
        model.add(BatchNormalization())
        model.add(Activation('relu'))
        model.add(Dropout(dropout_rate))

    model.add(Dense(1, activation='linear'))
    model.add(BatchNormalization())
    model.compile(optimizer=custom_optimizer, loss='mse', metrics=[rmse()])
    
    return model



# Define EarlyStopping callback
early_stopping = EarlyStopping(monitor='val_loss', patience=patience, restore_best_weights=True)

# KFold Cross Validation
kfold = KFold(n_splits=n_splits, shuffle=True, random_state=42)
history = []
loss_per_epoch = []
val_loss_per_epoch = []

for train, val in kfold.split(X_train_scaled, y_train_scaled):
    x_train_fold, x_val_fold = X_train_scaled[train], X_train_scaled[val]
    y_train_fold, y_val_fold = y_train_scaled[train], y_train_scaled[val]
    
    # Flatten the input data
    x_train_fold_flat = x_train_fold.flatten()
    x_val_fold_flat = x_val_fold.flatten()

    # Create windows for training and validation
    x_train_windows = create_windows(x_train_fold_flat, window_size)
    x_val_windows = create_windows(x_val_fold_flat, window_size)

    # Rebuild the model
    model = build_model((window_size, 1), k_reg)

    # Create a new optimizer
    custom_optimizer = Adam(learning_rate=learning_rate)
    
    # Recompile the model
    model.compile(optimizer=custom_optimizer, loss='mse', metrics=[rmse()])
    
    hist = model.fit(
        np.array(x_train_windows), y_train_fold[window_size - 1:],
        epochs=epochs,
        validation_data=(np.array(x_val_windows), y_val_fold[window_size - 1:]),
        batch_size=batch_size,
        callbacks=[reduce_lr, early_stopping]
    )
    history.append(hist)
    loss_per_epoch.append(hist.history['loss'])
    val_loss_per_epoch.append(hist.history['val_loss'])




mean_loss_per_epoch = [np.mean(loss) for loss in loss_per_epoch]
val_mean_loss_per_epoch = [np.mean(val_loss) for val_loss in val_loss_per_epoch]

print("mean_loss_per_epoch", mean_loss_per_epoch)
print("unique_min_val_loss_per_epoch", val_loss_per_epoch)

# Create a DataFrame to display the mean loss values
epoch_df = pd.DataFrame({
    'Epoch': range(1, len(mean_loss_per_epoch) + 1),
    'Train Loss': mean_loss_per_epoch,
    'Validation Loss': val_loss_per_epoch
})



LSTM

model = Sequential()
model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,1)))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100, return_sequences = True))
model.add(Dropout(0.3))
model.add(LSTM(100, return_sequences = False))
model.add(Dropout(0.3))
model.add(Dense(units=1, activation = 'sigmoid'))
model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])


avaliação da janela deslizante


Deixei um .py para você comparar LSTM e GRU e um cross validation.py.

Também deixei um simples GRU .py para fazer modelos

Com o modelo simples que está no GRU simples, podemos obter esses resultados em janeiro de 2024

backtesting

gráfico


Conclusão e Trabalho Futuro

Esta comparação é crucial para determinar qual modelo usar, ou podemos até considerar usar ambos de maneira empilhada ou sobreposta. Essa abordagem nos permite extrair informações essenciais dos modelos que empregamos, apesar das diferenças inerentes nos tamanhos de lote e nas configurações das camadas. Se, como eu acho, eles resultarem em resultados semelhantes, o GRU é muito mais rápido.

Como parte do trabalho futuro, seria benéfico explorar diferentes inicializadores de kernel e recorrentes adaptados a cada tipo de célula para possíveis melhorias de desempenho.

Uma boa abordagem para negociar com modelos ONNX seria integrar ambos no mesmo EA, por favor, leia este artigo: Um exemplo de como combinar modelos ONNX em mql5.


Conclusão

Modelos como GRU são capazes de obter bons resultados e parecem robustos. Espero que tenha gostado deste artigo tanto quanto eu gostei de criá-lo. Também vimos a comparação entre os modelos GRU e LSTM, e podemos usar esse código .py para saber quando parar as épocas (levando em consideração o número de dados de entrada).


Aviso

O desempenho passado não indica resultados futuros.


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

Algoritmos de otimização de população: Resistência a ficar preso em extremos locais (Parte II) Algoritmos de otimização de população: Resistência a ficar preso em extremos locais (Parte II)
Continuamos nosso experimento que visa examinar o comportamento dos algoritmos de otimização de população no contexto de sua capacidade de escapar eficientemente de mínimos locais quando a diversidade da população é baixa e alcançar máximos globais. Os resultados da pesquisa são fornecidos.
EA de grid-hedge modificado em MQL5 (Parte III): Otimização de uma estratégia de cobertura simples (I) EA de grid-hedge modificado em MQL5 (Parte III): Otimização de uma estratégia de cobertura simples (I)
Na terceira parte, retornamos aos EAs Simple Hedge e Simple Grid, desenvolvidos anteriormente. Agora, vamos melhorar o Simple Hedge EA por meio de análise matemática e abordagem de força bruta (brute force) com o objetivo de otimizar o uso da estratégia. Este artigo se aprofunda na otimização matemática da estratégia, estabelecendo a base para a futura pesquisa de otimização baseada em código nas partes seguintes.
Redes neurais de maneira fácil (Parte 80): modelo generativo adversarial do transformador de grafos (GTGAN) Redes neurais de maneira fácil (Parte 80): modelo generativo adversarial do transformador de grafos (GTGAN)
Neste artigo, apresento o algoritmo GTGAN, que foi introduzido em janeiro de 2024 para resolver tarefas complexas de criação de layout arquitetônico com restrições de grafos.
Redes neurais de maneira fácil (Parte 79): consultas agregadas de características (FAQ) Redes neurais de maneira fácil (Parte 79): consultas agregadas de características (FAQ)
No artigo anterior, nos familiarizamos com um dos métodos de detecção de objetos em imagens. No entanto, o processamento de imagens estáticas é um pouco diferente do trabalho com séries temporais dinâmicas, como aquelas relacionadas à dinâmica dos preços que estamos analisando. Neste artigo, quero apresentar a você o método de detecção de objetos em vídeo, que é mais relevante para a nossa tarefa atual.