English Русский Deutsch 日本語 Português
preview
Modelo de aprendizaje profundo GRU en Python usando ONNX en asesores expertos, GRU vs LSTM

Modelo de aprendizaje profundo GRU en Python usando ONNX en asesores expertos, GRU vs LSTM

MetaTrader 5Probador | 2 septiembre 2024, 17:04
77 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Introducción

Esta es una continuación del artículo: Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX, si no lo ha leído, no pasa nada. Todos los puntos necesarios se describirán en el actual. Además, adjuntaremos al artículo todos los archivos en cuestión. Iremos paso a paso a través del proceso de desarrollo del modelo GRU, luego crearemos un Asesor Experto para operar con este modelo y finalmente lo probaremos.

El aprendizaje automático representa una subsección de la inteligencia artificial que utiliza algoritmos y modelos estadísticos para permitir a los ordenadores realizar tareas sin programarlas explícitamente. El principal objetivo del aprendizaje automático es permitir que los ordenadores aprendan de los datos y mejoren su rendimiento pasado el tiempo.

Cómo funcionan los modelos

Nos valdremos de los principios básicos que subyacen al funcionamiento y la aplicación de los modelos de aprendizaje automático. Para quienes ya tengan experiencia en modelización estadística o aprendizaje automático, este material podría parecer elemental. No se preocupe, actuaremos con rapidez para desarrollar modelos sofisticados y fiables.

Para seguir trabajando, tomaremos el modelo de árbol de decisión. Obviamente, existen modelos más sofisticados que ofrecen una mayor precisión de predicción. Sin embargo, los árboles de decisión son un punto de entrada definitivamente disponible, porque son simples y, sin embargo, desempeñan un papel fundamental en la construcción incluso de algunos modelos avanzados en el aprendizaje automático.

Para hacer las cosas más sencillas, empezaremos con la forma más elemental de un árbol de decisión.

Árbol

Aquí dividiremos las casas en solo dos grupos. El precio previsto de cada vivienda apta se calculará a partir del precio medio histórico de las viviendas de la misma categoría.

Aquí se usarán los datos para determinar el mejor método de clasificación de viviendas en estos dos grupos y, a continuación, determinar el precio previsto para cada grupo. Este importante paso en el que el modelo extrae patrones de los datos se denominará entrenamiento del modelo. El conjunto de datos usado para este fin se denominará datos de entrenamiento.

Los entresijos de la selección de modelos, incluidas las decisiones de segmentación de datos, serán bastante complejos, y los veremos más adelante. Una vez afinado el modelo, podremos aplicarlo a nuevos datos para predecir los precios de la vivienda nueva.


Mejorando el árbol de decisión

¿Cuál de los dos árboles de decisión mostrados a continuación es más probable que surja como resultado del ajuste de los datos de entrenamiento en operaciones inmobiliarias?

Dos árboles de decisión

El árbol de decisión de la izquierda resulta obviamente más relevante, ya que refleja la correlación entre el número de dormitorios y unos precios de la vivienda más elevados. Sin embargo, su principal inconveniente es que no tiene en cuenta muchos otros factores que influyen en el valor de la vivienda, como el número de baños, el tamaño del terreno, la ubicación, etc.

Para considerar una gama más amplia de factores, podemos utilizar un árbol con "ramificaciones" adicionales, es decir, un árbol más profundo. Por ejemplo, un árbol de decisión que tenga en cuenta el tamaño total del terreno de cada casa podría mostrar este aspecto:

Árbol 3

Para estimar el precio de una vivienda, seguiremos las ramas del árbol de decisión, eligiendo siempre un camino según las características específicas de la vivienda. El precio previsto de la vivienda estará al final del árbol. Este punto concreto en el que se realiza la predicción se denomina hoja.

Las separaciones y los valores de estas hojas dependerán de los datos. Considerando esto, merecerá la pena examinar el conjunto de datos con el que estamos trabajando.


Uso de Pandas para los datos

Al principio de cualquier proyecto de aprendizaje automático, deberemos familiarizarnos con el conjunto de datos. Para ello, solo necesitaremos la biblioteca Pandas. Los científicos de datos suelen usar Pandas como herramienta principal para explorar y procesar datos. Suele aparecer como pd en el código.

import pandas as pd

Selección de datos para la modelización

El conjunto de datos contiene un gran número de variables que dificultan la comprensión de los elementos principales. ¿Cómo podemos organizar esta enorme cantidad de datos de manera más manejable para comprenderlos mejor?

La primera opción consiste en seleccionar un subconjunto de variables basándonos en la intuición. A continuación, le presentaremos los métodos estadísticos que permiten priorizar automáticamente las variables.

Para determinar las variables o columnas que vamos a utilizar, primero deberemos examinar una lista completa de todas las columnas del conjunto de datos.

Importaremos esos datos desde aquí:

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


Construyendo el modelo

El mejor recurso para crear modelos es la biblioteca scikit-learn (sklearn). Scikit-learn se elige con frecuencia para modelar tipos de datos almacenados típicamente en DataFrames.

Estos son los pasos clave para crear y usar el modelo:

  • Definimos el tipo de modelo que se va a crear. ¿Será un árbol de decisión o un modelo diferente? También definimos los parámetros específicos del tipo de modelo seleccionado.
  • Personalizamos el modelo. Esta etapa es la principal, aquí es donde nuestro modelo aprende y capta patrones basados en los datos proporcionados. Aquí se incluye el entrenamiento del modelo con nuestra muestra.
  • Predecimos datos nuevos o desconocidos con el modelo construido. En esta fase, el modelo generalizará lo que ha aprendido para hacer nuevas predicciones con conocimiento de causa.
  • Evaluamos la precisión de las predicciones de nuestro modelo. En este importante paso, el resultado del modelo se comparará con los resultados reales, lo que permitirá evaluar el rendimiento del modelo.

Si utilizamos la biblioteca scikit-learn, estos pasos nos proporcionarán un marco estructurado para construir, entrenar y evaluar de forma eficiente modelos adaptados a los distintos datos que suelen contener los DataFrames.


Bloque de recurrencia cerrada GRU

Wikipedia:

Los bloques recurrentes gestionables(GRU) son un mecanismo de puerta para las redes neuronales recurrentes, presentado por Kyunghyun Cho en 2014. Los GRU son similares a las con un mecanismo de salto para introducir u olvidar ciertas funciones, pero no tienen vector de contexto ni puertas de salida, lo cual hace que utilicen menos parámetros que las LSTM. El rendimiento de los GRU en tareas de modelado musical, modelado de señales de voz y procesamiento de lenguaje natural es similar al de las LSTM. El mecanismo GRU ha demostrado que los saltos en general son realmente útiles. En esta caso, además, el equipo de Bengio no ha llegado a una conclusión concreta sobre cuál de los dos mecanismos es mejor.

La GRU es una variante de arquitectura de red neuronal recurrente (RNN) similar a la LSTM (memoria a largo plazo).

Al igual que la LSTM, el mecanismo GRU está diseñado para modelar datos secuenciales, ofreciendo salto selectivo u omisión de información a lo largo del tiempo. Mientras tanto, la GRU posee una arquitectura más simple en comparación con la LSTM y menos parámetros. Esta característica aumenta la facilidad del aprendizaje y la eficiencia computacional.

La principal diferencia entre la GRU y la LSTM es la forma en que se gestiona el estado de una célula de memoria. En la LSTM, el estado de una célula de memoria es diferente del estado oculto y se actualiza usando tres tipos de puertas: de entrada, de salida y de olvido, mientras que la GRU sustituye el estado de una célula de memoria por el vector de activación candidato actualizado usando dos tipos de puertas: reseteo y actualización.

Así, la GRU puede resultar una alternativa más conveniente que la LSTM para el modelado secuencial de datos, especialmente cuando hablamos de restricciones computacionales o se requiere una arquitectura más simple.


Cómo funciona la GRU:

Al igual que otras arquitecturas de redes neuronales recurrentes, la GRU procesa los datos secuenciales por partes, ajustando su estado oculto según la entrada actual y el estado oculto anterior. En cada paso temporal, la GRU calculará un vector candidato combinando la información de los datos de entrada y el estado oculto anterior. Este vector actualizará el estado oculto para el siguiente paso temporal.


El vector candidato se calculará mediante dos puertas: reset y update. La puerta de reinicio determinará hasta qué punto se olvida el estado oculto anterior, mientras que la puerta de actualización afectará a la integración del vector de activación candidato en el nuevo estado oculto.

Este es el modelo (GRU) que utilizaremos en este artículo.

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

En primer lugar, seleccionaremos los datos de entrada en las funciones y la variable objetivo.

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']

Luego distribuiremos los datos en muestras de entrenamiento y de prueba.

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

En este caso, el tamaño de la muestra de prueba será del 20%; normalmente no se seleccionará más del 30% para los casos de prueba (para evitar el sobreentrenamiento).

Inicialización secuencial del modelo:

model = Sequential()

Esta línea creará un modelo secuencial vacío que nos permitirá añadir capas de forma incremental.

Luego añadiremos las capas 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)))

Una capa densa es una capa totalmente conectada de una red neuronal.

Los números entre paréntesis indicarán el número de neuronas de cada capa. Así, la primera capa constará de 128 neuronas, la segunda de 256 neuronas, la tercera de 128 neuronas y la cuarta de 64 neuronas.

La función de activación relu (Rectified Linear Unit) se usará para introducir la no linealidad después de cada capa, lo que ayudará al modelo a aprender patrones complejos.

El parámetro input_shape se especificará solo en la primera capa y definirá la forma de los datos de entrada. En este caso, será igual al número de características de los datos de entrada.

kernel_regularizer=l2(k_reg) aplicará la regularización L2 a los pesos de las capas y ayudará a evitar el sobreentrenamiento penalizando los valores de peso grandes.

Capa de salida:

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

  • La última capa constará de una sola neurona, que es típica para una tarea de regresión (predicción de un valor continuo).
  • Utilizaremos una función de activación lineal: la salida será una combinación lineal de las entradas sin más transformación.
  • Es decir, este modelo constará de varias capas densas con funciones de activación de unidades lineales rectificadas, seguidas de una capa de salida lineal. Para la regularización, se aplicará L2 a los pesos. Esta arquitectura se utiliza normalmente para problemas de regresión en los que el objetivo es predecir un valor numérico.

Ahora compilaremos el modelo

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

Optimizador:

El optimizador es un componente fundamental del proceso de aprendizaje. Determina cómo se actualizan los pesos del modelo durante el entrenamiento para minimizar la función de pérdida. Adam es un popular algoritmo de optimización que resulta bastante eficaz para entrenar redes neuronales. Ajusta el ritmo de aprendizaje de cada parámetro de forma individual y, por tanto, resulta adecuado para una amplia gama de tareas.

Función de pérdida:

El parámetro de pérdida definirá el objetivo que el modelo intenta minimizar durante el entrenamiento. En nuestro caso, utilizaremos mean_squared_error como función de pérdida. El error cuadrático medio (ECM) se utilizará a menudo para los problemas de regresión. El objetivo será minimizar la diferencia RMS entre los valores previstos y los reales. Esto resultará adecuado para aplicaciones en las que la señal de salida es un valor continuo. El MAE calculará la media de las diferencias absolutas entre los valores previstos y los valores reales.

mae y mse

Todos los errores se tratarán con la misma importancia, independientemente de su dirección. Un valor MAE más bajo también implicará una mayor eficacia del modelo.

Así, el operador model.compile configurará el modelo de red neuronal para el entrenamiento, y definirá un optimizador (Adam) para actualizar los pesos durante el entrenamiento, así como una función de pérdida (mean_squared_error). Esta etapa de compilación será un paso previo necesario para entrenar el modelo con datos.

Nosotros entrenaremos un modelo de red neuronal previamente definido.

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

X_train_scaled - datos de entrada del objeto para el entrenamiento, presumiblemente escalados o preprocesados para lograr la estabilidad numérica.

y_entrenamiento - valores objetivo o etiquetas correspondientes a los datos de entrenamiento.

Configuración del entrenamiento:

epochs=int(epoch) - este parámetro indica cuántas veces se ejecuta el conjunto completo de datos de entrenamiento en pasadas directas e inversas a través de la red neuronal.

int(epoch) - indica que el número de épocas está definido por la variable epoch.

batch_size=256 - durante cada época, los datos de entrenamiento se dividirán en lotes, mientras que los pesos del modelo se actualizarán después de procesar cada lote. Aquí, cada paquete constará de 256 puntos de datos.

Datos de validación:

validation_split=0.2 - el parámetro indica que el 20% de los datos de entrenamiento se utilizará como conjunto de validación. El rendimiento del modelo en este conjunto se controlará durante el entrenamiento, pero no se utilizará para actualizar las ponderaciones.

Visualización:
verbose=1 - el parámetro controla la salida de los resultados del entrenamiento. Un valor de 1 significa que el progreso del aprendizaje se mostrará en la consola.

Durante el entrenamiento, el modelo aprenderá a hacer predicciones ajustando sus pesos en función de los datos de entrada proporcionados (X_train_scaled) y los valores objetivo (y_train). Separar la muestra de validación nos permitirá evaluar el rendimiento del modelo en datos desconocidos, mostrando el progreso del entrenamiento según los ajustes.


Bloque lineal

Al trabajar con redes neuronales, lo correcto será empezar a estudiarlas por el componente básico: la neurona. Esquemáticamente, una neurona tendrá este aspecto si está sintonizada con un único parámetro de entrada:

y = x*w + b

Mecánica del bloque lineal

Ahora veremos el componente principal de una red neuronal: la neurona. Visualmente, una neurona con una sola entrada x se representará de la siguiente manera:

El parámetro de entrada, denotado x, formará una conexión con una neurona, y a esta conexión se le asignará un peso, denotado w. Cuando la información pase a través de esta conexión, el valor se multiplicará por el peso asignado a la conexión. Si la entrada es x, entonces el producto de w * x llegará finalmente a la neurona. Ajustando estos pesos, la red neuronal aprenderá con el tiempo.

Ahora introduciremos b, una forma especial de ponderación llamada desplazamiento. A diferencia de otros pesos, el desplazamiento no tendrá datos de entrada asociados. Aquí, se insertará un valor de 1 en el gráfico para garantizar que el valor que llegue a la neurona sea simplemente b (ya que 1 * b es igual a b). Introduciendo un desplazamiento, la neurona será capaz de cambiar su señal de salida independientemente de los datos de entrada.

Y = X*W + b*1


Utilización de varios conjuntos de datos de entrada

¿Y si necesitamos incluir más factores? También será bastante fácil de hacer. Ampliando nuestro modelo, podremos añadir fácilmente conexiones de entrada adicionales a la neurona, cada una correspondiente a una función específica.

Para obtener el resultado, seguiremos unos sencillos pasos. Cada parámetro de entrada se multiplicará por el peso compuesto correspondiente y se combinarán los resultados. El resultado será una representación holística en la que la neurona procesará hábilmente múltiples entradas. Esto hará que el modelo sea más detallado y refleje la compleja interacción de las distintas funciones. Este método permitirá a nuestra red neuronal captar una gama más amplia de información, lo cual mejorará su capacidad para reconocer patrones de forma exhaustiva.

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

Matemáticamente, el funcionamiento de esta neurona se resumirá en la fórmula:

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

Donde:

  • y representará la salida de la neurona.
  • w 0 , w 1 , w 2 denotarán los pesos vinculados a los datos de entrada correspondientes x 0 , x 1 , x 2.
  • b significará el desplazamiento.

Esta unidad lineal de doble entrada tiene la capacidad de modelar un plano en un espacio tridimensional. Cuando el número de entradas sea superior a dos, el módulo será capaz de ajustar hiperplanos, es decir, superficies multidimensionales que representan de forma compleja las relaciones entre varios objetos de entrada. Esta flexibilidad permite a la red neuronal navegar y comprender patrones complejos en los datos más allá de las simples relaciones lineales.

Bloques lineales en Keras

Una red neuronal en Keras puede ser creada con la ayuda de la utilidad `keras.Sequential()`. Esta monta una red neuronal superponiendo capas. Es una forma fácil y cómoda de crear un modelo. La arquitectura de una red neuronal presupone la presencia de capas. Los modelos que hemos visto se construyen partiendo de lo que se denominan capas densas.

model = Sequential()

A continuación, profundizaremos en los detalles de la capa densa y exploraremos sus capacidades y su papel en la construcción de la arquitectura de las redes neuronales.


Redes neuronales profundas

Podemos aumentar profundidad de la red neuronal integrando capas ocultas. Estas capas ocultas permitirán captar y revelar relaciones complejas dentro de los datos, de forma que la red neuronal pueda discernir y captar patrones complejos. Añadiendo capas ocultas al modelo, este tendrá la capacidad de aprender y presentar características detalladas para obtener predicciones más completas y precisas.

Construcción de redes neuronales complejas

Ahora pasaremos a construir redes neuronales capaces de comprender interconexiones complejas. Esto se aplicará a las redes neuronales profundas.

Para ello resultará fundamental el concepto de modularidad, una estrategia que consiste en combinar una compleja red de unidades funcionales elementales. Antes hemos visto cómo un bloque lineal calcula una función lineal. El siguiente paso consistirá en combinar y adaptar estos bloques individuales. Combinando y modificando los componentes básicos, las redes neuronales sofisticadas podrán comprender las relaciones más complejas y polifacéticas inherentes a las muestras complejas. La creación de redes neuronales capaces de detectar la comprensión de patrones complejos y sutiles entra en el ámbito del aprendizaje profundo.

Capas de redes neuronales

En la compleja arquitectura de las redes neuronales, las neuronas se organizan en capas. En este sentido, la configuración de capa densa que consideramos será una unión de bloques lineales que comparten un conjunto común de datos de entrada.

Esta disposición ofrece una estructura interconectada que permitirá a las neuronas de la capa procesar e interpretar colectivamente la información. El diseño de capas densas ilustra cómo las neuronas pueden contribuir de forma colectiva a la capacidad de una red para comprender y aprender relaciones complejas dentro de los datos.

Datos de entrada, datos de salida densos

 


Capas en Keras

En el paradigma de la biblioteca Keras, una capa será una entidad bastante universal. De hecho, se manifestará en cualquier forma de transformación de datos. Hay una serie de capas, como las convolucionales y las recurrentes, que utilizan neuronas para metamorfosear los datos, y que se distinguen principalmente por los complejos patrones de conexiones que crean. También hay otros niveles que servirán para diferentes propósitos, desde el diseño de características hasta la aritmética elemental. Así, dentro de la estructura modular de una red neuronal, podremos organizar una amplia gama. La diversidad de capas pone de relieve la adaptabilidad y las amplias capacidades que contribuirán a la rica variedad de arquitecturas de redes neuronales.

Ampliación de redes neuronales con funciones de activación

Sorprendentemente, la combinación de dos capas densas desprovistas de elementos intermedios no resulta más eficaz que una sola capa densa. Las capas densas están aisladas por estructuras lineales que no podrán extenderse más allá de los límites de líneas y planos. Para evitar esta linealidad, introduciremos un elemento lógico: la no linealidad. Para ello se utilizarán funciones de activación.

Al añadir una función de activación, se introducirá la no linealidad en la red neuronal. Esta mayor complejidad del modelo le permitirá discernir pautas y relaciones complejas en los datos. En esencia, las funciones de activación suponen catalizadores que ayudarán a las redes neuronales a captar los matices inherentes a los distintos conjuntos de datos.

Por ejemplo, si combinamos un rectificador (función de activación) con una unidad lineal, el resultado será una enorme entidad conocida como unidad lineal rectificada o ReLU. En general, la función rectificadora propiamente dicha suele denominarse "función ReLU". La aplicación de la función de activación ReLU al bloque lineal convertirá la salida en max(0, w * x + b). Esquemáticamente, esto podría representarse del modo que sigue:

w*x + b

Distribución multinivel con redes densas

Usando la no linealidad recién aprendida, exploraremos las posibilidades de la superposición de capas para organizar transformaciones de datos complejas.

Capa de entrada, oculta y de salida

Capas ocultas en las redes neuronales

Las capas intermedias que preceden a la capa de salida suelen denominarse capas ocultas, pues su salida permanece oculta a la observación directa.

Obsérvese que la capa final (de salida) adoptará la forma de un bloque lineal sin función de activación. Esta arquitectura se corresponderá con tareas de regresión cuyo objetivo es predecir un valor numérico. Sin embargo, tareas como la clasificación podrían requerir la inclusión de una función de activación en la capa de salida.


Construcción de modelos coherentes

El modelo secuencial que utilizamos enlazará una serie de capas de forma secuencial, desde la capa inicial hasta la final. En esta estructura, el primer nivel servirá de receptor de los datos de entrada, mientras que el último culminará en la creación del resultado deseado. Este montaje secuencial refleja el modelo representado en la ilustración anterior:

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),
])


Para enlazar capas, deberemos representar todas las capas juntas en una lista, por ejemplo [capa, capa, capa, capa, ...], en lugar de enumerarlas individualmente. Para incluir una función de activación en una capa, bastará con especificar su nombre en el argumento de activación. Este enfoque ofrecerá una representación concisa y organizada de la arquitectura de la red neuronal.

Selección del número de bloques de una capa densa

La decisión sobre el número de unidades de la capa Layers.Dense (por ejemplo, Layers.Dense(units=4, ...) ) dependerá de las características específicas del problema en cuestión y de la complejidad de los patrones a descubrir en los datos. Se considerarán los siguientes factores:

Complejidad de la tarea:

  • Para tareas más sencillas con relaciones menos complejas en los datos, un número menor de bloques, como 4, podría ser un punto de partida adecuado.
  • En escenarios más complejos, caracterizados por relaciones sutiles y polifacéticas, suele ser necesario seleccionar más bloques.

Tamaño de los datos:

  • El tamaño de la muestra es importante: las muestras más grandes pueden admitir un mayor número de bloques sobre los que el modelo puede aprender.
  • Los conjuntos de datos más pequeños requerirán un enfoque más cauteloso para evitar el sobreentrenamiento y el posible ruido de entrenamiento del modelo.

Capacidad del modelo:

  • El número de bloques afectará a la capacidad del modelo para captar patrones complejos; en este caso, además, el aumento de bloques suele mejorar dichas capacidades.
  • Le recomendamos tener cuidado y evitar una parametrización excesiva, especialmente cuando al tratar con datos limitados, ya que esto puede llevar a un sobreentrenamiento.

Experimentos:

  • Experimente con distintas configuraciones, empezando con un pequeño número de bloques que entrenen el modelo y mejórelo en función del rendimiento y las observaciones.
  • Métodos como la validación cruzada ofrecen información sobre la eficacia de la generalización de modelos para distintos conjuntos de datos.

Recuerde que la elección del número de bloques no es universal, a veces deberá recurrir al método de ensayo y error. La supervisión del rendimiento del modelo en la muestra de validación y el ajuste iterativo de la arquitectura serán también una parte integrante del proceso de desarrollo del modelo.

En nuestro caso,utilizaremos la siguiente opción:

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

Primera capa (Capa de entrada):

Bloques (128) - un número relativamente mayor de unidades (128) de la primera capa permitirá al modelo captar patrones diversos y complejos en los datos de entrada. Esta variante puede ser útil para extraer características complejas en las fases iniciales de la red. Activación - la función de activación de ReLU introduce la no linealidad, lo cual permitirá al modelo aprender de relaciones y patrones complejos. Regularización (L2) - L2 (kernel_regularizer=l2(k_reg)) ayuda a evitar el sobreentrenamiento penalizando los pesos grandes en la capa.

Segunda y la tercera capa:

Bloques (256 y 128) - sirve para mantener más bloques en las capas siguientes (256 y 128), seguirá permitiendo al modelo recoger y procesar información compleja. La reducción gradual del número de bloques ayudará a crear una jerarquía de funciones. Activación - utiliza también ReLU para posibilitar la no linealidad en cada nivel. Regularización (L2) - la aplicación coherente de la regularización de L2 a diferentes niveles ayudará a evitar el sobreentrenamiento.

Cuarta capa: 

Bloques (64) - la reducción de las unidades mejorará aún más la presentación de las características, ayudando a resaltar la información importante a la vez que se mantiene un equilibrio entre complejidad y simplicidad. Activación - nuevamente activación ReLU, conservará las propiedades no lineales. Regularización (L2) - se aplicará para garantizar la estabilidad.

Nivel 5 (nivel de salida):

Bloques (1) - la última capa con una unidad será muy adecuada para problemas de regresión en los que el objetivo es predecir un valor numérico continuo. Activación (linear) - esta activación será adecuada para la regresión, ya que permite que el modelo emita directamente el valor predicho sin transformaciones adicionales.

En general, la arquitectura elegida parece adaptada a la tarea de regresión con un equilibrio sensato entre potencia expresiva y regularización para evitar el sobreentrenamiento. La reducción gradual del número de bloques facilitará la extracción de características jerárquicas. Esta arquitectura implica una comprensión profunda de la complejidad del problema y un intento de construir un modelo que pueda resumir bien los datos no vistos.

Así, la reducción gradual del número de módulos en las capas ocultas, además de las elecciones específicas para cada capa, sugieren una estructura destinada a recoger representaciones jerárquicas y abstractas de los datos de entrada. La arquitectura equilibrará la complejidad del modelo asociada a la necesidad de hacer frente al sobreentrenamiento, mientras que la elección del número de bloques resultará adecuada para la naturaleza del problema de regresión actual. Las cifras concretas podrán determinarse experimentando con los ajustes basados en el rendimiento del modelo en los datos de validación.


Compilación de modelos

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

Ya hemos examinado la creación de redes totalmente conectadas utilizando pilas de capas densas. En la fase inicial de creación, los pesos de la red se establecen aleatoriamente, y esto significa que la red carece de conocimientos previos. Vamos a pasar ahora al proceso de entrenamiento de la red neuronal.

Como es habitual en el campo del aprendizaje automático, partiremos de una muestra de datos de entrenamiento cuidadosamente seleccionada. Cada ejemplo de este conjunto de datos incluirá características (datos de entrada) y un objetivo previsto (datos de salida). La esencia del entrenamiento de la red consistirá en ajustar sus pesos para transformar las características de entrada en predicciones precisas de las salidas objetivo.

Entrenar una red para una tarea de este tipo implicará que sus pesos encapsulen hasta cierto punto la relación entre estas características y el objetivo.

Más allá de los datos de formación, aquí entrarán en juego dos componentes de importancia crítica:

  1. La función de pérdida, que medirá la eficacia de las predicciones de la red.
  2. El optimizador, que se encargará de indicar a la red cómo ajustar iterativamente sus pesos para mejorar el rendimiento.

El principal objetivo al construir una red neuronal será desarrollar su capacidad de generalizar y hacer predicciones precisas a partir de datos desconocidos.

Función de pérdida

Ya hemos visto el diseño arquitectónico de la red, pero aún tenemos que explorar cómo aprende la red sobre el problema concreto que debe resolver. Esta responsabilidad recaerá en la función de pérdida.

En esencia, la función de pérdida cuantificará la diferencia entre el valor real del objetivo y el valor predicho por el modelo. Servirá como criterio para evaluar la concordancia entre las predicciones del modelo y los resultados reales.

Una función de pérdida comúnmente usada en problemas de regresión es el error medio absoluto (MAE). En el contexto de cada previsión, denotada como y_pred, MAE estimará la diferencia con el valor objetivo verdadero y_true calculando la diferencia absoluta abs(y_true - y_pred).

El error MAE acumulado en la muestra se calculará como la media de todas estas diferencias absolutas. Este indicador ofrecerá una medida global del error medio de previsión, orientando el modelo para minimizar el desajuste global entre sus previsiones y los objetivos reales.

mae

El error medio absoluto MAE representará la distancia media entre la curva ajustada y los puntos de datos reales.

Además del MAE, con frecuencia se utilizan funciones de pérdida alternativas, como el error cuadrático medio (MSE) y la función de pérdida de Huber. Encontrará ambos elementos en la biblioteca Keras.

Durante el proceso de entrenamiento, el modelo utilizará la función de pérdida como referencia para determinar los valores óptimos de los pesos, buscando la menor pérdida posible. En esencia, la función de pérdida comunicará el propósito de la red, guiándola para que aprenda y mejore sus parámetros con el fin de perfeccionar su precisión de predicción.

El optimizador será el descenso de gradiente estocástico

El siguiente paso, una vez definido el problema que debe resolver la red, será determinar cómo resolverlo. Para ello se utilizará un optimizador, es decir, un algoritmo diseñado para ajustar los pesos con el fin de minimizar las pérdidas.

En el campo del aprendizaje profundo, la mayoría de los algoritmos de optimización se encuadran en el descenso de gradiente estocástico. Se trata de algoritmos iterativos que entrenan gradualmente la red. Cada etapa de aprendizaje seguirá esta secuencia:

  1. Primero tomaremos algunos datos de entrenamiento y los introduciremos en la red para generar predicciones.
  2. Luego estimaremos las pérdidas comparando las predicciones con los valores reales.
  3. Después ajustaremos los pesos en la dirección de la reducción de la pérdida.

Este proceso se repetirá de forma iterativa hasta alcanzarse el nivel deseado de reducción de pérdidas o hasta que una mayor reducción resulte inviable. Básicamente, el optimizador guiará a la red a través de los ajustes de los pesos, orientándola hacia una configuración que minimice las pérdidas y mejore la precisión de la predicción.

Cada conjunto de datos de entrenamiento seleccionado en cada iteración se denominará minilote o simplemente lote. Por otra parte, el conjunto completo de datos de entrenamiento se denominará época. El número especificado de épocas determinará cuántas veces procesa la red cada ejemplo de entrenamiento.

Velocidad de aprendizaje y tamaño de los paquetes

En cada paquete, solo veremos un pequeño desplazamiento, no un cambio de datos completo. La magnitud de estos cambios vendrá determinada por la velocidad de aprendizaje. Una tasa de aprendizaje más baja significa que la red necesitará estar expuesta a más paquetes antes de que sus pesos se ajusten a los valores óptimos.

La velocidad de aprendizaje y el tamaño de los paquetes afectarán a la trayectoria de aprendizaje del SGD. Comprender sus interacciones puede resultar sutil, y la elección óptima no siempre será obvia.

Afortunadamente, para la mayoría de los problemas, no será necesaria una búsqueda exhaustiva de hiperparámetros óptimos para obtener resultados satisfactorios. Por ejemplo, con el algoritmo estocástico Adam, no será necesario un ajuste avanzado de los parámetros. Su capacidad de autoajuste lo convertirá en un excelente optimizador polivalente adecuado para una amplia gama de tareas.

En este ejemplo, elegiremos ADAM como descenso de gradiente estocástico y MSE como función de pérdida.

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

En el entrenamiento

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

obtendremos algo como esto:

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


Ajuste y entrenamiento insuficientes

Keras realizará un seguimiento de las pérdidas de entrenamiento y validación a lo largo de las épocas mientras se entrena el modelo. Nosotros estudiaremos la interpretación de estas curvas de aprendizaje y descubriremos cómo utilizarlas para mejorar el desarrollo de modelos. En concreto, analizaremos las curvas de aprendizaje para identificar signos de infraentrenamiento y sobreentrenamiento, y estudiaremos varias estrategias para resolver estos problemas.

Interpretación de las curvas de aprendizaje:

La información sobre los datos de entrenamiento puede dividirse en dos componentes: señal y ruido. La señal es la parte resumida que ayudará a nuestro modelo a hacer predicciones basadas en nuevos datos. El ruido incluirá fluctuaciones aleatorias derivadas de datos reales y patrones no informativos que no contribuirán a la capacidad de predicción del modelo. Resulta esencial saber identificar y comprender el ruido.

Durante el entrenamiento del modelo, nuestro objetivo consistirá en elegir pesos o parámetros que minimicen la pérdida en el conjunto de entrenamiento. Pero para evaluar exhaustivamente la eficacia del modelo, deberemos evaluarlo con un nuevo conjunto de datos: la muestra de validación.

Por ello, para nosotros será importante analizar dichas curvas para entrenar con éxito los modelos de aprendizaje profundo.

curva de aprendizaje

El error en el entrenamiento se reducirá cuando el modelo reciba una señal, o bien ruido. Pero en la muestra de validación, el error solo se reducirá cuando el modelo aprenda la señal, ya que ningún ruido de la muestra de entrenamiento podrá generalizarse a los nuevos datos. En consecuencia, cuando el modelo aprenda la señal, ambas curvas descenderán y el ruido de entrenamiento creará una brecha entre ellas. La magnitud de esta discontinuidad indicará el grado de ruido que recibe el modelo.

over_under_fitting



En un mundo ideal, nuestro objetivo sería construir un modelo que aprendiera todas las señales e ignorara el ruido. Sin embargo, en el mundo real, eso resulta prácticamente imposible. Así que nos veremos obligados a transigir. Podemos estimular al modelo para que aprenda más señales a costa de producir más ruido. Mientras este compromiso nos sea favorable, el error de validación seguirá disminuyendo. Llegará un momento en que la compensación deja de resultar rentable, los costes superarán a los beneficios y los errores en la muestra de validación empezarán a aumentar.


Esta compensación pone de manifiesto dos problemas potenciales en el entrenamiento del modelo: la señal insuficiente y el ruido excesivo. El infraentrenamiento se producirá cuando la pérdida no tenga minimización de errores debido a que el modelo no asimila suficiente señal. El sobreentrenamiento se producirá cuando el error no se reduzca porque el modelo ha absorbido demasiado ruido. Por lo tanto, nuestro objetivo será encontrar el mejor equilibrio entre estas dos opciones.

El gráfico tendrá ahora este aspecto:

Ajuste y entrenamiento insuficiente

Capacidad del modelo:

La capacidad del modelo influirá en su capacidad para captar y comprender patrones complejos. En el contexto de las redes neuronales, esto se verá influido principalmente por el número de neuronas y su interconectividad. Si parece que nuestra red no capta adecuadamente la complejidad de los datos (está insuficientemente entrenada), deberemos considerar la posibilidad de aumentar su capacidad.

La capacidad de la red puede aumentarse ampliándola (añadiendo más dispositivos a las capas existentes) o profundizándola (incluyendo más capas). Las redes más amplias destacan en el aprendizaje de relaciones más lineales, mientras que las redes más profundas tienden a captar patrones menos lineales. La elección dependerá de la naturaleza de la muestra.

Parada anticipada:

Como ya hemos comentado, cuando un modelo incluye mucho ruido durante el entrenamiento, el error de validación puede empezar a crecer. Para evitar este problema, podremos añadir una parada anticipada, una técnica con la que detendremos el proceso de entrenamiento en cuanto resulte obvio que el error en los datos de validación ya no disminuye. Esta intervención ayudará a evitar el sobreentrenamiento y garantizará una buena generalización del modelo a los nuevos datos.

Cuando observemos un aumento del error de validación, podremos restablecer los pesos al punto en el que se registró el mínimo. De este modo, el modelo no se entrenará constantemente con ruido, y podremos evitar el sobreentrenamiento.

La aplicación del entrenamiento con parada anticipada también reducirá el riesgo de detener prematuramente el proceso de aprendizaje antes de que la red haya captado completamente la señal. Además de combatir el sobreentrenamiento debido a periodos de entrenamiento excesivamente largos, la parada anticipada servirá como defensa contra el infraentrenamiento causado por un periodo de entrenamiento insuficiente. Así, podremos establecer un número bastante grande de épocas de entrenamiento (más de las necesarias), y la parada anticipada permitirá gestionar la finalización según el cambio en el error de validación.

Integración de la parada anticipada:

En Keras, la incorporación de la parada anticipada en nuestro entrenamiento se realizará a través de una llamada de retorno. La llamada de retorno es esencialmente una función que se ejecutará a intervalos regulares durante el proceso de entrenamiento de la red. Esta función para el entrenamiento temprano se activará después de cada época. Keras tiene una serie de llamadas de retorno predefinidas, y también podrá crear solicitudes personalizadas para requisitos específicos.

Código con uso de Keras:

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)


Y también añadiremos más bloques y otra capa oculta (una vez personalizado, el modelo .py se volverá más complejo).

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

Estos parámetros contendrán la siguiente instrucción: "Si el error de validación no mejora en al menos 0,001 respecto a las 20 épocas anteriores, interrumpiremos el entrenamiento y nos quedaremos con el modelo de mejor rendimiento encontrado hasta ese momento". Determinar si el error de validación aumentará debido al sobreentrenamiento o simplemente a cambios aleatorios en el paquete puede resultar a veces complicado. Estos parámetros permitirán establecer tolerancias específicas, indicando al sistema cuándo debe detener el proceso de entrenamiento.

Hemos fijado el número de épocas en 300 con la esperanza de completar antes el proceso de aprendizaje.

Trabajo con contenedores

La presencia de omisiones en la muestra puede ser provocada por diversas circunstancias.

Al trabajar con bibliotecas de aprendizaje automático como scikit-learn, el intento de construir un modelo utilizando datos que faltan suele dar lugar a un error. Por lo tanto, para resolver este problema deberemos adoptar una de las siguientes estrategias.

Tres enfoques

  1. La solución optimizada (descartar nan) consistirá en excluir las columnas con valores faltantes. Un método sencillo sería eliminar las columnas que contienen valores omitidos.
df2 = df2.dropna()

No obstante, si falta una parte significativa de los valores de las columnas descartadas, la elección de este enfoque hará que el modelo pierda acceso a una cantidad significativa de información potencialmente valiosa. Para ilustrarlo, imaginemos un conjunto de datos de 10 000 filas al que solo le falta un registro en una columna importante. Con esta estrategia, se dará la eliminación de toda la columna.

2) Alternativa mejorada: imputación

La imputación consiste en completar los valores que faltan con valores numéricos específicos. Por ejemplo, podemos especificar el valor medio de cada columna.

Aunque el valor añadido puede resultar inexacto en la mayoría de los casos, este método suele producir modelos más precisos en comparación con descartar la fila por completo.


3) Desarrollo de técnicas de imputación

Este enfoque tradicional suele resultar eficaz. no obstante, los valores estimados de forma condicional pueden desviarse sistemáticamente de los valores reales (no presentes en el conjunto de datos). Además, las filas con valores faltantes pueden tener características diferentes. En estos casos, mejorar el modelo para considerar la originalidad de los valores faltantes puede mejorar la precisión de la predicción.

En esta metodología, seguiremos añadiendo los valores que faltan como hemos descrito antes. Además, para cada columna con registros faltantes en el conjunto de datos original, introduciremos una nueva columna que indicará las posiciones de los registros añadidos.

Aunque este método puede mejorar significativamente los resultados en determinados escenarios, su eficacia varía, y en algunos casos podría no ofrecer ninguna mejora.

Resultados del modelo ONNX

1 Descarga de datos.

Ya tenemos una comprensión básica del archivo .py que hemos creado para entrenar el modelo. Ahora pasaremos a su entrenamiento. 

Anotamos las rutas:

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

# create dataframe
df = pd.DataFrame(eurusd_rates)

Este es el aspecto final del código (GRU_create_model.py):

Al realizar el entrenamiento, obtendremos estos 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

Al igual que en el artículo Forex exchange rate forecasting using deep recurrent neural networks, los resultados para GRU y LTSM son similares. 

Artículo

paper_table


Tras ejecutar ONNX_GRU.py, obtendremos el modelo ONNX en la misma carpeta donde se encuentra el archivo Python para el entrenamiento (ONNX_GRU.py). Este modelo ONNX deberá guardarse en la carpeta MQL5 Files para poder llamarlo desde el Asesor Experto.

Encontrará el asesor experto en los anexos del artículo.


De esta forma podrá probar el modelo con un simulador de estrategias o incluso utilizarlo en el trading.


Comparación de LSTM y GRU


Comparación de LSTM y GRU

La célula LSTM mantiene un estado de célula desde el que lee y escribe simultáneamente. El mecanismo incluye cuatro tipos de puertas que controlarán los procesos de lectura, escritura y salida de los valores hacia y desde el estado de la célula según los valores de entrada y los valores del estado de la célula. La puerta inicial definirá la información que el estado oculto debe olvidar. Las puertas siguientes se encargarán de identificar el segmento de estado de la célula que se va a registrar. La tercera puerta definirá el contenido que se va a escribir. Finalmente, la última puerta extraerá información del estado de la célula para generar datos de salida.

LSTM


La célula GRU tiene similitudes con la célula LSTM, pero presentará algunas diferencias significativas. En primer lugar, carece de estado oculto porque se supone que la funcionalidad del estado oculto en el diseño de la célula LSTM será el estado de la célula. Por lo tanto, los procesos de decisión sobre qué olvidará el estado de la célula y en qué parte del estado de la célula se escribirá se combinarán en una única puerta. Entonces solo se escribirá la parte del estado de la célula que se ha borrado. Por último, el estado completo de la célula servirá como datos de salida, a diferencia de la célula LSTM, que leerá selectivamente el estado de la célula para generar datos de salida. De esta forma obtendremos un diseño más simple con menos parámetros en comparación con LSTM. Sin embargo, la reducción de los parámetros podría provocar una disminución de la capacidad expresiva.

GRU


Comparación 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()])


Valoración de una ventana móvil


Adjunto al artículo encontrará un archivo .py para comparar LSTM y GRU, además de cross validation.py.

También tenemos GRU simple.py para crear modelos ONNX.

Utilizando un modelo GRU simple, obtendremos estos resultados para enero de 2024.

Pruebas con la historia

Gráfico


Conclusión y trabajo futuro

Esta comparación nos ayudará a determinar qué modelo utilizar en un caso concreto. También podremos considerar el uso de ambos modelos. Este enfoque permitirá extraer información importante de los modelos a pesar de las diferencias inherentes al tamaño de los paquetes y las configuraciones de las capas. En las mismas condiciones, el modelo GRU resulta más rápido.

El trabajo futuro se beneficiaría de la exploración de diferentes núcleos e inicializadores recurrentes adaptados a cada tipo de célula para posibles mejoras de rendimiento.

Un buen enfoque para trabajar con modelos ONNX sería integrar ambos modelos en un EA. Le recomiendo la lectura del artículo: Ejemplo de un conjunto de modelos ONNX en MQL5.


Conclusión

Modelos como GRU son capaces de producir buenos resultados y parecen bastante fiables. Espero que le haya gustado el artículo. En él hemos presentado una comparación de los modelos GRU y LSTM. También hemos añadido algo de código python para ayudar a detener las épocas (considerando la cantidad de datos de entrada).


Observación

Los resultados pasados no nos garantizan resultados futuros.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14113

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Aprendizaje automático y Data Science (Parte 21): Desbloqueando las redes neuronales: desmitificando los algoritmos de optimización Aprendizaje automático y Data Science (Parte 21): Desbloqueando las redes neuronales: desmitificando los algoritmos de optimización
Sumérjase en el corazón de las redes neuronales mientras desmitificamos los algoritmos de optimización utilizados dentro de la red neuronal. En este artículo, descubra las técnicas clave que liberan todo el potencial de las redes neuronales, impulsando sus modelos a nuevas cotas de precisión y eficacia.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Hibridación de algoritmos basados en poblaciones. Esquema secuencial y paralelo Hibridación de algoritmos basados en poblaciones. Esquema secuencial y paralelo
En este artículo, nos sumergiremos en el mundo de la hibridación de algoritmos de optimización analizando tres tipos clave: la mezcla de estrategias y la hibridación secuencial y paralela. Asimismo, realizaremos una serie de experimentos combinando y probando los algoritmos de optimización correspondientes.