English Deutsch 日本語
preview
Aprendizaje automático y Data Science (Parte 22): Aprovechar las redes neuronales de autocodificadores para realizar operaciones más inteligentes pasando del ruido a la señal

Aprendizaje automático y Data Science (Parte 22): Aprovechar las redes neuronales de autocodificadores para realizar operaciones más inteligentes pasando del ruido a la señal

MetaTrader 5Indicadores | 16 septiembre 2024, 10:23
47 0
Omega J Msigwa
Omega J Msigwa

¿Qué son los autocodificadores?

Los autocodificadores son redes neuronales artificiales no supervisadas. En su forma más simple, un autoencoder es una red neuronal que intenta dos cosas. Comprime los datos de entrada en una dimensión inferior e intenta utilizar esta representación dimensional inferior de los datos para recrear la entrada original.

Supongamos que se pasa una imagen borrosa de un gato al autocodificador, esta imagen se comprimirá y descomprimirá de nuevo a su estado original perdiendo algunos de sus píxeles ruidosos/borrosos a lo largo del proceso para terminar con una imagen clara de un gato.

Imagen borrosa de un gato frente a imagen no borrosa

En este artículo, veremos cómo podemos utilizar una red neuronal con autocodificador en el espacio financiero para ayudarnos a eliminar el ruido del mercado y así poder descubrir oportunidades de negociación.

Este artículo es una lectura fácil si usted tiene una comprensión básica de ONNX, PCA, y Redes Neuronales en general.

Un autocodificador consta de dos partes:

  1. Un codificador toma los datos de entrada y los comprime en una representación latente de menor dimensión, capturando las características esenciales.
  2. Un decodificador recibe la representación latente e intenta reconstruir los datos de entrada originales con la mayor precisión posible.

Ventajas de los autocodificadores:

  • Pueden ser útiles para tareas de reducción de la dimensionalidad, ya que pueden aprender una representación comprimida de los datos de comercio de divisas, lo que resulta útil para tareas como la extracción de características, la compresión de datos y la visualización en conjuntos de datos de alta dimensión.
  • Al intentar reconstruir los datos de entrada, el autocodificador aprende las características esenciales y elimina el ruido o la información irrelevante. Estas características aprendidas pueden ser beneficiosas para otras tareas de aprendizaje automático, como la clasificación o la detección de anomalías.
  • Al no estar supervisados, pueden descubrir patrones ocultos en los datos comerciales sin interacción humana.
  • La representación latente aprendida de un autocodificador puede utilizarse como características preentrenadas para otros modelos, mejorando potencialmente su rendimiento.


¿De qué se componen?

Diseccionemos los autocodificadores y observemos de qué están compuestos y qué los hace especiales.

El núcleo de un autocodificador es una red neuronal artificial que consta de tres partes.

  1. El codificador
  2. El vector de incrustación/capa latente
  3. El descodificador

Arquitectura sencilla del autocodificador

La parte izquierda de la red neuronal se denomina codificador. Su trabajo consiste en transformar los datos de entrada originales en una representación de menor dimensión.

La parte intermedia de la red neuronal se denomina capa latente o vector de incrustación, su función es comprimir los datos de entrada en datos de menor dimensión. Se espera que esta capa tenga menos neuronas que el codificador y el decodificador.

La parte derecha de esta red neuronal se denomina descodificador. Su trabajo consiste en recrear la entrada original utilizando la salida del codificador. En otras palabras, intenta invertir el proceso de codificación.

Esto es fascinante porque el descodificador intenta recrear un dato de mayor dimensión a partir de un dato de menor dimensión devuelto por el codificador. Algo así como intentar construir una casa mirando una foto de una.

Casa en 1D frente a 3D

Esto fuerza la pérdida de información, que es clave para que todo este proceso funcione. Haciendo que el descodificador tenga información imperfecta y entrenando a toda la red para minimizar el error de construcción. Durante el entrenamiento, el codificador y el descodificador se ven obligados a trabajar juntos para minimizar el error de construcción.

Error de construcción es la diferencia entre el intento de recreación y los datos de entrada originales.

Si no hubiera pérdida de información entre el codificador y el decodificador, la red aprendería simplemente a multiplicar la entrada por uno y obtendría una reconstrucción perfecta, lo que haría inútil el autocodificador. Tener un codificador con cierto grado de errores es crucial para esta técnica de aprendizaje automático, asegúrate de no sobreajustar tu modelo.

Tanto los codificadores como los descodificadores no se limitan a una sola capa, como puede verse en la imagen de la arquitectura del autocodificador mostrada más arriba. Puede contener múltiples capas como se ve en el siguiente código Python donde tenemos una lista llamada hidden_dims para almacenar las neuronas de las capas codificadora y decodificadora.

Python:

class Autoencoder(Model):
  def __init__(self, input_dim, latent_dim, hidden_dims=[]):
    super(Autoencoder, self).__init__()

    self.encoder = tf.keras.Sequential()
    # Add hidden layers to the encoder (if any)
    for dim in hidden_dims:
      self.encoder.add(layers.Dense(dim, activation='relu'))
      self.encoder.add(layers.Dropout(0.5))

    # Define the latent layer
    self.encoder.add(layers.Dense(latent_dim, activation='relu'))

    # Decoder ( mirrored structure )
    self.decoder = tf.keras.Sequential()
    # Add hidden layers to the decoder (in reverse order)
    for dim in hidden_dims[::-1]:
      self.decoder.add(layers.Dense(dim, activation='relu'))
      self.decoder.add(layers.Dropout(0.5))

    # Define the output layer
    self.decoder.add(layers.Dense(input_dim, activation='sigmoid'))  #the output layer with dimensions matching the original input data

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

Llamada a la clase Autoencoder:

Python:

input_dim = dataset.shape[1]  # number of columns in the data
latent_dim = 5  # Dimension of latent layer
hidden_dims = [12, 10]

autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)

A continuación se muestra el aspecto de la arquitectura del Autoencoder:

Arquitectura del autocodificador

En la clase Autoencoder, has visto el uso de RELU (Rectified Linear Unit) tanto en el codificador como en el decodificador. Esta función de activación se utiliza ampliamente en la mayoría de los autocodificadores que encontrarás, y hay una razón importante para ello.

RELU es eficiente desde el punto de vista computacional, evita los gradientes evanescentes y puede aprender representaciones dispersas que suelen encontrarse en los datos comerciales. Otras variantes de RELU como GELU y Leaky RELU pueden ser útiles cuando se trabaja con datos financieros.

Otras funciones de activación populares como la Sigmoidea y la Tangente Hiperbólica (TANH) pueden ser útiles, sin embargo uno debe entender sus pros y sus contras antes de usarlas en datos de trading.

Sigmoide:

  • Pros: A menudo se utiliza para la reconstrucción de imágenes donde la salida debe estar entre 0 y 1 (representando la intensidad del píxel).
  • Desventajas: Puede no ser ideal para datos financieros ya que puede introducir gradientes de fuga durante la retropropagación, especialmente en arquitecturas profundas.
    Cuando se aplicó sigmoide al Autoencoder, la red no convergió, ya que siguió oscilando hacia los mínimos locales:
    Epoch 1/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - loss: 0.4001 - val_loss: 0.3753
    Epoch 2/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3733 - val_loss: 0.3745
    Epoch 3/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3724 - val_loss: 0.3746
    Epoch 4/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3758 - val_loss: 0.3746
    Epoch 5/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3692 - val_loss: 0.3745
    Epoch 6/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3747 - val_loss: 0.3746
    Epoch 7/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3716 - val_loss: 0.3746
    Epoch 8/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3740 - val_loss: 0.3745
    Epoch 9/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3698 - val_loss: 0.3745
    Epoch 10/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3713 - val_loss: 0.3745
    Epoch 11/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3726 - val_loss: 0.3745
    Epoch 12/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3739 - val_loss: 0.3745
    Epoch 13/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3725 - val_loss: 0.3746
    Epoch 14/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3749 - val_loss: 0.3746

Tanh (tangente hiperbólica):

  • Pros: Los resultados oscilan entre -1 y 1, similar al sigmoide pero con gradientes más pronunciados, lo que potencialmente conduce a una convergencia más rápida.
  • Desventajas: Puede sufrir de gradientes de fuga en redes muy profundas.

Estas funciones de activación sigmoidea y TANH y otras de su clase funcionan mejor cuando se utilizan en la capa de salida del descodificador con el fin de reconstruir los datos de entrada con la mayor precisión posible. En este contexto, la salida del autocodificador debe parecerse a la entrada original. Dado que los datos de entrada suelen estar normalizados en el rango [0, 1] o [-1, 1], dependiendo del preprocesamiento, se suele utilizar la función de activación sigmoidea para escalar los valores de salida a este rango.

Python:

# Define the output layer
self.decoder.add(layers.Dense(input_dim, activation='sigmoid'))  #the output layer of the decoder with dimensions matching the original input data


El escalador Min-Max es tu amigo.

Los autocodificadores son fáciles de codificar e implantar, pero para que funcionen bien hay que darles la información y las herramientas adecuadas. Como acabamos de ver, la elección de una función de activación es crucial para este tipo de red neuronal, al igual que la técnica de escalado.

Dado que estamos utilizando la función de activación RELU que devuelve el valor de cero cuando se le da un valor menor o igual a cero, de lo contrario devuelve el valor dado. Es decir: (x = 0 cuando x<=0 de lo contrario x = x).

Cuando se utiliza Standard-Scaler, centra los datos restando la media y los escala a la varianza unitaria. Esto puede empujar a los valores atípicos con grandes valores positivos hacia valores muy negativos (potencialmente -1) durante la normalización. Si un valor normalizado de un valor atípico se convierte en -1, cuando se pasa este valor negativo la activación RELU en el codificador siempre dará como salida 0 para esa característica específica.

Esto puede dar lugar a un fenómeno llamado neuronas RELU, en el que algunas neuronas del codificador nunca se activan debido a estos valores de entrada negativos. Estas neuronas RELU moribundas pueden obstaculizar el aprendizaje en el codificador ya que esencialmente se vuelven inactivas y no contribuyen al proceso de codificación la mayoría de los valores atípicos o picos en los datos comerciales se predecirán planos en su mayoría: ver la imagen de abajo donde se utilizó Standard-Scaler.

  Autocodificador Standard-Scaler

Para abordar esta cuestión:

Prueba otras técnicas de normalización, como el escalador Min-Max, que ajusta los datos a un rango específico entre 0 y 1, lo que podría evitar la creación de valores de -1 que causan problemas con RELU. Sin embargo, teniendo en cuenta las limitaciones del escalador Min-Max, también puede explorar con el RobustScaler, que es menos sensible a los valores atípicos que el escalador estándar y podría ofrecer un mejor escalado para las activaciones RELU.

También es posible que desee considerar el uso de Leaky RELU (leaky_relu = 0.01x for x <= 0, relu = x for x > 0) en lugar de RELU estándar. Leaky RELU permite un pequeño gradiente distinto de cero incluso para entradas negativas, lo que mitiga el problema del "RELU moribundo".


Entrenamiento del autocodificador

Ahora que hemos discutido brevemente los fundamentos de un autocodificador, vamos a entrenar uno y ver cómo podemos utilizarlo para ayudarnos en el trading.

Python:

import sklearn
from sklearn.model_selection import train_test_split
from keras import optimizers
from keras.callbacks import EarlyStopping

x_train, x_test = train_test_split(dataset, test_size=0.3, random_state=42) #train test the data

# Normalizing the input data 

scaler = sklearn.preprocessing.MinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)

print(f"x_train {x_train.shape}.dtype({x_train.dtype}) x_test {x_test.shape}.dtype({x_test.dtype})")

# compile the autoencoder

input_dim = dataset.shape[1]
latent_dim = 32  # Dimension of latent space
hidden_dims = [256, 128, 64]

autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)

optimizer = optimizers.Adam(learning_rate=1e-5)
autoencoder.compile(optimizer=optimizer, loss=losses.MeanSquaredError())

early_stopping = EarlyStopping(monitor='val_loss', patience = 5, restore_best_weights=True) //stop the training process if 5 epochs have no change in loss
history = autoencoder.fit(x_train, x_train, epochs=50, shuffle=True, callbacks=[early_stopping], validation_data=(x_test, x_test), batch_size=64, verbose=1)

He optado por una arquitectura de red neuronal compleja [256, 128, 64] para el codificador y una disposición inversa de [64,128, 256] para el decodificador, con 32 neuronas en la capa latente.

Una red neuronal así de compleja tiene más posibilidades de sobreajustar los datos de entrenamiento, siéntete libre de empezar con arquitecturas más sencillas, esto es sólo un ejemplo

Salidas:

x_train (7000, 4).dtype(float64) x_test (3000, 4).dtype(float64)
Epoch 1/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - loss: 0.0669 - val_loss: 0.0636
Epoch 2/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0648 - val_loss: 0.0608
Epoch 3/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0624 - val_loss: 0.0550

....
....
....

Epoch 46/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.2096e-04 - val_loss: 1.0195e-04
Epoch 47/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.0758e-04 - val_loss: 9.7759e-05
Epoch 48/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.0923e-04 - val_loss: 9.4798e-05
Epoch 49/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 1.0243e-04 - val_loss: 9.0442e-05
Epoch 50/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 1.0222e-04 - val_loss: 8.7384e-05

Gráfico de pérdidas frente a iteraciones:

Gráfico de pérdidas frente a iteraciones

Pasemos los datos al Autoencoder y observemos el resultado:

Python:

original_norm_data = scaler.transform(dataset)

new_data = autoencoder.call(original_norm_data)

new_data = scaler.inverse_transform(new_data) #return data to the original form 

print("original data\n",dataset,"\nnew data\n",new_data)

Salidas:

Datos originales
 [[1.06507 1.06633 1.06497 1.06538]
 [1.06628 1.06685 1.06463 1.06508]
 [1.06771 1.06797 1.06599 1.06627]
 ...
 [0.99941 0.99996 0.9991  0.99916]
 [0.99687 0.99999 0.99646 0.99941]
 [0.99536 0.99724 0.99444 0.99687]] 
new data
 [[1.06612682 1.06676685 1.06537819 1.06605109]
 [1.06617137 1.06679912 1.06541834 1.06609218]
 [1.06742607 1.06804771 1.06668032 1.06736937]
 ...
 [0.99906356 1.00121275 0.9980908  0.99980352]
 [0.998204   1.00034005 0.9972261  0.99893805]
 [0.99581326 0.99789913 0.99494114 0.99651365]]

Decidí visualizar los precios de cierre:

Precios de cierre frente a precios autocodificados

Podemos concluir que los nuevos datos pasados por el autocodificador tienen algo de ruido filtrado y es fácil detectar los valores atípicos con sólo mirar el gráfico. Ahora que estamos seguros de que funciona, hablemos de las aplicaciones del autocodificador y de cómo podemos utilizarlas finalmente en nuestros programas basados en MQL5.


Aplicaciones de los autocodificadores

Los autocodificadores se han utilizado en diversos campos e industrias como la ingeniería, el campo de la medicina, el entretenimiento y muchos más para la reducción de la dimensionalidad, el aprendizaje de características, la detección de anomalías, en sistemas de recomendación y la eliminación de ruido en imágenes.

Reducción de la dimensionalidad

Los autocodificadores destacan en la compresión de datos de alta dimensión en un espacio latente de menor dimensión. Esto es especialmente valioso cuando se trata de conjuntos de datos que contienen un gran número de características, ya que capturan las características esenciales en una representación más compacta que puede:

  • Mejorar la eficiencia computacional en tareas posteriores de aprendizaje automático reduciendo el número de características a procesar. 
  • Mejora la visualización de datos de alta dimensión permitiendo aplicar técnicas de reducción de la dimensionalidad como el Principal Component Analysis (PCA) al espacio latente aprendido.

Para lograr esta tarea necesitamos utilizar únicamente la parte codificadora de nuestra red neuronal

Tenemos que modificar la clase Autoencoder añadiendo la función build que se supone que debe ser llamada poco después de que se inicie la clase Autoencoder. Este método es útil para crear capas de forma dinámica en función de la forma de los datos de entrada, lo que le permite retrasar la construcción de capas hasta que se conozcan sus formas.

Python:

class Autoencoder(Model):
  def __init__(self, input_dim, latent_dim, hidden_dims=[]):
    super(Autoencoder, self).__init__()
    self.hidden_dims = hidden_dims
    self.input_dim = input_dim
    
    # Encoder
    self.encoder = tf.keras.Sequential(name='encoder') #give the encoder Sequential layer name=encoder
    # Decoder ( mirrored structure )
    self.decoder = tf.keras.Sequential(name='decoder') #give the decoder Sequential layer name=decoder
    
  def build(self):

    # Add hidden layers to the encoder (if any)
    for dim in hidden_dims:
      self.encoder.add(layers.Dense(dim, activation='relu'))
      self.encoder.add(layers.Dropout(0.5))

    # Define the latent layer
    self.encoder.add(layers.Dense(latent_dim, activation='relu'))
        
    # Add hidden layers to the decoder (in reverse order)
    for dim in hidden_dims[::-1]:
      self.decoder.add(layers.Dense(dim, activation='relu'))
      self.decoder.add(layers.Dropout(0.5))

    # Define the output layer
    self.decoder.add(layers.Dense(self.input_dim, activation='sigmoid'))  #the output layer with dimensions matching the original input data

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

También tenemos que cambiar un poco la forma en que llamamos a las funciones de nuestra clase, como hemos dicho antes se supone que debemos llamar a la función build antes de compilar y entrenar nuestro modelo de red neuronal. ¡El orden de llamada a los métodos de la clase es importante!

Python:

# Instantiate the autoencoder and build the model
autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)
autoencoder.build()

optimizer = optimizers.Adam(learning_rate=1e-5)
autoencoder.compile(optimizer=optimizer, loss=losses.MeanSquaredError())

Ahora que tenemos la función build en su lugar, podemos finalmente extraer las redes neuronales del codificador y del decodificador por separado pronto, después de que el Autoencoder haya sido entrenado con éxito y sin errores.

Python:

# Extract Encoder
encoder_input = autoencoder.encoder.layers[0].input
encoder_output = autoencoder.encoder.get_layer(index=-1).output # the layer at index -1 is the last layer

# Define the encoder model
encoder_model = tf.keras.Model(inputs=encoder_input, outputs=encoder_output)

# Extract Decoder
decoder_input = autoencoder.decoder.layers[0].input
decoder_output = autoencoder.decoder.get_layer(index=-1).output # the layer at index -1 is the last layer

# Define the decoder model
decoder_model = tf.keras.Model(inputs=decoder_input, outputs=decoder_output)

Una vez que tengamos el codificador, podemos pasar la información y obtener la matriz de resultados a través de la capa latente (espacio).

Python:

from sklearn.decomposition import PCA

# Fit & transform the encoded data 
encoded_data = encoder_model.predict(original_norm_data)
print("decoded data.shape: ",encoded_data.shape)

# Create PCA object
pca = PCA(n_components=encoded_data.shape[1])

reduced_data = pca.fit_transform(encoded_data)
print("pca reduced data.shape: ",reduced_data.shape)

print("explained var:\n",np.cumsum(pca.explained_variance_ratio_))

# Plotting the scree plot
plt.figure(figsize=(10, 6))
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance')
plt.title('Scree Plot')
plt.grid(True)
plt.show()

Asignando el número de columnas encoded_data.shape[1] a los componentes del PCA podemos medir la varianza explicada de cada característica y dibujar un diagrama scree que puede ayudarnos a comprender el mejor número de componentes que aplicar al PCA para reducir la dimensión de los datos.

313/313 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step
decoded data.shape:  (10000, 32)
pca reduced data.shape:  (10000, 32)
explained var:
 [0.99623495 0.9989214  0.99982804 0.9999363  0.99996614 0.9999872
 0.99999297 0.9999953  0.9999972  0.9999982  0.9999987  0.9999991
 0.9999994  0.9999996  0.9999997  0.9999998  0.99999994 1.
 1.         1.         1.         1.         1.         1.
 1.         1.         1.         1.         1.         1.
 1.         1.        ]

Gráfico de pantalla del PCA

Observando la varianza explicada acumulada podemos observar ratios de varianza explicada cercanos a 1 en la mayoría de los casos y a 1 para algunos componentes. Esto implica que podría conseguir una reducción significativa de la dimensionalidad sin perder mucha información.
El gráfico de la pantalla muestra el punto de codo en casi 2 componentes, lo que explica aproximadamente 0,9989 de varianza total, este es el mejor número de componentes para reducir nuestros datos. Incluso 1 componente debería funcionar bien, ya que no pude ver una distinción importante entre los componentes cuando los tracé en un eje.

La próxima vez que se llame a la clase PCA, deberá llamarse con el valor 2 aplicado para obtener de ella 2 componentes.

# Create PCA object
pca = PCA(n_components=2)

reduced_data = pca.fit_transform(encoded_data)
print("pca reduced data.shape: ",reduced_data.shape)

Resultado:

pca reduced data.shape:  (10000, 2)

Decidí trazar los 32 componentes de la capa latente en un eje. Sólo una característica se distinguía mucho de las demás, que parecían casi iguales en el gráfico esto pasa a aclarar algunos componentes en estos datos reducidos tiene sentido.

bar = [count+1 for count in range(reduced_data.shape[0])]

plt.figure(figsize = (7,10))
for col in range(reduced_data.shape[1]):
    plt.plot(bar,  reduced_data[:, col],label=f'feature {col}')
    
plt.xlabel("index")
plt.ylabel("feature")
plt.title("PCA encoded features")
plt.legend()
plt.savefig("pca-encoded features")

Gráfico de componentes vs índice:

Gráfico de componentes PCA

La aplicación del PCA al espacio latente del autocodificador ofrece un mayor control sobre el proceso de reducción en comparación con la aplicación directa del PCA a los datos originales de alta dimensión, por no mencionar que ayuda a reducir el ruido innecesario en los datos a lo largo del proceso.

Un elefante en la habitación:

En el ejemplo comentado redujimos la dimensión de todos los datos de entrada, lo que puede no ser ideal si desea aplicar los datos reducidos tras el PCA en modelos predictivos; en ese caso, puede que necesite aplicar el PCA sólo a las variables independientes.


Pero antes de poder utilizar este autocodificador que hemos creado para reducir el ruido de los datos comerciales en MetaTrader 5 como otra aplicación de autocodificación, tenemos que guardarlo en formato ONNX.


Guardar el modelo de autoencoder en formato ONNX

Ya hemos extraído tanto el codificador como el decodificador antes de aplicarlos para la reducción de dimensión convertirlos y guardarlos en formato ONNX debería ser fácil. Empecemos por el modelo de codificador, ya que guardaremos ambos por separado.

Python:

import tf2onnx
import onnx
import os

output_path = os.path.join('/kaggle/working/',"encoder.eurusd.h1.onnx")

# saving the encoder for MetaTrader 5

input_signature = [tf.TensorSpec(encoder_input.shape, tf.float16, name='x_inputs')] #onnx input signature
# Use from_function for tf functions
onnx_model, _ = tf2onnx.convert.from_keras(encoder_model, input_signature, opset=13)
onnx.save(onnx_model, output_path)

La input_signature para ONNX ayuda a evitar errores con las últimas versiones de TensorFlow y ONNX ya que ayuda a clarificar los nombres de entrada para nuestro archivo .onnx al cargar un modelo de este formato en MetaTrader 5.

Guardado del modelo de descodificador:

Python:

# saving the decoder

output_path = os.path.join('/kaggle/working/',"decoder.eurusd.h1.onnx")

input_signature = [tf.TensorSpec(decoder_input.shape, tf.float16, name='decoder_inputs')] #onnx input signature

onnx_model, _ = tf2onnx.convert.from_keras(decoder_model, input_signature, opset=13) #conver keras model to onnx
onnx.save(onnx_model, output_path)

En el artículo Superar los retos de integración de ONNX, abordé el problema de integrar las mismas técnicas de reducción de dimensiones y escalado disponibles tanto para Python como para el lenguaje de programación mql5 de precisión, pero encontré una solución fácil para mitigar el problema del escalado.

Guardado del escalador:

Utilizar el mismo escalador en Python y en MQL5 es crucial. No puedo insistir lo suficiente en lo importante que es esto.

Python:

scaler.data_min_.tofile("minmax_min.bin")
scaler.data_max_.tofile("minmax_max.bin")

Guardamos las matrices de información del escalador Min-Max en simples archivos binarios que podemos incluir en nuestro indicador de MetaTrader 5. Después de guardarlos en la carpeta MQL5\Files.

MQL5 (AutoEncoder Indicator.mq5):

//Load both the encoder_model and the decoder_model
#resource "\\Files\\encoder.eurusd.h1.onnx" as uchar encoder_onnx[];
#resource "\\Files\\decoder.eurusd.h1.onnx" as uchar decoder_onnx[];

// Load the MinMax scaler also
#resource "\\Files\\minmax_min.bin" as double min_values[];
#resource "\\Files\\minmax_max.bin" as double max_values[];


Reducir el ruido de los datos comerciales

El autocodificador puede eliminar el ruido de los datos, como se ha visto en varios aspectos diferentes, como la eliminación del ruido de las imágenes, pero aún tenemos que probarlo en los datos financieros. Al observar la imagen de los precios de cierre y los nuevos precios de cierre, queda claro que los valores de los precios de cierre autocodificados son menos ruidosos. Hagamos un indicador que nos ayude a dibujar las velas para el nuevo OHLC proporcionado por el Autoencoder.

MQL5 (AutoEncoder Indicator.mq5):

#property indicator_chart_window
#property indicator_plots 1
#property indicator_buffers 5

input bool show_bars = true;
input bool show_bullish_bearish = false;

//--- plot Candle
#property indicator_label1  "autoencoded open; high; low; close"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrRed, clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

Necesitamos hacer una clase Autoencoder para facilitar el uso de los modelos ONNX cargados en MQL5 como si los estuviéramos usando en Python.

MQL5(Autoencoder-onnx.mqh):

class CAutoEncoderONNX
  {
protected:

   bool initialized;
   long onnx_handle;
   void PrintTypeInfo(const long num,const string layer,const OnnxTypeInfo& type_info);
   long inputs[], outputs[];
   
   void replace(long &arr[]) { for (uint i=0; i<arr.Size(); i++) if (arr[i] <= -1) arr[i] = UNDEFINED_REPLACE; }
   
public:
                     CAutoEncoderONNX(void);
                    ~CAutoEncoderONNX(void);
                     
                     bool Init(const uchar &onnx_buff[], ulong flags=ONNX_DEFAULT); //load the onnx model from a resource uchar array
                     bool Init(string onnx_filename, uint flags=ONNX_DEFAULT); //load the onnx model from a .onnx file 
                     
                     matrix predict(const matrix &x); //passing inputs for either the encoder or the decoder to the outputs in matrix form
                     vector predict(const vector &x); //passing inputs for either the encoder or the decoder to the outputs in matrix form
  };

Instanciando la clase CAutoEncoderONNX para cada modelo por separado tal y como son:

MQL5 (AutoEncoder Indicator.mq5):

#include <Autoencoder-onnx.mqh>
#include <MALE5\preprocessing.mqh>

CAutoEncoderONNX encoder_model; //for the encoder model
CAutoEncoderONNX decoder_model; //for the decoder model
MinMaxScaler *scaler; //Python-like MinMax scaler

Inicialización de los modelos:

MQL5 (AutoEncoder Indicator.mq5):

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   if (!encoder_model.Init(encoder_onnx)) //initializing the encoder
     return INIT_FAILED;
   
   if (!decoder_model.Init(decoder_onnx)) //initializing the decoder
     return INIT_FAILED;
   
   scaler = new MinMaxScaler(min_values, max_values); //Load the Minmax scaler saved in python
     
//---
   return(INIT_SUCCEEDED);
  }

Para obtener las predicciones del modelo vamos a pasar los datos brutos al codificador y luego pasaremos el resultado al decodificador para la salida final. ¡Recuerde! En Python, teníamos dos modelos separados pasados uno tras otro en la función call.

Python:

class Autoencoder(Model):
...
...

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

Veámoslo en acción en MQL5:

MQL5 (AutoEncoder Indicator.mq5):

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   
   int start = prev_calculated;
   if(start>=rates_total)
      start = rates_total-1;
   
   
   vector encoded_data = {}, decoded_data = {};
   for(int i = start; i<rates_total; i++)
     {
        vector x_inputs = {open[i], high[i], low[i], close[i]};
        
        x_inputs = scaler.transform(x_inputs); //Normalize the input data, important!
        encoded_data = encoder_model.predict(x_inputs); //encode the data
        decoded_data = decoder_model.predict(encoded_data); //decode the data
        
        decoded_data = scaler.inverse_transform(decoded_data); //return data to its original state  
          
        open_candle[i]= decoded_data[0];
        high_candle[i]= decoded_data[1];
        low_candle[i]=  decoded_data[2];
        close_candle[i]=decoded_data[3];
        
        // Set upper and lower body colors based on the gradient
        
        if (close_candle[i]>open_candle[i])
         {
           color_buffer[i] = 1.0; //Draw gray for bullish candle
         }
        else
         {
          color_buffer[i] = 0.0; //draw red when there was a bearish candle
         }
                         
        if (MQLInfoInteger(MQL_DEBUG))
         Comment(StringFormat("plotting [%d/%d] OPEN[%.5f] HIGH[%.5f] LOW[%.5f] CLOSE[%.5f]",i,rates_total,open_candle[i],high_candle[i],low_candle[i],close_candle[i]));
     }
     
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Gráfico del indicador:

Autoencoder Doji como velas

Según mi observación, las velas hechas por el Autoencoder tienen casi el mismo tamaño de cuerpo y la diferencia entre el precio más bajo y el más alto es alta y casi la misma para todas las velas.

La mayoría de las velas bajistas son rojas, y muy pocas son alcistas en color gris.

Para que este indicador aparezca bien en el gráfico podemos rellenar el espacio entre el precio más bajo y el más alto de la vela. Tanto para velas alcistas como bajistas.

MQL5 (AutoEncoder Indicator.mq5):

  if (close_candle[i]>open_candle[i])
   {
     color_buffer[i] = 1.0; //Draw gray for bullish candle

     close_candle[i] = high_candle[i];
     open_candle[i] = low_candle[i];
   }
  else
   {
     color_buffer[i] = 0.0; //draw red when there was a bearish candle
    
     close_candle[i] = low_candle[i];
     open_candle[i] = high_candle[i];
   }

Gráfico del indicador:

Nuevas barras autocodificadas

Podemos dar a nuestro indicador la opción de distinguir entre velas alcistas y bajistas basándose en los precios reales de apertura y cierre del mercado.

MQL5 (AutoEncoder Indicator.mq5):

if (show_bullish_bearish)
 {
  if (close[i]>open[i])
   color_buffer[i] = 1.0;
  else
    color_buffer[i] = 0.0;
 }

Gráfico del indicador:

Velas de colores OHLC en EURUSD autocodificadas

También tenemos la opción de ocultar las velas originales e ir sólo con las nuevas velas hechas con el autocodificador.

Autoencoder indicator show bars=false


Desventajas de los autocodificadores

Los autocodificadores, como todos los modelos de aprendizaje automático, presentan su propio conjunto de desafíos:

  1. Reconstrucción imperfecta de datos
    Los autocodificadores intentan recrear los datos después de comprimirlos. A veces, no hacen un gran trabajo, lo que provoca errores en la reconstrucción de los datos originales. Esto supone un problema si necesita una recreación muy exacta de los datos originales.

  2. Difícil de entender
    Los formatos de datos comprimidos que producen los autocodificadores pueden ser difíciles de interpretar. A menudo no está claro qué características de los datos ha conseguido captar el autocodificador, lo que hace difícil explicar cómo funciona el modelo.

  3. Sensible al ruido
    Los autocodificadores pretenden resaltar los patrones principales de los datos, pero pueden tener problemas con el ruido y los valores atípicos. Esto puede dar lugar a una mala reconstrucción y a características sesgadas, lo que no es lo ideal.

  4. Cuello de botella de la dimensionalidad
    La capa intermedia de un autocodificador, donde se comprimen los datos, a veces puede ser demasiado pequeña. Si no tiene suficientes dimensiones, puede que no capte toda la información importante para lo que necesita hacer. Elegir el tamaño adecuado para esta capa es clave y depende de lo que intente conseguir.

  5. Costoso de entrenar
    El entrenamiento de autocodificadores profundos, especialmente en grandes conjuntos de datos, puede consumir mucha potencia de cálculo. Es importante tenerlo en cuenta si dispone de recursos o tiempo limitados.

  6. No es ideal para todas las tareas
    Los autocodificadores pueden no ser la mejor opción para tareas como la clasificación o la regresión, en las que trabajar directamente con los datos de entrada podría ser más eficaz.

  7. Riesgo de sobreajuste
    Utilizar modelos complejos para problemas sencillos puede conducir a un sobreajuste, en el que el modelo aprende demasiado bien los datos de entrenamiento pero obtiene malos resultados con datos nuevos no vistos.



Reflexiones finales

Los autocodificadores pueden ser una gran herramienta para reducir el ruido en el mercado de divisas, como se ve en el indicador terminamos con velas menos ruidosas que siguen reflejando el mercado. Podrían ser mejores o peores que las velas originales. Estas nuevas velas nos dan una perspectiva diferente del mercado.

Siéntase libre de explorar las nuevas velas extrayendo señales de los patrones y construyendo estrategias de negociación a partir de ellas.

Paz.

Tabla de adjuntos: 

Archivo Descripción y uso
Include\MatrixExtend.mqh
Dispone de funciones adicionales para la manipulación de matrices.
Include\ preprocessing.mqh
Librería de preprocesamiento de datos de entrada sin procesar para hacerlos aptos para el uso de modelos de aprendizaje automático.
Indicators\ AutoEncoder Indicator.mq5 El archivo principal del indicador. Despliega el autocodificador discutido y dibuja velas sobre las predicciones de resultados.
Include\ Autoencoder-onnx.mqh  Una librería para cargar un modelo de aprendizaje automático en formato ONNX e interpretar los resultados.
Files\...  Guarde estos archivos en la carpeta MQL5\Files
autoencoders.ipynb Python Jupyter Notebook para ejecutar todo el código Python discutido. 




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

Archivos adjuntos |
Code_6_Files.zip (689.5 KB)
Algoritmos de optimización de la población: Algoritmo de optimización de ballenas (Whale Optimization Algorithm, WOA) Algoritmos de optimización de la población: Algoritmo de optimización de ballenas (Whale Optimization Algorithm, WOA)
El algoritmo de optimización de ballenas (WOA) es un algoritmo metaheurístico inspirado en el comportamiento y las estrategias de caza de las ballenas jorobadas. La idea básica del WOA es imitar el método de alimentación denominado "red de burbujas", en el que las ballenas crean burbujas alrededor de la presa para atacarla después en espiral.
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 7): Señales de los indicadores ZigZag y Awesome Oscillator Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 7): Señales de los indicadores ZigZag y Awesome Oscillator
En este artículo, entenderemos por EA multidivisa un EA o robot comercial que utiliza indicadores ZigZag y Awesome Oscillator que filtran mutuamente sus señales.
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.
Indicadores personalizados (Parte 1): Guía introductoria paso a paso para desarrollar indicadores personalizados simples en MQL5 Indicadores personalizados (Parte 1): Guía introductoria paso a paso para desarrollar indicadores personalizados simples en MQL5
Aprenda a crear indicadores personalizados utilizando MQL5. Este artículo introductorio le guiará a través de los fundamentos de la construcción de indicadores personalizados simples y demostrar un enfoque práctico para la codificación de diferentes indicadores personalizados para cualquier programador MQL5 nuevo en este interesante tema.