Освоение ONNX: Переломный момент для MQL5-трейдеров
"Возможность экспортировать и импортировать модели ИИ в формате ONNX упрощает процесс разработки, экономя время и ресурсы при интеграции ИИ в экосистемы, написанные на разных языках".
Введение
Нельзя отрицать, что мы живем в эпоху искусственного интеллекта (ИИ) и машинного обучения: каждый день появляются новые технологии на основе ИИ, применяемые в финансах, искусстве, играх, образовании и многих других аспектах жизни.
Для нас, трейдеров, ИИ полезен тем, что позволяет обнаруживать закономерности и взаимосвязи, которые мы не можем увидеть невооруженным глазом.
Несмотря на кажущуюся мощь и волшебство, за моделями ИИ скрываются сложные математические операции, требующие огромного объема работы и высокой степени точности для их правильного понимания и реализации. К счастью, открытый исходный код существенно упрощает задачу, устраняя необходимость реализовывать модель машинного обучения с нуля.
Сейчас не нужно быть гением математики и программирования, чтобы создавать и реализовывать модели ИИ. Необходимо лишь базовое понимание определенного языка программирования или инструментов, которые вы хотите использовать в вашем проекте. Иногда даже необязательно иметь компьютер. Благодаря таким сервисам как Google Colab, вы можете бесплатно кодировать, создавать и запускать модели ИИ с помощью Python.
Реализовать модели машинного обучения с использованием Python и других популярных и развитых языков программирования относительно просто, чего нельзя сказать об MQL5. Если только вы не хотите изобрести велосипед, создавая модели машинного обучения в MQL5 с нуля, что мы и делаем в этой серии статей, я бы настоятельно советовал использовать ONNX для интеграции моделей ИИ, построенных с помощью Python. К счастью, ONNX теперь поддерживается в MQL5.
Для понимания статьи необходимо базовое представление об ИИ и машинном обучении. Информацию можно найти здесь и здесь.
Что такое ONNX?
ONNX (Open Neural Network Exchange) - открытая библиотека для представления моделей машинного и глубокого обучения. Она позволяет конвертировать модели, обученные в одном фреймворке глубокого обучения, в общий формат, который можно использовать в других фреймворках, что упрощает работу с моделями на разных платформах и инструментах.
Это означает, что вы можете создавать модели машинного обучения, используя любой язык, который его поддерживает, кроме MQL5, затем конвертировать эту модель в формат ONNX и использовать внутри вашей MQL5-программы.
В этой статье я буду использовать Python для создания машинного обучения, поскольку я с ним знаком. Насколько я понял, можно использовать и другие языки, хотя я не уверен в этом. Кстати, похоже, что вся документация по ONNX основана на Python. Я считаю, что на данный момент ONNX создан для Python. Это имеет смысл, потому что я не думаю, что существует какой-либо другой язык, кроме Python, с расширенными библиотеками и инструментами на основе ИИ.
Основные понятия в ONNX
Прежде чем погрузиться в ONNX, вы должны быть знакомы с некоторыми ключевыми понятиями:
- Модель ONNX: Модель ONNX — это представление модели машинного обучения. Она состоит из графа вычислений, где узлы представляют операции (например, свертку, сложение), а ребра представляют поток данных между операциями.
- Узлы: Узлы графа ONNX представляют операции или функции, применяемые к входным данным. Этими узлами могут быть такие операции, как свертка, сложение или пользовательские операции.
- Тензоры: Тензоры — это многомерные массивы, которые представляют данные, передаваемые между узлами вычислительного графа. Они могут быть входными, выходными или промежуточными данными.
- Операторы: Операторы — это функции, применяемые к тензорам в ONNX. Каждый оператор представляет собой определенную операцию, например умножение матрицы или поэлементное сложение.
Создание моделей на Python и развертывание на MQL5 с использованием ONNX
Чтобы успешно построить модель машинного обучения на Python, разверните эту модель внутри своего советника, индикатора или скрипта на MQL5. Для модели требуется нечто большее, чем просто код Python. Ниже приведены важные шаги, которые необходимо выполнить, чтобы в итоге вы получили не только модель ONNX, но и модель, которая действительно дает нужные вам точные прогнозы;
- Сбор данных
- Нормализация данных на стороне MQL5
- Построение моделей на Python
- Получение встроенной модели ONNX в MQL5
- Запуск модели в реальном времени
01: Сбор данных
Сбор данных — это первое, что необходимо сделать прямо внутри вашей MQL5-программы. Я считаю, что лучше всего собирать все данные внутри вашей программы, чтобы они соответствовали тому, как мы собираем данные обучения и данные, которые используются во время реальной торговли или запуска модели в режиме реального времени. Имейте в виду, что сбор данных может варьироваться в зависимости от характера проблемы, которую вы пытаетесь решить. В этой статье мы будем пытаться решить проблему регрессии. Мы будем использовать данные OHLC (Open, High, Low, Close) в качестве нашего основного набора данных, где Open, High и Low будут использоваться как независимые переменные, а значения Close будут использоваться в качестве целевой переменной.
Inside ONNX get data.mq5
matrixf GetTrainData(uint start, uint total) { matrixf return_matrix(total, 3); ulong last_col; OPEN.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_LOW, start, total); CLOSE.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_CLOSE, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); x_vars = "OPEN,HIGH,LOW"; return_matrix.Resize(total, 4); //if we are collecting the train data collect the target variable also last_col = return_matrix.Cols()-1; //Column located at the last index is the last column return_matrix.Col(CLOSE, last_col); //put the close price information in the last column of a matrix csv_name_ +=".targ=CLOSE"; csv_header = x_vars + ",CLOSE"; if (!WriteCsv("ONNX Datafolder\\"+csv_name_+".csv", return_matrix, csv_header)) Print("Failed to Write to a csv file"); else Print("Data saved to a csv file successfully"); return return_matrix; }
Эта функция собирает независимые переменные OHL и целевую переменную CLOSE. При контролируемом машинном обучении целевую переменную необходимо указать и передать модели, чтобы она могла учиться на ней и понимать закономерности между целевой переменной и остальными переменными. В нашем случае модель попытается понять, как эти показания индикатора приводят к бычьему или медвежьему движению.
При развертывании модели нам необходимо собирать данные таким же способом за исключением того, что на этот раз мы будем собирать данные без целевой переменной, потому что мы хотим, чтобы наша обученная модель это выяснила. По сути, речь идет о прогнозировании.
Вот почему существует другая функция GetLiveData для загрузки новых данных для быстрых прогнозов на рынке.
Inside ONNX mt5.mq5
matrixf GetLiveData(uint start, uint total) { matrixf return_matrix(total, 3); OPEN.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_LOW, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); return return_matrix; }
Сбор обучающих данных
matrixf dataset = GetTrainData(start_bar, total_bars); Print("Train data\n",dataset);
Результаты
DK 0 23:10:54.837 ONNX get data (EURUSD,H1) Train data PR 0 23:10:54.837 ONNX get data (EURUSD,H1) [[1.4243405,1.4130603,1.4215617,1.11194] HF 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3976599,1.3894916,1.4053394,1.11189] RK 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.402994,1.3919021,1.397626,1.11123] PM 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3848507,1.3761013,1.3718294,1.11022] FF 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3597701,1.3447646,1.3545419,1.1097701] CH 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3461626,1.3522644,1.3433729,1.1106] NL 0 23:10:54.837 ONNX get data (EURUSD,H1) [1.3683074,1.3525325,1.3582669,1.10996]
Получение реальных данных
Получение текущей данных OHL по бару.
matrixf live_data = GetLiveData(0,1); Print("Live data\n",live_data);
Результаты
MN 0 23:15:47.167 ONNX mt5 (EURUSD,H1) Live data KS 0 23:15:47.167 ONNX mt5 (EURUSD,H1) [[-0.21183228,-0.23540309,-0.20334835]]
Способ загрузки данных в реальном времени может немного отличаться при подготовке данных для модели прогнозирования временных рядов, таких как RNN, GRU и LSTM
error 2023.09.18 18:03:53.212 ONNX: invalid parameter size, expected 1044480 bytes instead of 32640
02: Нормализация данных на стороне MQL5
Нормализация данных — одна из наиболее важных вещей, которые необходимо реализовать правильно для набора данных, который будет использоваться моделью машинного обучения.
Имейте в виду, что метод нормализации, используемый для подготовки обучающих данных, должен быть таким же, как и для подготовки тестовых и реальных данных. Это означает, что при использовании метода MinMaxScaler значения min и max, которые являются фундаментальными переменными в уравнении MinMaxScaler, использовавшемся при подготовке обучающих данных, должны использоваться для продолжения нормализации новых данных, которые будут обрабатываться моделью в другом месте. Чтобы добиться такой согласованности, нам необходимо сохранить переменные для каждого метода нормализации в файл csv:
Нормализация данных предназначена только для независимых переменных. Независимо от того, какую проблему вы пытаетесь решить, вам не нужно нормализовать целевую переменную.
Мы собираемся использовать класс Preprocessing, представленный здесь.
Inside ONNX get data.mq5 script
//--- Saving the normalization prameters switch(NORM) { case NORM_MEAN_NORM: //--- saving the mean norm_params.Assign(norm_x.mean_norm_scaler.mean); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv",norm_params,x_vars); //--- saving the min norm_params.Assign(norm_x.mean_norm_scaler.min); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.mean_norm_scaler.max); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.max.csv",norm_params,x_vars); break; case NORM_MIN_MAX_SCALER: //--- saving the min norm_params.Assign(norm_x.min_max_scaler.min); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.min_max_scaler.max); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.max.csv",norm_params,x_vars); break; case NORM_STANDARDIZATION: //--- saving the mean norm_params.Assign(norm_x.standardization_scaler.mean); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.mean.csv",norm_params,x_vars); //--- saving the std norm_params.Assign(norm_x.standardization_scaler.std); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.std.csv",norm_params,x_vars); break; }
Результаты
Когда Standardization Scaler использовался внутри файлов csv, параметры выглядели следующим образом:
Обратите внимание, что нормализация также интегрирована внутри функции GetData. Поскольку нормализация очень важна, каждая матрица данных, возвращаемая обеими функциями, ответственными за сбор данных, должна быть матрицей с нормализованными значениями цен.
Inside ONNX get data.mq5 script
matrixf GetTrainData(uint start, uint total) { matrixf return_matrix(total, 3); ulong last_col; OPEN.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_LOW, start, total); CLOSE.CopyRates(Symbol(), PERIOD_CURRENT, COPY_RATES_CLOSE, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); matrixf norm_params = {}; csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); x_vars = "OPEN,HIGH,LOW"; while (CheckPointer(norm_x) != POINTER_INVALID) delete (norm_x); norm_x = new CPreprocessing<vectorf, matrixf>(return_matrix, NORM); //--- Saving the normalization prameters switch(NORM) { case NORM_MEAN_NORM: //--- saving the mean norm_params.Assign(norm_x.mean_norm_scaler.mean); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv",norm_params,x_vars); //--- saving the min norm_params.Assign(norm_x.mean_norm_scaler.min); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.mean_norm_scaler.max); WriteCsv(normparams_folder+csv_name_+".mean_norm_scaler.max.csv",norm_params,x_vars); break; case NORM_MIN_MAX_SCALER: //--- saving the min norm_params.Assign(norm_x.min_max_scaler.min); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.min.csv",norm_params,x_vars); //--- saving the max norm_params.Assign(norm_x.min_max_scaler.max); WriteCsv(normparams_folder+csv_name_+".min_max_scaler.max.csv",norm_params,x_vars); break; case NORM_STANDARDIZATION: //--- saving the mean norm_params.Assign(norm_x.standardization_scaler.mean); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.mean.csv",norm_params,x_vars); //--- saving the std norm_params.Assign(norm_x.standardization_scaler.std); WriteCsv(normparams_folder+csv_name_+".standardization_scaler.std.csv",norm_params,x_vars); break; } return_matrix.Resize(total, 4); //if we are collecting the train data collect the target variable also last_col = return_matrix.Cols()-1; //Column located at the last index is the last column return_matrix.Col(CLOSE, last_col); //put the close price information in the last column of a matrix csv_name_ +=".targ=CLOSE"; csv_header = x_vars + ",CLOSE"; if (!WriteCsv("ONNX Datafolder\\"+csv_name_+".csv", return_matrix, csv_header)) Print("Failed to Write to a csv file"); else Print("Data saved to a csv file successfully"); return return_matrix; }
Наконец, данные были сохранены в формате CSV, чтобы их можно было использовать совместно с кодом Python.
03: Построение моделей на Python
Я буду строить нейронную сеть многослойного перцептрона, но вы можете построить любую модель по вашему выбору. Вы не ограничены этим конкретным типом модели. Установите Python, если у вас его еще нет. Затем установите virtualenv, выполнив следующие команды из Windows CMD (не путайте с Powershell!!)
$ pip3 install virtualenv
После запуска,
$ virtualenv venv
Это создаст виртуальную среду Python для вашего компьютера с Windows. Этот процесс может немного отличаться для пользователей Mac и Linux. После этого запустите виртуальную среду, выполнив эту команду:
$ venv\Scripts\activate
После этого установите все зависимости, используемые в этом руководстве, запустив
Всегда важно изолировать проект, создавая виртуальную среду, чтобы избежать конфликтов между модулями и версиями Python и упростить совместное использование проекта.
Импорт и инициализация MT5
import MetaTrader5 as mt5 if not mt5.initialize(): #This will open MT5 app in your pc print("initialize() failed, error code =",mt5.last_error()) quit() # program logic and ML code will be here mt5.shutdown() #This closes the program # Getting the data we stored in the Files path on Metaeditor data_path = terminal_info.data_path dataset_path = data_path + "\\MQL5\\Files\\ONNX Datafolder"
Нам нужно проверить, существует ли путь. Если он не существует, это означает, что мы не собрали данные со стороны MetaTrader 5.
import os if not os.path.exists(dataset_path): print("Dataset folder doesn't exist | Be sure you are referring to the correct path and the data is collected from MT5 side of things") quit()
Построение многоуровневой нейронной сети перцептрона (MLP)
Мы собираемся обернуть NN MLP внутри класса, чтобы превратить наш код в читаемые разделы;
01: Инициализация класса
Данные собираются и разбиваются на обучающую и тестовую выборки, при этом важные переменные объявляются доступными для использования всем классом,
class NeuralNetworkClass(): def __init__(self, csv_name, target_column, batch_size=32): # Loading the dataset and storing to a variable Array self.data = pd.read_csv(dataset_path+"\\"+csv_name) if self.data.empty: print(f"No such dataset or Empty dataset csv = {csv_name}") quit() # quit the program print(self.data.head()) # Print 5 first rows of a given data self.target_column = target_column # spliting the data into training and testing samples X = self.data.drop(columns=self.target_column).to_numpy() # droping the targeted column, the rest is x variables Y = self.data[self.target_column].to_numpy() # We convert data arrays to numpy arrays compartible with sklearn and tensorflow self.train_x, self.test_x, self.train_y, self.test_y = train_test_split(X, Y, test_size=0.3, random_state=42) # splitting the data into training and testing samples print(f"train x shape {self.train_x.shape}\ntest x shape {self.test_x.shape}") self.input_size = self.train_x.shape[-1] # obtaining the number of columns in x variable as our inputs self.output_size = 1 # We are solving for a regression problem we need to have a single output neuron self.batch_size = batch_size self.model = None # Object to store the model self.plots_directory = "Plots" self.models_directory = "Models"
Результаты
02: Построение нейронной сети
Наша однослойная нейронная сеть определяется заданным количеством нейронов.
def BuildNeuralNetwork(self, activation_function='relu', neurons = 10): # Create a Feedforward Neural Network model self.model = keras.Sequential([ keras.layers.Input(shape=(self.input_size,)), # Input layer keras.layers.Dense(units=neurons, activation=activation_function, activity_regularizer=l2(0.01), kernel_initializer="he_uniform"), # Hidden layer with an activation function keras.layers.Dense(units=self.output_size, activation='linear', activity_regularizer=l2(0.01), kernel_initializer="he_uniform") ]) # Print a summary of the model's architecture. self.model.summary()
Результаты
03: Обучение и тестирование модели нейронной сети
def train_network(self, epochs=100, learning_rate=0.001, loss='mean_squared_error'): early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) # Early stoppage mechanism | stop training when there is no major change in loss in the last to epochs, defined by the variable patience adam = optimizers.Adam(learning_rate=learning_rate) # Adam optimizer >> https://machinelearningmastery.com/adam-optimization-algorithm-for-deep-learning/ # Compile the model: Specify the loss function, optimizer, and evaluation metrics. self.model.compile(loss=loss, optimizer=adam, metrics=['mae']) # One hot encode the validation and train target variables validation_y = self.test_y y = self.train_y history = self.model.fit(self.train_x, y, epochs=epochs, batch_size=self.batch_size, validation_data=(self.test_x, validation_y), callbacks=[early_stopping], verbose=2) if not os.path.exists(self.plots_directory): #create plots path if it doesn't exist for saving the train-test plots os.makedirs(self.plots_directory) # save the loss and validation loss plot plt.figure(figsize=(12, 6)) plt.plot(history.history['loss'], label='Training Loss') plt.plot(history.history['val_loss'], label='Validation Loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() title = 'Training and Validation Loss Curves' plt.title(title) plt.savefig(fname=f"{self.plots_directory}\\"+title) # use the trained model to make predictions on the trained data pred = self.model.predict(self.train_x) acc = metrics.r2_score(self.train_y, pred) # Plot actual & pred count = [i*0.1 for i in range(len(self.train_y))] title = f'MLP {self.target_column} - Train' # Saving the plot containing information about predictions and actual values plt.figure(figsize=(7, 5)) plt.plot(count, self.train_y, label = "Actual") plt.plot(count, pred, label = "forecast") plt.xlabel('Actuals') plt.ylabel('Preds') plt.title(title+f" | Train acc={acc}") plt.legend() plt.savefig(fname=f"{self.plots_directory}\\"+title) self.model.save(f"Models\\lstm-pat.{self.target_column}.h5") #saving the model in h5 format, this will help us to easily convert this model to onnx later def test_network(self): # Plot actual & pred count = [i*0.1 for i in range(len(self.test_y))] title = f'MLP {self.target_column} - Test' pred = self.model.predict(self.test_x) acc = metrics.r2_score(self.test_y, pred) # Saving the plot containing information about predictions and actual values plt.figure(figsize=(7, 5)) plt.plot(count, self.test_y, label = "Actual") plt.plot(count, pred, label = "forecast") plt.xlabel('Actuals') plt.ylabel('Preds') plt.title(title+f" | Train acc={acc}") plt.legend() plt.savefig(fname=f"{self.plots_directory}\\"+title) if not os.path.exists(self.plots_directory): #create plots path if it doesn't exist for saving the train-test plots os.makedirs(self.plots_directory) plt.savefig(fname=f"{self.plots_directory}\\"+title) return acc
Результаты
Epoch 1/50 219/219 - 2s - loss: 1.2771 - mae: 0.3826 - val_loss: 0.1153 - val_mae: 0.0309 - 2s/epoch - 8ms/step Epoch 2/50 219/219 - 1s - loss: 0.0836 - mae: 0.0305 - val_loss: 0.0582 - val_mae: 0.0291 - 504ms/epoch - 2ms/step Epoch 3/50 219/219 - 1s - loss: 0.0433 - mae: 0.0283 - val_loss: 0.0323 - val_mae: 0.0284 - 515ms/epoch - 2ms/step Epoch 4/50 219/219 - 0s - loss: 0.0262 - mae: 0.0272 - val_loss: 0.0218 - val_mae: 0.0270 - 482ms/epoch - 2ms/step Epoch 5/50 ... ... Epoch 48/50 219/219 - 0s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0121 - 490ms/epoch - 2ms/step Epoch 49/50 219/219 - 0s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0109 - 486ms/epoch - 2ms/step Epoch 50/50 219/219 - 1s - loss: 0.0112 - mae: 0.0106 - val_loss: 0.0112 - val_mae: 0.0097 - 501ms/epoch - 2ms/step 219/219 [==============================] - 0s 2ms/step C:\Users\Omega Joctan\OneDrive\Documents\onnx article\ONNX python\venv\Lib\site-packages\keras\src\engine\training.py:3079: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`. saving_api.save_model( 94/94 [==============================] - 0s 2ms/step Test accuracy = 0.9336617822086006
Модель нейронной сети имела точность 93% во время обучения и около 95% во время тестирования. Есть риск подгонки, но мы всё равно продолжим работать с ней.
04: Сохранение модели ONNX
Рекомендуется сохранять модель после успешного завершения обучения и после того, как вы убедились в производительности модели как при обучении, так и при проверке вне выборки. Нам необходимо добавить код времени выполнения ONNX, чтобы сохранить модель во время функции train_network в нашем классе. Прежде всего нам нужно установить две библиотеки - onnx и tf2onnx
def train_network(self, epochs=100, learning_rate=0.001, loss='mean_squared_error'): # at the end of this function # .... self.model.save(f"Models\\MLP.REG.{self.target_column}.{self.data.shape[0]}.h5") #saving the model in h5 format, this will help us to easily convert this model to onnx later self.saveONNXModel() def saveONNXModel(self, folder="ONNX Models"): path = data_path + "\\MQL5\\Files\\" + folder if not os.path.exists(path): # create this path if it doesn't exist os.makedirs(path) onnx_model_name = f"MLP.REG.{self.target_column}.{self.data.shape[0]}.onnx" path += "\\" + onnx_model_name loaded_keras_model = load_model(f"Models\\MLP.REG.{self.target_column}.{self.data.shape[0]}.h5") onnx_model, _ = tf2onnx.convert.from_keras(loaded_keras_model, output_path=path) onnx.save(onnx_model, path ) print(f'Saved model to {path}')
Результаты
Возможно, вы заметили, что я решил сохранить модель ONNX в родительском каталоге Files. Почему там? Это связано с тем, что файл ONNX проще включить в нашу MQL5-программу в качестве ресурса, например советника или индикатора.
04: Получение встроенной модели ONNX в MQL5
#resource "\\Files\\ONNX Models\\MLP.REG.CLOSE.10000.onnx" as uchar RNNModel[]
Команда импортирует модель ONNX и сохраняет ее внутри массива RNNModel uchar.
Следующее, что нам нужно сделать, это определить хэндл ONNX в качестве глобальной переменной и создать хэндл внутри функции OnInit.
Inside ONNX mt5.mq5 EA
long mlp_onnxhandle; #include <MALE5\preprocessing.mqh> CPreprocessing<vectorf, matrixf> *norm_x; int inputs[], outputs[]; vectorf OPEN, HIGH, LOW; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if (!LoadNormParams()) //Load the normalization parameters we saved once { Print("Normalization parameters csv files couldn't be found \nEnsure you are collecting data and Normalizing them using [ONNX get data.ex5] Script \nTrain the Python model again if necessary"); return INIT_FAILED; } //--- ONNX SETTINGS mlp_onnxhandle = OnnxCreateFromBuffer(RNNModel, MQLInfoInteger(MQL_DEBUG) ? ONNX_DEBUG_LOGS : ONNX_DEFAULT); //creating onnx handle buffer | rUN DEGUG MODE during debug mode if (mlp_onnxhandle == INVALID_HANDLE) { Print("OnnxCreateFromBuffer Error = ",GetLastError()); return INIT_FAILED; } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) OnnxTypeInfo type_info; //Getting onnx information for Reference In case you forgot what the loaded ONNX is all about long input_count=OnnxGetInputCount(mlp_onnxhandle); Print("model has ",input_count," input(s)"); for(long i=0; i<input_count; i++) { string input_name=OnnxGetInputName(mlp_onnxhandle,i); Print(i," input name is ",input_name); if(OnnxGetInputTypeInfo(mlp_onnxhandle,i,type_info)) { PrintTypeInfo(i,"input",type_info); ArrayCopy(inputs, type_info.tensor.dimensions); } } long output_count=OnnxGetOutputCount(mlp_onnxhandle); Print("model has ",output_count," output(s)"); for(long i=0; i<output_count; i++) { string output_name=OnnxGetOutputName(mlp_onnxhandle,i); Print(i," output name is ",output_name); if(OnnxGetOutputTypeInfo(mlp_onnxhandle,i,type_info)) { PrintTypeInfo(i,"output",type_info); ArrayCopy(outputs, type_info.tensor.dimensions); } } //--- if (MQLInfoInteger(MQL_DEBUG)) { Print("Inputs & Outputs"); ArrayPrint(inputs); ArrayPrint(outputs); } const long input_shape[] = {batch_size, 3}; if (!OnnxSetInputShape(mlp_onnxhandle, 0, input_shape)) //Giving the Onnx handle the input shape { printf("Failed to set the input shape Err=%d",GetLastError()); return INIT_FAILED; } const long output_shape[] = {batch_size, 1}; if (!OnnxSetOutputShape(mlp_onnxhandle, 0, output_shape)) //giving the onnx handle the output shape { printf("Failed to set the input shape Err=%d",GetLastError()); return INIT_FAILED; } //--- return(INIT_SUCCEEDED); }
Результаты
PR 0 18:57:10.265 ONNX mt5 (EURUSD,H1) ONNX: Creating and using per session threadpools since use_per_session_threads_ is true CN 0 18:57:10.265 ONNX mt5 (EURUSD,H1) ONNX: Dynamic block base set to 0 EE 0 18:57:10.266 ONNX mt5 (EURUSD,H1) ONNX: Initializing session. IM 0 18:57:10.266 ONNX mt5 (EURUSD,H1) ONNX: Adding default CPU execution provider. JN 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Use DeviceBasedPartition as default QK 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Saving initialized tensors. GR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Done saving initialized tensors RI 0 18:57:10.269 ONNX mt5 (EURUSD,H1) ONNX: Session successfully initialized. JF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) model has 1 input(s) QR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 input name is input_1 NF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) type ONNX_TYPE_TENSOR PM 0 18:57:10.269 ONNX mt5 (EURUSD,H1) data type ONNX_TYPE_TENSOR HI 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape [-1, 3] FS 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 input shape must be defined explicitly before model inference NE 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape of input data can be reduced to [3] if undefined dimension set to 1 GD 0 18:57:10.269 ONNX mt5 (EURUSD,H1) model has 1 output(s) GQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 output name is dense_1 LJ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) type ONNX_TYPE_TENSOR NQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) data type ONNX_TYPE_TENSOR LF 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape [-1, 1] KQ 0 18:57:10.269 ONNX mt5 (EURUSD,H1) 0 output shape must be defined explicitly before model inference CO 0 18:57:10.269 ONNX mt5 (EURUSD,H1) shape of output data can be reduced to [1] if undefined dimension set to 1 GR 0 18:57:10.269 ONNX mt5 (EURUSD,H1) Inputs & Outputs IE 0 18:57:10.269 ONNX mt5 (EURUSD,H1) -1 3 CK 0 18:57:10.269 ONNX mt5 (EURUSD,H1) -1 1
Получение реальных данных
Как я уже говорил ранее, живые данные должны быть получены с рынка и нормализованы так же, как они были нормализованы при сборе данных для обучения.
Inside ONNX mt5.mq5 EA
matrixf GetLiveData(uint start, uint total) { matrixf return_matrix(total, 3); OPEN.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_OPEN, start, total); HIGH.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_HIGH, start, total); LOW.CopyRates(Symbol(), PERIOD_CURRENT,COPY_RATES_LOW, start, total); return_matrix.Col(OPEN, 0); return_matrix.Col(HIGH, 1); return_matrix.Col(LOW, 2); if (!norm_x.Normalization(return_matrix)) Print("Failed to Normalize"); return return_matrix; }
Рабочий экземпляр класса norm_x был объявлен внутри функции LoadNormParams() в OnInit. Функция загружает сохраненные параметры нормализации из соответствующего CSV-файла.
Inside ONNX mt5.mq5 EA
bool LoadNormParams() { vectorf min = {}, max ={}, mean={} , std = {}; csv_name_ = Symbol()+"."+EnumToString(Period())+"."+string(total_bars); switch(NORM) { case NORM_MEAN_NORM: mean = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.mean.csv"); //--- Loading the mean min = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.min.csv"); //--- Loading the min max = ReadCsvVector(normparams_folder+csv_name_+".mean_norm_scaler.max.csv"); //--- Loading the max if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMean ",mean,"\nMin ",min,"\nMax ",max); norm_x = new CPreprocessing<vectorf,matrixf>(max, mean, min); if (mean.Sum()<=0 && min.Sum()<=0 && max.Sum() <=0) return false; break; case NORM_MIN_MAX_SCALER: min = ReadCsvVector(normparams_folder+csv_name_+".min_max_scaler.min.csv"); //--- Loading the min max = ReadCsvVector(normparams_folder+csv_name_+".min_max_scaler.max.csv"); //--- Loading the max if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMin ",min,"\nMax ",max); norm_x = new CPreprocessing<vectorf,matrixf>(max, min); if (min.Sum()<=0 && max.Sum() <=0) return false; break; case NORM_STANDARDIZATION: mean = ReadCsvVector(normparams_folder+csv_name_+".standardization_scaler.mean.csv"); //--- Loading the mean std = ReadCsvVector(normparams_folder+csv_name_+".standardization_scaler.std.csv"); //--- Loading the std if (MQLInfoInteger(MQL_DEBUG)) Print(EnumToString(NORM),"\nMean ",mean,"\nStd ",std); norm_x = new CPreprocessing<vectorf,matrixf>(mean, std, NORM_STANDARDIZATION); if (mean.Sum()<=0 && std.Sum() <=0) return false; break; } return true; }
05: Запуск модели в режиме реального времени
Чтобы использовать модель внутри функции OnTick, необходимо вызвать функцию OnnxRun и передать ей хэндл ONNX, вектор или матрицу значений float как для входных данных, так и для прогнозов.
Inside ONNX mt5.mq5 EA
void OnTick() { //--- matrixf input_data = GetLiveData(0,1); vectorf output_data(1); //It is very crucial to resize this vector or matrix if (!OnnxRun(mlp_onnxhandle, ONNX_NO_CONVERSION, input_data, output_data)) { Print("Failed to Get the Predictions Err=",GetLastError()); ExpertRemove(); return; } Comment("inputs_data\n",input_data,"\npredictions\n",output_data); }
Вектор выходных данных или матрицы float необходимо изменить, чтобы избежать ошибки 5805 (ERR_ONNX_INVALID_PARAMETER). Поскольку у меня есть только один выход в нейронной сети, я изменил размер этого вектора, чтобы он имел размер 1 Если бы я тогда использовал матрицу, я должен был изменить ее размер до 1 строки и 1 столбца.
Результаты
Всё работает нормально. Сейчас мы используем модель нейронной сети, созданную и обученную с использованием Python внутри MetaTrader5. Процедура не так сложна.
Преимущества использования ONNX в MQL5
- Взаимодействие: ONNX предоставляет общий формат для представления моделей глубокого обучения. Этот формат позволяет моделям, обученным в одной среде глубокого обучения (такой как TensorFlow, PyTorch или scikit-learn), использоваться в MQL5 без необходимости обширной повторной реализации модели. Это может сэкономить много времени, поскольку нам больше не придется жестко кодировать модели с нуля, чтобы заставить их работать в MQL5.
- Гибкость: ONNX поддерживает широкий спектр типов моделей глубокого обучения: от традиционных нейронных сетей прямого распространения до более сложных моделей, таких как рекуррентные нейронные сети (RNN) и сверточные нейронные сети (CNN). Эта гибкость делает его пригодным для различных применений.
- Эффективность: модели ONNX можно оптимизировать для эффективного развертывания на различном оборудовании и платформах. Это означает, что вы можете развертывать модели на периферийных устройствах, мобильных устройствах, облачных серверах и даже в специализированных аппаратных ускорителях.
- Поддержка сообщества: ONNX располагает существенной поддержкой сообщества. Основные платформы глубокого обучения, такие как TensorFlow, PyTorch и scikit-learn, поддерживают экспорт моделей в формат ONNX, а различные механизмы выполнения, такие как ONNX Runtime, упрощают развертывание ONNX-моделей.
- Широкая экосистема: ONNX интегрирован в различные пакеты программного обеспечения, имеется обширный набор инструментов для работы с ONNX-моделями. С помощью этих инструментов вы можете конвертировать, оптимизировать и запускать модели в формате ONNX.
- Кроссплатформенная совместимость: Модели, экспортированные в формате ONNX, могут работать на различных операционных системах и оборудовании без изменений.
- Эволюция модели: ONNX поддерживает управление версиями и эволюцию модели. Вы можете со временем улучшать и расширять свои модели, сохраняя при этом совместимость с предыдущими версиями.
- Стандартизация: ONNX становится фактическим стандартом взаимодействия между различными платформами глубокого обучения, что упрощает для сообщества обмен моделями и инструментами.
Заключение
ONNX особенно ценен в сценариях, где вам необходимо использовать модели в разных платформах, развертывать модели на различных платформах или сотрудничать с другими людьми, которые могут использовать разные инструменты глубокого обучения. Он упрощает процесс работы с моделями глубокого обучения, а по мере дальнейшего роста экосистемы преимущества ONNX становятся еще более значимыми. В этой статье мы рассмотрели пять важных шагов, которые необходимо выполнить, чтобы начать работу с рабочей моделью. По меньшей мере вы можете расширить этот код в соответствии со своими потребностями. Кроме того, чтобы программа работала с тестером стратегий, CSV-файлы нормализации необходимо прочитать внутри тестера. В текущей статье я это не рассматривал.
Спасибо за внимание!
Файл | Применение |
---|---|
neuralnet.py | Основной файл Python-скрипта. Содержит всю реализацию нейронной сети на языке Python |
ONNX mt5.mq5 | Советник, показывающий, как использовать модель ONNX в торговле |
ONNX get data.mq5 | Скрипт для сбора и подготовки данных для совместного использования с Python-скриптом |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/13394
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования