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
¿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.
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:
- Un codificador toma los datos de entrada y los comprime en una representación latente de menor dimensión, capturando las características esenciales.
- 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.
- El codificador
- El vector de incrustación/capa latente
- El descodificador
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.
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:
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.
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:
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:
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. ]
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:
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:
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:
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:
También tenemos la opción de ocultar las velas originales e ir sólo con las nuevas velas hechas con el autocodificador.
Desventajas de los autocodificadores
Los autocodificadores, como todos los modelos de aprendizaje automático, presentan su propio conjunto de desafíos:
-
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. -
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. -
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. -
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. -
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. -
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. -
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso