Регрессионные модели библиотеки Scikit-learn и их экспорт в ONNX

MetaQuotes | 8 ноября, 2023

ONNX (Open Neural Network Exchange) представляет собой формат для описания и обмена моделями машинного обучения, обеспечивая возможность переноса моделей между различными фреймворками машинного обучения. В глубоком обучении и нейронных сетях часто используются числа типа данных float32. Они широко применяются, поскольку обычно обеспечивают приемлемую точность и эффективность для обучения моделей глубокого обучения.

Некоторые классические модели машинного обучения трудно представить в виде ONNX-операторов, поэтому для их реализации в ONNX были введены дополнительные ML-операторы (ai.onnx.ml). Следует отметить, что согласно спецификации ONNX, ключевые операторы из этого набора (LinearRegressor, SVMRegressor, TreeEnsembleRegressor) могут принимать различные типы входных данных(tensor(float), tensor(double), tensor(int64), tensor(int32)), но на выходе всегда возвращают тип tensor(float). При этом параметризация этих операторов также производится при помощи чисел float, что может ограничивать точность вычислений, особенно если для определения параметров оригинальной модели использовались числа double.

Это может привести к потере точности при преобразовании моделей или использовании различных типов данных в процессе конвертации и обработки данных в ONNX. Здесь многое зависит от конвертера, как мы увидим далее, для некоторых моделей удается обойти эти ограничения и обеспечить полную переносимость ONNX-моделей и работать с ними в double без потери точности. Важно учитывать эти особенности при работе с моделями и их представлении в ONNX, особенно в случаях, когда точность представления данных имеет значение.

Scikit-learn является одной из наиболее популярных и широко используемых библиотек для машинного обучения в сообществе Python. Она предлагает широкий выбор алгоритмов, простой в использовании интерфейс и хорошую документацию. В предыдущей статье "Классификационные модели библиотеки Scikit-learn и их экспорт в ONNX"  рассматривались модели классификации.

В данной статье мы рассмотрим применение регрессионных моделей пакета Scikit-learn, рассчитаем их параметры с точностью double для тестового набора данных, попробуем их сконвертировать в ONNX-формат для float и double и использовать полученные модели в программах на MQL5. Также мы сравним точность работы оригинальных моделей и их ONNX-версий для float и double. Кроме того, мы рассмотрим ONNX-представление регресионных моделей, это позволит лучше понять их внутреннее устройство и принцип работы.


Содержание



If it bothers you, welcome to contribute

На форуме разработчиков ONNX Runtime один из пользователей сообщил о том, что при исполнении модели через ONNX Runtime он столкнулся с ошибкой "[ONNXRuntimeError] : 9 : NOT_IMPLEMENTED : Could not find an implementation for the node LinearRegressor:LinearRegressor(1)":

Hi all, getting this error when trying to inferance a linear regression model. PLease help me resolve this.

Сообщение об ошибке "NOT_IMPLEMENTED : Could not find an implementation for the node LinearRegressor:LinearRegressor(1)" с форума разработчиков ONNX Runtime

Сообщение об ошибке "NOT_IMPLEMENTED : Could not find an implementation for the node LinearRegressor:LinearRegressor(1)" с форума разработчиков ONNX Runtime

Ответ разработчика:

It is because we only implemented it for float32, not float64. But your model needs float64.

See:
https://github.com/microsoft/onnxruntime/blob/master/onnxruntime/core/providers/cpu/ml/linearregressor.cc#L16

If it bothers you, welcome to contribute.

В ONNX-модели пользователя вызывается оператор ai.onnx.ml.LinearRegressor с double (float64), и сообщение об ошибке возникает из-за того, что в ONNX Runtime поддержка оператора LinearRegressor() с double не реализована.

Согласно спецификации оператора ai.onnx.ml.LinearRegressor, входной тип данных double возможен (T: tensor(float), tensor(double), tensor(int64), tensor(int32)), однако разработчики сознательно отказались от его реализации.

Причина в том, что на выходе всегда возвращается значение Y : tensor(float). Более того, расчетные параметры задаются в числах float (coefficients : list of floats, intercepts : list of floats).

Получается, что при расчетах в double этот оператор понижает точность до float и его реализация в расчетах на double имеет сомнительную ценность.

Описание оператора ai.onnx.ml.LinearRegressor

Описание оператора ai.onnx.ml.LinearRegressor


Таким образом, понижение точности до float в параметрах и выходном значении делают невозможной полноценную работу ai.onnx.ml.LinearRegressor с числами double (float64). Вероятно, по этой причине разработчики ONNXRuntime решили отказаться от его реализации для типа double.

Способ "добавления поддержки double" разработчики показали в комментариях к коду (выделено желтым).

В ONNX Runtime его расчет производится при помощи класса LinearRegressor (https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/providers/cpu/ml/linearregressor.h).

Параметры оператора coefficients_ и intercepts_ храняться в виде std::vector<float>:

#pragma once

#include "core/common/common.h"
#include "core/framework/op_kernel.h"
#include "core/util/math_cpuonly.h"
#include "ml_common.h"

namespace onnxruntime {
namespace ml {

class LinearRegressor final : public OpKernel {
 public:
  LinearRegressor(const OpKernelInfo& info);
  Status Compute(OpKernelContext* context) const override;

 private:
  int64_t num_targets_;
  std::vector<float> coefficients_;
  std::vector<float> intercepts_;
  bool use_intercepts_;
  POST_EVAL_TRANSFORM post_transform_;
};

}  // namespace ml
}  // namespace onnxruntime

Реализация расчета LinearRegressor (https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/providers/cpu/ml/linearregressor.cc)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "core/providers/cpu/ml/linearregressor.h"
#include "core/common/narrow.h"
#include "core/providers/cpu/math/gemm.h"

namespace onnxruntime {
namespace ml {

ONNX_CPU_OPERATOR_ML_KERNEL(
    LinearRegressor,
    1,
    // KernelDefBuilder().TypeConstraint("T", std::vector<MLDataType>{
    //                                            DataTypeImpl::GetTensorType<float>(),
    //                                            DataTypeImpl::GetTensorType<double>()}),
    KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<float>()),
    LinearRegressor);

LinearRegressor::LinearRegressor(const OpKernelInfo& info)
    : OpKernel(info),
      intercepts_(info.GetAttrsOrDefault<float>("intercepts")),
      post_transform_(MakeTransform(info.GetAttrOrDefault<std::string>("post_transform", "NONE"))) {
  ORT_ENFORCE(info.GetAttr<int64_t>("targets", &num_targets_).IsOK());
  ORT_ENFORCE(info.GetAttrs<float>("coefficients", coefficients_).IsOK());

  // use the intercepts_ if they're valid
  use_intercepts_ = intercepts_.size() == static_cast<size_t>(num_targets_);
}

// Use GEMM for the calculations, with broadcasting of intercepts
// https://github.com/onnx/onnx/blob/main/docs/Operators.md#Gemm
//
// X: [num_batches, num_features]
// coefficients_: [num_targets, num_features]
// intercepts_: optional [num_targets].
// Output: X * coefficients_^T + intercepts_: [num_batches, num_targets]
template <typename T>
static Status ComputeImpl(const Tensor& input, ptrdiff_t num_batches, ptrdiff_t num_features, ptrdiff_t num_targets,
                          const std::vector<float>& coefficients,
                          const std::vector<float>* intercepts, Tensor& output,
                          POST_EVAL_TRANSFORM post_transform,
                          concurrency::ThreadPool* threadpool) {
  const T* input_data = input.Data<T>();
  T* output_data = output.MutableData<T>();

  if (intercepts != nullptr) {
    TensorShape intercepts_shape({num_targets});
    onnxruntime::Gemm<T>::ComputeGemm(CBLAS_TRANSPOSE::CblasNoTrans, CBLAS_TRANSPOSE::CblasTrans,
                                      num_batches, num_targets, num_features,
                                      1.f, input_data, coefficients.data(), 1.f,
                                      intercepts->data(), &intercepts_shape,
                                      output_data,
                                      threadpool);
  } else {
    onnxruntime::Gemm<T>::ComputeGemm(CBLAS_TRANSPOSE::CblasNoTrans, CBLAS_TRANSPOSE::CblasTrans,
                                      num_batches, num_targets, num_features,
                                      1.f, input_data, coefficients.data(), 1.f,
                                      nullptr, nullptr,
                                      output_data,
                                      threadpool);
  }

  if (post_transform != POST_EVAL_TRANSFORM::NONE) {
    ml::batched_update_scores_inplace(gsl::make_span(output_data, SafeInt<size_t>(num_batches) * num_targets),
                                      num_batches, num_targets, post_transform, -1, false, threadpool);
  }
  return Status::OK();
}

Status LinearRegressor::Compute(OpKernelContext* ctx) const {
  Status status = Status::OK();

  const auto& X = *ctx->Input<Tensor>(0);
  const auto& input_shape = X.Shape();

  if (input_shape.NumDimensions() > 2) {
    return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Input shape had more than 2 dimension. Dims=",
                           input_shape.NumDimensions());
  }

  ptrdiff_t num_batches = input_shape.NumDimensions() <= 1 ? 1 : narrow<ptrdiff_t>(input_shape[0]);
  ptrdiff_t num_features = input_shape.NumDimensions() <= 1 ? narrow<ptrdiff_t>(input_shape.Size())
                                                            : narrow<ptrdiff_t>(input_shape[1]);
  Tensor& Y = *ctx->Output(0, {num_batches, num_targets_});
  concurrency::ThreadPool* tp = ctx->GetOperatorThreadPool();

  auto element_type = X.GetElementType();

  switch (element_type) {
    case ONNX_NAMESPACE::TensorProto_DataType_FLOAT: {
      status = ComputeImpl<float>(X, num_batches, num_features, narrow<ptrdiff_t>(num_targets_), coefficients_,
                                  use_intercepts_ ? &intercepts_ : nullptr,
                                  Y, post_transform_, tp);

      break;
    }
    case ONNX_NAMESPACE::TensorProto_DataType_DOUBLE: {
      // TODO: Add support for 'double' to the scoring functions in ml_common.h
      // once that is done we can just call ComputeImpl<double>...
      // Alternatively we could cast the input to float.
    }
    default:
      status = ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Unsupported data type of ", element_type);
  }

  return status;
}

}  // namespace ml
}  // namespace onnxruntime

Получается, что есть вариант использовать числа double как входные значения и расчет оператора производить c float параметрами. Также можно было бы понизить точность входных данных до float. Однако все эти варианты нельзя считать нормальным решением.

Спецификация оператора ai.onnx.ml.LinearRegressor ограничивает возможность полноценной работы с числами double, поскольку параметры и выходное значение ограничены типом float.

Аналогичная ситуация и с другими ML-операторами ONNX, например ai.onnx.ml.SVMRegressor и ai.onnx.ml.TreeEnsembleRegressor.

Таким образом, все разработчики сред исполнения ONNX-моделей в double сталкиваются с этим ограничением спецификации. Вариантом решения может быть расширение спецификации ONNX (или добавление аналогичных операторов LinearRegressor64, SVMRegressor64 и TreeEnsembleRegressor64 с параметрами и выходными значениями double), однако пока эта проблема остается нерешенной.

Здесь многое зависит от конвертера в ONNX, для моделей с расчетом в double лучше отказаться от использования этих операторов (однако это возможно не всегда), в данном случае с моделью пользователя конвертер в ONNX сработал не лучшим образом.

Как мы увидим далее, конвертеру sklearn-onnx удается обойти ограничение LinearRegressor: для ONNX-моделей double вместо него используются ONNX-операторы MatMul() и Add(). Благодаря такому способу множество регрессионных моделей библиотеки Scikit-learn удается успешно конвертировать в ONNX-модели с расчетом в double, и эти модели показывают сохранение точности оригинальных моделей в double.


1. Тестовый набор данных

Для запуска примеров понадобится установить Python (мы использовали версию 3.10.8), дополнительные библиотеки (pip install -U scikit-learn numpy matplotlib onnx onnxruntime skl2onnx) и указать путь к Python в редакторе MetaEditor (в меню Tools->Options->Compilers->Python).

В качестве тестового набора данных будем использовать сгенерированные значения функции y = 4*X + 10*sin(X*0.5).

Для отображения графика подобной функции откроем MetaEditor, создадим файл RegressionData.py, скопируем текст скрипта и запустим его, нажав на кнопку "Compile".

Скрипт отображения тестового набора данных

# RegressionData.py
# The code plots the synthetic data, used for all regression models
# Copyright 2023, MetaQuotes Ltd.
# https://mql5.com

# import necessary libraries
import numpy as np
import matplotlib.pyplot as plt

# generate synthetic data for regression
X = np.arange(0,100,1).reshape(-1,1)
y = 4*X + 10*np.sin(X*0.5)

# set the figure size
plt.figure(figsize=(8,5))

# plot the initial data for regression
plt.scatter(X, y, label='Regression Data', marker='o')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.title('Regression data')
plt.show()

В результате будет выведен график функции, которую мы будем использовать для проверки методов регрессии.

Рис.1. Функция для тестирования методов регрессии

Рис.1. Функция для тестирования методов регрессии


2. Модели регрессии

Цель задачи регрессии заключается в поиске такой математической функции или модели, которая наилучшим образом описывает зависимость между признаками и целевой переменной, чтобы предсказать числовое значение для новых данных. Это позволяет сделать прогнозы, оптимизировать решения и принимать информированные решения на основе данных.

Рассмотрим основные модели регресии пакета scikit-learn.

2.0. Список моделей регрессии пакета Scikit-learn

Для вывода списка доступных моделей регрессии Scikit-learn можно использовать скрипт:

# ScikitLearnRegressors.py
# The script lists all the regression algorithms available inb scikit-learn
# Copyright 2023, MetaQuotes Ltd.
# https://mql5.com

# print Python version
from platform import python_version  
print("The Python version is ", python_version()) 

# print scikit-learn version
import sklearn
print('The scikit-learn version is {}.'.format(sklearn.__version__))

# print scikit-learn regression models
from sklearn.utils import all_estimators

regressors = all_estimators(type_filter='regressor')
for index, (name, RegressorClass) in enumerate(regressors, start=1):
    print(f"Regressor {index}: {name}")

Результат:

The Python version is  3.10.8
The scikit-learn version is 1.3.2.
Regressor 1: ARDRegression
Regressor 2: AdaBoostRegressor
Regressor 3: BaggingRegressor
Regressor 4: BayesianRidge
Regressor 5: CCA
Regressor 6: DecisionTreeRegressor
Regressor 7: DummyRegressor
Regressor 8: ElasticNet
Regressor 9: ElasticNetCV
Regressor 10: ExtraTreeRegressor
Regressor 11: ExtraTreesRegressor
Regressor 12: GammaRegressor
Regressor 13: GaussianProcessRegressor
Regressor 14: GradientBoostingRegressor
Regressor 15: HistGradientBoostingRegressor
Regressor 16: HuberRegressor
Regressor 17: IsotonicRegression
Regressor 18: KNeighborsRegressor
Regressor 19: KernelRidge
Regressor 20: Lars
Regressor 21: LarsCV
Regressor 22: Lasso
Regressor 23: LassoCV
Regressor 24: LassoLars
Regressor 25: LassoLarsCV
Regressor 26: LassoLarsIC
Regressor 27: LinearRegression
Regressor 28: LinearSVR
Regressor 29: MLPRegressor
Regressor 30: MultiOutputRegressor
Regressor 31: MultiTaskElasticNet
Regressor 32: MultiTaskElasticNetCV
Regressor 33: MultiTaskLasso
Regressor 34: MultiTaskLassoCV

Regressor 35: NuSVR
Regressor 36: OrthogonalMatchingPursuit
Regressor 37: OrthogonalMatchingPursuitCV
Regressor 38: PLSCanonical
Regressor 39: PLSRegression
Regressor 40: PassiveAggressiveRegressor
Regressor 41: PoissonRegressor
Regressor 42: QuantileRegressor
Regressor 43: RANSACRegressor
Regressor 44: RadiusNeighborsRegressor
Regressor 45: RandomForestRegressor
Regressor 46: RegressorChain
Regressor 47: Ridge
Regressor 48: RidgeCV
Regressor 49: SGDRegressor
Regressor 50: SVR
Regressor 51: StackingRegressor
Regressor 52: TheilSenRegressor
Regressor 53: TransformedTargetRegressor
Regressor 54: TweedieRegressor
Regressor 55: VotingRegressor

Для удобства в этом списке моделей регрессии они выделены разными цветами. Модели, которые требуют базовых моделей, выделены серым цветом, в то время как остальные модели могут использоваться самостоятельно. Отметим, что зеленым цветом отмечены модели, которые удалось успешно экспортировать в формат ONNX, красным цветом отмечены модели, при конвертации которых в текущей версии scikit-learn 1.2.2 возникают ошибки. Синим цветом выделены методы, которые не подходят для рассматриваемой нами тестовой задачи.

Для анализа качества моделей используются метрики регрессии, это функции от истинных и предсказанных значений. В языке MQL5 доступно несколько различных метрик, подробнее о них в статье "Оценка ONNX-моделей при помощи регрессионных метрик".

В этой статье для сравнения качества различных моделей будут использоваться 3 метрики:

  1. Коэффициент детерминации R-квадрат (R-squared, R2);
  2. Средняя абсолютная ошибка (Mean Absolute Error, MAE);
  3. Среднеквадратичная ошибка (Mean Squared Error, MSE).


2.1. Регрессионные модели библиотеки Scikit-learn, которые конвертируются в ONNX-модели float и double

В этом разделе приводятся модели регрессии, которые успешно конвертируются в ONNX в форматах float и double.

Все рассматриваемые далее регрессионные модели приведены в виде:

  1. Описание модели, принцип работы, преимущества и ограничения
  2. Скрипт на Python для создания модели, ее экспорта в ONNX-файлы в форматах float и double, и исполнения полученных моделей через ONNX Runtime в Python. Для оценки качества оригинальной и ONNX-моделей используются метрики R^2, MAE, MSE, рассчитываемые при помощи sklearn.metrics.
  3. Скрипт MQL5 для исполнения ONNX-моделей (float и double) через ONNX Runtime, метрики рассчитываются через RegressionMetric().
  4. ONNX-представление моделей в Netron для float и double.


    2.1.1. sklearn.linear_model.ARDRegression

    ARDRegression (Automatic Relevance Determination Regression) - это метод регрессии, который предназначен для решения задачи регрессии, при этом автоматически определяет важность (релевантность) признаков и устанавливает их веса в процессе обучения модели.

    ARDRegression позволяет обнаруживать и использовать только наиболее важные признаки для построения регрессионной модели, что может быть полезным в случаях, когда имеется большое количество признаков.

    Принцип работы ARDRegression:

    1. Линейная регрессия: ARDRegression основана на линейной регрессии, где предполагается линейная зависимость между независимыми переменными (признаками) и целевой переменной.
    2. Автоматическое определение важности признаков: Основное отличие ARDRegression заключается в том, что он автоматически определяет, какие признаки наиболее важны для предсказания целевой переменной. Это достигается путем введения априорных распределений (регуляризации) над весами которые позволяют модели автоматически установить нулевые веса для малозначимых признаков.
    3. Оценка апостериорных вероятностей: ARDRegression вычисляет апостериорные вероятности для каждого признака, что позволяет определить их важность. Признаки с высокими апостериорными вероятностями считаются релевантными и получают ненулевые веса, в то время как признаки с низкими апостериорными вероятностями получают нулевые веса.
    4. Снижение размерности: Таким образом, ARDRegression может привести к снижению размерности данных, удаляя незначимые признаки.

    Преимущества ARDRegression:

    Ограничения ARDRegression:

    ARDRegression - это метод регрессии, который автоматически определяет важность признаков и устанавливает их веса на основе апостериорных вероятностей. Этот метод полезен, когда требуется учитывать только важные признаки для построения регрессионной модели и снижения размерности данных.


    2.1.1.1. Код создания модели ARDRegression и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.ARDRegression, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # ARDRegression.py
    # The code demonstrates the process of training ARDRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import ARDRegression
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name="ARDRegression"
    onnx_model_filename = data_path + "ard_regression"

    # create an ARDRegression model
    regression_model = ARDRegression()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)

    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType
    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)

    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Скрипт создает и обучает модель sklearn.linear_model.ARDRegression (оригинальная модель считается в double), затем производится экспорт модели в ONNX для float и double (ard_regression_float.onnx и ard_regression_double.onnx) и сравнивается точность ее работы.

    Он также создает файлы ARDRegression_plot_float.png и ARDRegression_plot_double.png, позволяющие визуально оценить результаты работы ONNX-моделей для float и double (рис. 2-3).

     Рис.2. Результат работы скрипта ARDRegression.py (float)

    Рис.2. Результат работы скрипта ARDRegression.py (float)


     Рис.3. Результат работы скрипта ARDRegression.py (double)

    Рис.3. Результат работы скрипта ARDRegression.py (double)

    Визуально ONNX-модели для float и double выглядят одинаково (рис.2-3), во вкладке Journal можно посмотреть детали:

    Python  ARDRegression Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382628120845
    Python  Mean Absolute Error: 6.347568012853758
    Python  Mean Squared Error: 49.77815934891289
    Python  
    Python  ARDRegression ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ard_regression_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382627587808
    Python  Mean Absolute Error: 6.347568283744705
    Python  Mean Squared Error: 49.778160054267204
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  4
    Python  float ONNX model precision:  6
    Python  
    Python  ARDRegression ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ard_regression_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382628120845
    Python  Mean Absolute Error: 6.347568012853758
    Python  Mean Squared Error: 49.77815934891289
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15

    В данном примере оригинальная модель считалась в double, затем она была экспортирована в ONNX-модели ard_regression_float.onnx и ard_regression_double.onnx для float и double соответственно.

    Если точность модели оценивать по порядку средней абсолютной ошибки (Mean Absolute Error), то точность ONNX-модели для float составляет не более 6 знаков после запятой, ONNX-модель с использованием double показала сохранение точности в 15 знаков после запятой в соответствии с точностью оригинальной модели.

    Свойства ONNX-моделей можно посмотреть в MetaEditor (рис.4-5).


    Рис.4. ONNX-модель ARDRegression (float) в MetaEditor

    Рис.4. ONNX-модель ard_regression_float.onnx в MetaEditor



     Рис.5. ONNX-модель ARDRegression (double) в MetaEditor

    Рис.5. ONNX-модель ard_regression_double.onnx в MetaEditor


    Сравнение float и double ONNX-моделей показывает, что в данном случае расчет ONNX-моделей ARDRegression происходит по-разному: для чисел float используется оператор LinearRegressor() из ONNX-ML, для чисел double используются ONNX-операторы MatMul(), Add() и Reshape().

    Реализация модели в ONNX зависит от конвертера, в примерах для экспорта в ONNX будет использоваться функция skl2onnx.convert_sklearn() библиотеки skl2onnx.


    2.1.1.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели ard_regression_float.onnx и ard_regression_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                ARDRegression.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "ARDRegression"
    #define   ONNXFilenameFloat  "ard_regression_float.onnx"
    #define   ONNXFilenameDouble "ard_regression_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    ARDRegression (EURUSD,H1)       Testing ONNX float: ARDRegression (ard_regression_float.onnx)
    ARDRegression (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382627587808
    ARDRegression (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3475682837447049
    ARDRegression (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781600542671896
    ARDRegression (EURUSD,H1)       
    ARDRegression (EURUSD,H1)       Testing ONNX double: ARDRegression (ard_regression_double.onnx)
    ARDRegression (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382628120845
    ARDRegression (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3475680128537597
    ARDRegression (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781593489128795
    

    Сравнение с оргинальной моделью double в Python:

    Testing ONNX float: ARDRegression (ard_regression_float.onnx)
    Python  Mean Absolute Error: 6.347568012853758
    MQL5:   Mean Absolute Error: 6.3475682837447049
           
    Testing ONNX double: ARDRegression (ard_regression_double.onnx)
    Python  Mean Absolute Error: 6.347568012853758
    MQL5:   Mean Absolute Error: 6.3475680128537597
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.1.3. ONNX-представление моделей ard_regression_float.onnx и ard_regression_double.onnx

    Netron (web-версия) - это инструмент для визуализации моделей и анализа графов вычислений, который может использоваться для моделей формата ONNX (Open Neural Network Exchange).

    Netron представляет графы моделей и их архитектуру в наглядной и интерактивной форме, позволяя исследовать структуру и параметры моделей глубокого обучения, включая модели, созданные с использованием ONNX.

    Основные возможности Netron включают:

    Netron удобен для разработчиков и исследователей в области машинного обучения и глубокого обучения, так как он облегчает визуализацию и анализ моделей, что помогает в понимании и отладке сложных нейронных сетей.

    Этот инструмент позволяет быстро посмотреть модели, исследовать их структуру и параметры, что упрощает работу с глубокими нейронными сетями.

    Подробнее про Netron в статьях: Visualizing your Neural Network with Netron и Visualize Keras Neural Networks with Netron.

    Видео про Netron:





    ONNX-модель ard_regression_float.onnx представлена на рис.6:

    Рис.6. ONNX-представление модели ARDRegression (float) в Netron

    Рис.6. ONNX-представление модели ard_regression_float.onnx в Netron


    ONNX-оператор ai.onnx.ml LinearRegressor() представляет собой часть стандарта ONNX, который описывает модель для задачи регрессии. Этот оператор применяется для выполнения регрессии, то есть предсказания числовых значений (непрерывных) на основе входных признаков.

    Он принимает на вход параметры модели, такие как веса и смещение, а также входные признаки, и выполняет линейную регрессию. Линейная регрессия оценивает параметры (веса) для каждого входного признака и затем выполняет линейную комбинацию этих признаков с весами для получения предсказания.

    Этот оператор выполняет следующие шаги:

    1. Принимает веса и смещение модели, а также входные признаки.
    2. Для каждого примера входных данных выполняет линейную комбинацию весов с соответствующими признаками.
    3. Добавляет смещение к полученному значению.

    Результат представляет собой прогноз целевой переменной в задаче регрессии.

    Параметры оператора LinearRegressor() представлены на рис.7.

    Рис.7. Свойства оператора LinearRegressor модели ARDRegression (float) в Netron

    Рис.7. Свойства оператора LinearRegressor() модели ard_regression_float.onnx в Netron


    ONNX-модель ard_regression_double.onnx изображена на рис.8:

    Рис.8. ONNX-представление модели ARDRegression (double) в Netron

    Рис.8. ONNX-представление модели ard_regression_double.onnx в Netron


    Параметры ONNX-операторов MatMul(), Add() и Reshape() приведены на рис.9-11.

    Рис.9. Свойства оператора MatMul модели ARDRegression (double) в Netron

    Рис.9. Свойства оператора MatMul модели ard_regression_double.onnx в Netron


    ONNX-оператор MatMul (матричное умножение) выполняет умножение двух матриц.

    Он принимает два входа: две матрицы и возвращает их матричное произведение.

    Если у вас есть две матрицы A и B, то результат Matmul(A, B) - это матрица C, где каждый элемент C[i][j] вычисляется как сумма произведений элементов строки i матрицы A на элементы столбца j матрицы B.


    Рис.10. Свойства оператора Add модели ARDRegression (double) в Netron

    Рис.10. Свойства оператора Add модели ard_regression_double.onnx в Netron


    ONNX-оператор Add (сложение) выполняет покомпонентное сложение двух тензоров или массивов одинаковой формы.

    Он принимает два входа и возвращает результат, где каждый элемент результирующего тензора равен сумме соответствующих элементов входных тензоров.


    Рис.11. Свойства оператора Reshape модели ARDRegression (double) в Netron

    Рис.11. Свойства оператора Reshape модели ard_regression_double.onnx в Netron



    ONNX-оператор Reshape(-1,1) используется для изменения формы (или размерности) входных данных. В этом операторе значение -1 для размерности означает, что размер данной размерности должен быть автоматически вычислен на основе остальных размерностей, чтобы обеспечить согласованность данных.

    Значение 1 второй размерности указывает, что после преобразования формы каждый элемент будет иметь одну единственную подразмерность.


    2.1.2. sklearn.linear_model.BayesianRidge

    BayesianRidge - это метод регрессии, который использует байесовский подход для оценки параметров модели. Этот метод позволяет моделировать априорное распределение параметров и обновлять его с учетом данных, чтобы получить апостериорное распределение параметров.

    BayesianRidge представляет собой байесовский метод регрессии, предназначенный для предсказания зависимой переменной на основе одной или нескольких независимых переменных.

    Принцип работы BayesianRidge:

    1. Априорное распределение параметров: Начинается с определения априорного распределения параметров модели. Это распределение представляет собой априорные знания или предположения о параметрах модели до учета данных. В случае BayesianRidge используются априорные распределения с Гауссовской формой.
    2. Обновление распределения параметров: После того как априорное распределение параметров задано, оно обновляется на основе данных. Это делается с использованием метода байесовской теории, где апостериорное распределение параметров вычисляется с учетом данных. Важным аспектом является оценка гиперпараметров, которые влияют на форму апостериорного распределения.
    3. Предсказание: После оценки апостериорного распределения параметров, можно делать предсказания для новых наблюдений. В результате получается распределение прогнозов, а не просто одно точечное значение, что позволяет учесть неопределенность в предсказаниях.

    Преимущества BayesianRidge:

    • Учет неопределенности: BayesianRidge позволяет учитывать неопределенность в параметрах и предсказаниях модели. Вместо точечных прогнозов предоставляются доверительные интервалы.
    • Регуляризация: Байесовский метод регрессии может быть полезен для регуляризации моделей, что помогает предотвратить переобучение.
    • Автоматический отбор признаков: BayesianRidge может автоматически определять важность признаков, уменьшая веса незначительных признаков.

    Ограничения BayesianRidge:

    • Вычислительная сложность: Метод требует вычислительных ресурсов для оценки параметров и вычисления апостериорного распределения.
    • Высокий уровень абстракции: Для понимания и использования BayesianRidge может потребоваться более глубокое понимание байесовской статистики.
    • Не всегда наилучший выбор: BayesianRidge может быть не самым подходящим методом в некоторых задачах регрессии, особенно если данных мало.

    BayesianRidge полезен в задачах регрессии, где неопределенность параметров и предсказаний важна, и в случаях, когда требуется регуляризация модели.

    2.1.2.1. Код создания модели BayesianRidge и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.BayesianRidge, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # BayesianRidge.py
    # The code demonstrates the process of training BayesianRidge model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import BayesianRidge
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "BayesianRidge"
    onnx_model_filename = data_path + "bayesian_ridge"

    # create a Bayesian Ridge regression model
    regression_model = BayesianRidge()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType
    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ", compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType
    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  BayesianRidge Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382628120845
    Python  Mean Absolute Error: 6.347568012853758
    Python  Mean Squared Error: 49.77815934891288
    Python  
    Python  BayesianRidge ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\bayesian_ridge_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382627587808
    Python  Mean Absolute Error: 6.347568283744705
    Python  Mean Squared Error: 49.778160054267204
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  6
    Python  
    Python  BayesianRidge ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\bayesian_ridge_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382628120845
    Python  Mean Absolute Error: 6.347568012853758
    Python  Mean Squared Error: 49.77815934891288
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.12. Результат работы скрипта BayesianRidge.py (float)

    Рис.12. Результат работы скрипта BayesianRidge.py (float ONNX)


    2.1.2.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели bayesian_ridge_float.onnx и bayesian_ridge_double.onnx  и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                BayesianRidge.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "BayesianRidge"
    #define   ONNXFilenameFloat  "bayesian_ridge_float.onnx"
    #define   ONNXFilenameDouble "bayesian_ridge_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    BayesianRidge (EURUSD,H1)       Testing ONNX float: BayesianRidge (bayesian_ridge_float.onnx)
    BayesianRidge (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382627587808
    BayesianRidge (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3475682837447049
    BayesianRidge (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781600542671896
    BayesianRidge (EURUSD,H1)       
    BayesianRidge (EURUSD,H1)       Testing ONNX double: BayesianRidge (bayesian_ridge_double.onnx)
    BayesianRidge (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382628120845
    BayesianRidge (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3475680128537624
    BayesianRidge (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781593489128866
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: BayesianRidge (bayesian_ridge_float.onnx)
    Python  Mean Absolute Error: 6.347568012853758
    MQL5:   Mean Absolute Error: 6.3475682837447049
    
    Testing ONNX double: BayesianRidge (bayesian_ridge_double.onnx)
    Python  Mean Absolute Error: 6.347568012853758
    MQL5:   Mean Absolute Error: 6.3475680128537624
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.2.3. ONNX-представление моделей bayesian_ridge_float.onnx и bayesian_ridge_double.onnx

    Рис.13. ONNX-представление модели bayesian_ridge_float.onnx в Netron

    Рис.13. ONNX-представление модели bayesian_ridge_float.onnx в Netron



    Рис.14. ONNX-представление модели bayesian_ridge_double.onnx в Netron

    Рис.14. ONNX-представление модели bayesian_ridge_double.onnx в Netron


    Примечание к методам ElasticNet и ElasticNetCV

    ElasticNet и ElasticNetCV - это два связанных метода в машинном обучении, используемые для регуляризации моделей регрессии, особенно линейной регрессии. Они имеют общий функционал, но есть различия в способе использования и применения.

        ElasticNet (Elastic Net Regression):

    • Принцип работы: ElasticNet - это метод регрессии, который объединяет в себе регуляризацию Лассо (L1-регуляризация) и регуляризацию Риджа (L2-регуляризация). Он добавляет к функции потерь два компонента регуляризации: один, который штрафует модель за большие абсолютные значения коэффициентов (как Лассо), и другой, который штрафует модель за большие квадраты коэффициентов (как Ридж).
    • Преимущества: ElasticNet обычно используется, когда в данных присутствует мультиколлинеарность (когда признаки сильно коррелированы) и когда необходимо сокращение размерности, а также контроль над значениями коэффициентов.

        ElasticNetCV (Elastic Net Cross-Validation):

    • Принцип работы: ElasticNetCV - это расширение ElasticNet, которое включает в себя автоматический подбор оптимальных гиперпараметров альфа (коэффициент смешивания между L1 и L2 регуляризацией) и лямбда (сила регуляризации) с использованием кросс-валидации. Он выполняет перебор различных значений альфа и лямбда и выбирает комбинацию, которая дает наилучшую производительность на кросс-валидации.
    • Преимущества: ElasticNetCV автоматически настраивает параметры модели на основе кросс-валидации, что позволяет выбрать оптимальные значения гиперпараметров без необходимости ручной настройки. Это делает его более удобным для использования и помогает предотвратить переобучение модели.

    Итак, основное отличие между ElasticNet и ElasticNetCV заключается в том, что ElasticNet - это сам метод регрессии, который применяется к данным, а ElasticNetCV - это инструмент, который автоматически находит оптимальные значения гиперпараметров для модели ElasticNet с использованием кросс-валидации. ElasticNetCV полезен, когда вам нужно найти лучшие параметры модели и сделать процесс настройки более автоматизированным.


    2.1.3. sklearn.linear_model.ElasticNet

    ElasticNet - это метод регрессии, который представляет собой комбинацию L1 (Lasso) и L2 (Ridge) регуляризации.

    Этот метод используется для регрессии, то есть для предсказания числовых значений целевой переменной на основе набора признаков. ElasticNet помогает управлять переобучением и учитывает как L1, так и L2 штрафы на коэффициенты модели.

    Принцип работы ElasticNet:

    1. Входные данные: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Целевая функция: ElasticNet минимизирует функцию потерь, которая включает две компоненты - среднеквадратичное отклонение (MSE) и две регуляризации: L1 (Lasso) и L2 (Ridge).
      Это означает, что целевая функция имеет следующий вид:
      Целевая функция = MSE + α * L1 + β * L2
      Где α и β - гиперпараметры, которые контролируют веса L1 и L2 регуляризации соответственно.
    3. Подбор оптимальных α и β: Для нахождения наилучших значений α и β обычно используется метод перекрестной проверки (кросс-валидации). Это позволяет выбрать оптимальные значения, которые обеспечивают хороший баланс между уменьшением переобучения и сохранением важных признаков.
    4. Обучение модели: ElasticNet обучает модель с учетом оптимальных α и β, минимизируя целевую функцию.
    5. Предсказание: После обучения модели ElasticNet может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества ElasticNet:

    Ограничения ElasticNet:

    ElasticNet - это мощный метод регрессии, который может быть полезен в задачах, где важен отбор признаков и управление переобучением.


    2.1.3.1. Код создания модели ElasticNet и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.ElasticNet, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # ElasticNet.py
    # The code demonstrates the process of training ElasticNet model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import ElasticNet
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "ElasticNet"
    onnx_model_filename = data_path + "elastic_net"

    # create an ElasticNet model
    regression_model = ElasticNet()

    # fit the model to the data
    regression_model.fit(X,y)

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  ElasticNet Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962377031744798
    Python  Mean Absolute Error: 6.344394662876524
    Python  Mean Squared Error: 49.78556489812415
    Python  
    Python  ElasticNet ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\elastic_net_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962377032416807
    Python  Mean Absolute Error: 6.344395027824294
    Python  Mean Squared Error: 49.78556400887057
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  6
    Python  float ONNX model precision:  5
    Python  
    Python  ElasticNet ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\elastic_net_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962377031744798
    Python  Mean Absolute Error: 6.344394662876524
    Python  Mean Squared Error: 49.78556489812415
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

     Рис.15. Результат работы скрипта ElasticNet.py (float ONNX)

    Рис.15. Результат работы скрипта ElasticNet.py (float ONNX)


    2.1.3.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели elastic_net_double.onnx и elastic_net_float.onnx  и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                   ElasticNet.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "ElasticNet"
    #define   ONNXFilenameFloat  "elastic_net_float.onnx"
    #define   ONNXFilenameDouble "elastic_net_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    ElasticNet (EURUSD,H1)  Testing ONNX float: ElasticNet (elastic_net_float.onnx)
    ElasticNet (EURUSD,H1)  MQL5:   R-Squared (Coefficient of determination): 0.9962377032416807
    ElasticNet (EURUSD,H1)  MQL5:   Mean Absolute Error: 6.3443950278242944
    ElasticNet (EURUSD,H1)  MQL5:   Mean Squared Error: 49.7855640088705869
    ElasticNet (EURUSD,H1)  
    ElasticNet (EURUSD,H1)  Testing ONNX double: ElasticNet (elastic_net_double.onnx)
    ElasticNet (EURUSD,H1)  MQL5:   R-Squared (Coefficient of determination): 0.9962377031744798
    ElasticNet (EURUSD,H1)  MQL5:   Mean Absolute Error: 6.3443946628765220
    ElasticNet (EURUSD,H1)  MQL5:   Mean Squared Error: 49.7855648981241217
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: ElasticNet (elastic_net_float.onnx)
    Python  Mean Absolute Error: 6.344394662876524
    MQL5:   Mean Absolute Error: 6.3443950278242944
      
    Testing ONNX double: ElasticNet (elastic_net_double.onnx)
    Python  Mean Absolute Error: 6.344394662876524
    MQL5:   Mean Absolute Error: 6.3443946628765220
    

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.3.3. ONNX-представление моделей elastic_net_float.onnx и elastic_net_double.onnx


    Рис.16. ONNX-представление модели elastic_net_float.onnx в Netron

    Рис.16. ONNX-представление модели elastic_net_float.onnx в Netron



    Рис.17. ONNX-представление модели elastic_net_double.onnx в Netron

    Рис.17. ONNX-представление модели elastic_net_double.onnx в Netron


    2.1.4. sklearn.linear_model.ElasticNetCV

    ElasticNetCV - это расширение метода ElasticNet, предназначенное для автоматического подбора оптимальных значений гиперпараметров α и β (L1 и L2 регуляризации) с использованием метода кросс-валидации.

    Это позволяет находить наилучшее сочетание регуляризаций для модели ElasticNet без необходимости ручной настройки параметров.

    Принцип работы ElasticNetCV:

    1. Входные данные: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Определение диапазона α и β: Пользователь указывает диапазон значений α и β, которые следует рассмотреть в процессе оптимизации. Эти значения обычно выбираются на логарифмической шкале.
    3. Разбиение данных: Набор данных разбивается на несколько фолдов для кросс-валидации. Каждый фолд используется как тестовый набор данных, в то время как остальные фолды используются для обучения.
    4. Кросс-валидация: Для каждой комбинации α и β из указанного диапазона выполняется кросс-валидация. Модель ElasticNet обучается на обучающих данных, а затем оценивается на тестовых данных.
    5. Оценка производительности: Для каждой комбинации α и β вычисляется средняя ошибка на тестовых наборах данных в рамках кросс-валидации.
    6. Выбор оптимальных параметров: Находятся значения α и β, соответствующие минимальной средней ошибке, полученной в результате кросс-валидации.
    7. Обучение модели с оптимальными параметрами: Модель ElasticNetCV обучается с использованием найденных оптимальных значений α и β.
    8. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества ElasticNetCV:

    Ограничения ElasticNetCV:

    ElasticNetCV - это мощный инструмент для автоматической настройки регуляризации в модели ElasticNet и повышения ее производительности.


    2.1.4.1. Код создания модели ElasticNetCV и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.ElasticNetCV, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # ElasticNetCV.py
    # The code demonstrates the process of training ElasticNetCV model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import ElasticNetCV
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "ElasticNetCV"
    onnx_model_filename = data_path + "elastic_net_cv"

    # create an ElasticNetCV model
    regression_model = ElasticNetCV()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  ElasticNetCV Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962137763338385
    Python  Mean Absolute Error: 6.334487104423225
    Python  Mean Squared Error: 50.10218299945999
    Python  
    Python  ElasticNetCV ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\elastic_net_cv_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962137770260989
    Python  Mean Absolute Error: 6.334486542922601
    Python  Mean Squared Error: 50.10217383894468
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  5
    Python  
    Python  ElasticNetCV ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\elastic_net_cv_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962137763338385
    Python  Mean Absolute Error: 6.334487104423225
    Python  Mean Squared Error: 50.10218299945999
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    < Рис.18. Результат работы скрипта ElasticNetCV.py (float ONNX)

    Рис.18. Результат работы скрипта ElasticNetCV.py (float ONNX)


    2.1.4.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели elastic_net_cv_float.onnx и elastic_net_cv_double.onnx  и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                 ElasticNetCV.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "ElasticNetCV"
    #define   ONNXFilenameFloat  "elastic_net_cv_float.onnx"
    #define   ONNXFilenameDouble "elastic_net_cv_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    ElasticNetCV (EURUSD,H1)        Testing ONNX float: ElasticNetCV (elastic_net_cv_float.onnx)
    ElasticNetCV (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9962137770260989
    ElasticNetCV (EURUSD,H1)        MQL5:   Mean Absolute Error: 6.3344865429226038
    ElasticNetCV (EURUSD,H1)        MQL5:   Mean Squared Error: 50.1021738389446938
    ElasticNetCV (EURUSD,H1)        
    ElasticNetCV (EURUSD,H1)        Testing ONNX double: ElasticNetCV (elastic_net_cv_double.onnx)
    ElasticNetCV (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9962137763338385
    ElasticNetCV (EURUSD,H1)        MQL5:   Mean Absolute Error: 6.3344871044232205
    ElasticNetCV (EURUSD,H1)        MQL5:   Mean Squared Error: 50.1021829994599983
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: ElasticNetCV (elastic_net_cv_float.onnx)
    Python  Mean Absolute Error: 6.334487104423225
    MQL5:   Mean Absolute Error: 6.3344865429226038
    
    Testing ONNX double: ElasticNetCV (elastic_net_cv_double.onnx)
    Python  Mean Absolute Error: 6.334487104423225
    MQL5:   Mean Absolute Error: 6.3344871044232205

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.4.3. ONNX-представление моделей elastic_net_cv_float.onnx и elastic_net_cv_double.onnx

    Рис.19. ONNX-представление модели elastic_net_cv_float.onnx в Netron

    Рис.19. ONNX-представление модели elastic_net_cv_float.onnx в Netron


    Рис.20. ONNX-представление модели elastic_net_cv_double.onnx в Netron

    Рис.20. ONNX-представление модели elastic_net_cv_double.onnx в Netron  



    2.1.5. sklearn.linear_model.HuberRegressor

    HuberRegressor - это метод машинного обучения, используемый для задачи регрессии, который является модификацией метода наименьших квадратов (Ordinary Least Squares, OLS) и спроектирован для быть устойчивым к выбросам в данных.

    В отличие от OLS, который минимизирует квадраты ошибок, HuberRegressor минимизирует комбинацию квадратов ошибок и абсолютных значений ошибок. Это позволяет методу более устойчиво работать в случае наличия выбросов в данных.

    Принцип работы HuberRegressor:

    1. Входные данные: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Функция потерь Huber: HuberRegressor использует функцию потерь Huber, которая объединяет квадратичную функцию потерь для малых ошибок и линейную функцию потерь для больших ошибок. Это делает метод более устойчивым к выбросам.
    3. Обучение модели: Модель обучается на данных с использованием функции потерь Huber. Во время обучения она настраивает веса (коэффициенты) для каждого признака и смещение.
    4. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества HuberRegressor:

    Ограничения HuberRegressor:

    HuberRegressor полезен в задачах регрессии, где данные могут содержать выбросы и требуется модель, которая устойчива к таким аномалиям.


    2.1.5.1. Код создания модели HuberRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.HuberRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # HuberRegressor.py
    # The code demonstrates the process of training HuberRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com

    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import HuberRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "HuberRegressor"
    onnx_model_filename = data_path + "huber_regressor"

    # create a Huber Regressor model
    huber_regressor_model = HuberRegressor()

    # fit the model to the data
    huber_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = huber_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(huber_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(huber_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  HuberRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962363935647066
    Python  Mean Absolute Error: 6.341633708569641
    Python  Mean Squared Error: 49.80289464784336
    Python  
    Python  HuberRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\huber_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962363944236795
    Python  Mean Absolute Error: 6.341633300252807
    Python  Mean Squared Error: 49.80288328126165
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  4
    Python  float ONNX model precision:  6
    Python  
    Python  HuberRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\huber_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962363935647066
    Python  Mean Absolute Error: 6.341633708569641
    Python  Mean Squared Error: 49.80289464784336
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.21. Результат работы скрипта HuberRegressor.py (float ONNX)

    Рис.21. Результат работы скрипта HuberRegressor.py (float ONNX)


    2.1.5.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели huber_regressor_float.onnx и huber_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                               HuberRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "HuberRegressor"
    #define   ONNXFilenameFloat  "huber_regressor_float.onnx"
    #define   ONNXFilenameDouble "huber_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    HuberRegressor (EURUSD,H1)      Testing ONNX float: HuberRegressor (huber_regressor_float.onnx)
    HuberRegressor (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.9962363944236795
    HuberRegressor (EURUSD,H1)      MQL5:   Mean Absolute Error: 6.3416333002528074
    HuberRegressor (EURUSD,H1)      MQL5:   Mean Squared Error: 49.8028832812616571
    HuberRegressor (EURUSD,H1)      
    HuberRegressor (EURUSD,H1)      Testing ONNX double: HuberRegressor (huber_regressor_double.onnx)
    HuberRegressor (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.9962363935647066
    HuberRegressor (EURUSD,H1)      MQL5:   Mean Absolute Error: 6.3416337085696410
    HuberRegressor (EURUSD,H1)      MQL5:   Mean Squared Error: 49.8028946478433525
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: HuberRegressor (huber_regressor_float.onnx)
    Python  Mean Absolute Error: 6.341633708569641
    MQL5:   Mean Absolute Error: 6.3416333002528074
          
    Testing ONNX double: HuberRegressor (huber_regressor_double.onnx)
    Python  Mean Absolute Error: 6.341633708569641
    MQL5:   Mean Absolute Error: 6.3416337085696410

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.5.3. ONNX-представление моделей huber_regressor_float.onnx и huber_regressor_double.onnx


    Рис.22. ONNX-представление модели huber_regressor_float.onnx в Netron

    Рис.22. ONNX-представление модели huber_regressor_float.onnx в Netron


    Рис.23. ONNX-представление модели huber_regressor_double.onnx в Netron

    Рис.23. ONNX-представление модели huber_regressor_double.onnx в Netron  


    2.1.6. sklearn.linear_model.Lars

    LARS (Least Angle Regression) - это метод машинного обучения, используемый для задачи регрессии. Он представляет собой алгоритм, который строит модель линейной регрессии, выбирая активные признаки (переменные) по мере обучения.

    LARS пытается найти наименьшее количество признаков, которые дают наилучшее приближение к целевой переменной.

    Принцип работы LARS:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: Начинается с нулевой модели, то есть без активных признаков. Все коэффициенты устанавливаются в ноль.
    3. Выбор признака: На каждом шаге LARS выбирает признак, который наиболее коррелирует с остатками модели. Затем этот признак добавляется в модель, и соответствующий коэффициент настраивается с использованием метода наименьших квадратов.
    4. Регрессия вдоль активных признаков: После добавления признака в модель, LARS обновляет коэффициенты всех активных признаков, чтобы учесть изменения в новой модели.
    5. Шаги повторяются: Этот процесс повторяется до тех пор, пока не будут выбраны все признаки или достигнут заданный критерий останова.
    6. Получение прогноза: После обучения модели, она может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LARS:

    Ограничения LARS:

    LARS полезен в задачах регрессии, где важно выбрать самые информативные признаки и построить линейную модель с минимальным количеством признаков.


    2.1.6.1. Код создания модели Lars и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.Lars, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # Lars.py
    # The code demonstrates the process of training Lars model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import Lars
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "Lars"
    onnx_model_filename = data_path + "lars"

    # create a Lars Regressor model
    lars_regressor_model = Lars()

    # fit the model to the data
    lars_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = lars_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lars_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType
    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lars_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  Lars Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336425
    Python  Mean Squared Error: 49.778140171281784
    Python  
    Python  Lars ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lars_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641628886
    Python  Mean Absolute Error: 6.3477377671679385
    Python  Mean Squared Error: 49.77814147404787
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  Lars ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lars_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336425
    Python  Mean Squared Error: 49.778140171281784
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  15
    Python  double ONNX model precision:  15
    

    Рис.24. Результат работы скрипта Lars.py (float ONNX)

    Рис.24. Результат работы скрипта Lars.py (float ONNX)


    2.1.6.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lars_cv_float.onnx и lars_cv_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                         Lars.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "Lars"
    #define   ONNXFilenameFloat  "lars_float.onnx"
    #define   ONNXFilenameDouble "lars_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    Lars (EURUSD,H1)        Testing ONNX float: Lars (lars_float.onnx)
    Lars (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9962382641628886
    Lars (EURUSD,H1)        MQL5:   Mean Absolute Error: 6.3477377671679385
    Lars (EURUSD,H1)        MQL5:   Mean Squared Error: 49.7781414740478638
    Lars (EURUSD,H1)        
    Lars (EURUSD,H1)        Testing ONNX double: Lars (lars_double.onnx)
    Lars (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    Lars (EURUSD,H1)        MQL5:   Mean Absolute Error: 6.3477379263364302
    Lars (EURUSD,H1)        MQL5:   Mean Squared Error: 49.7781401712817768
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: Lars (lars_float.onnx)
    Python  Mean Absolute Error: 6.347737926336425
    MQL5:   Mean Absolute Error: 6.3477377671679385
    
    Testing ONNX double: Lars (lars_double.onnx)
    Python  Mean Absolute Error: 6.347737926336425
    MQL5:   Mean Absolute Error: 6.3477379263364302

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.6.3. ONNX-представление моделей lars_float.onnx и lars_double.onnx


    Рис.25. ONNX-представление модели lars_float.onnx в Netron

    Рис.25. ONNX-представление модели lars_float.onnx в Netron


    Рис.26. ONNX-представление модели lars_double.onnx в Netron

    Рис.26. ONNX-представление модели lars_double.onnx в Netron



    2.1.7. sklearn.linear_model.LarsCV

    LarsCV - это вариант метода LARS (Least Angle Regression), который автоматически выбирает оптимальное количество признаков для включения в модель, используя кросс-валидацию.

    Этот метод позволяет найти баланс между моделью, которая хорошо обобщает данные, и моделью, которая использует минимальное количество признаков.

    Принцип работы LarsCV:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: Начинается с нулевой модели, то есть без активных признаков. Все коэффициенты устанавливаются в ноль.
    3. Кросс-валидация: LarsCV выполняет кросс-валидацию для разного количества включенных признаков. Это позволяет оценить производительность модели при разных наборах признаков.
    4. Выбор оптимального числа признаков: LarsCV выбирает количество признаков, при котором достигается наилучшая производительность модели, как определено с использованием кросс-валидации.
    5. Обучение модели: Модель обучается с использованием выбранного количества признаков и соответствующих коэффициентов.
    6. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LarsCV:

    Ограничения LarsCV:

    LarsCV полезен в задачах регрессии, где важно автоматически выбрать наилучший набор признаков, используемых в модели, и при этом сохранить интерпретируемость модели.


    2.1.7.1. Код создания модели LarsCV и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LarsCV, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LarsCV.py
    # The code demonstrates the process of training LarsCV model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LarsCV
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LarsCV"
    onnx_model_filename = data_path + "lars_cv"

    # create a LarsCV Regressor model
    larscv_regressor_model = LarsCV()

    # fit the model to the data
    larscv_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = larscv_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(larscv_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(larscv_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LarsCV Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642612767
    Python  Mean Absolute Error: 6.3477379221400145
    Python  Mean Squared Error: 49.77814017210321
    Python  
    Python  LarsCV ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lars_cv_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382640824089
    Python  Mean Absolute Error: 6.347737845846069
    Python  Mean Squared Error: 49.778142539016564
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  LarsCV ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lars_cv_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642612767
    Python  Mean Absolute Error: 6.3477379221400145
    Python  Mean Squared Error: 49.77814017210321
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  16
    

    Рис.27. Результат работы скрипта LarsCV.py (float ONNX)

    Рис.27. Результат работы скрипта LarsCV.py (float ONNX)


    2.1.7.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lars_cv_float.onnx и lars_cv_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                       LarsCV.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LarsCV"
    #define   ONNXFilenameFloat  "lars_cv_float.onnx"
    #define   ONNXFilenameDouble "lars_cv_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LarsCV (EURUSD,H1)      Testing ONNX float: LarsCV (lars_cv_float.onnx)
    LarsCV (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.9962382640824089
    LarsCV (EURUSD,H1)      MQL5:   Mean Absolute Error: 6.3477378458460691
    LarsCV (EURUSD,H1)      MQL5:   Mean Squared Error: 49.7781425390165566
    LarsCV (EURUSD,H1)      
    LarsCV (EURUSD,H1)      Testing ONNX double: LarsCV (lars_cv_double.onnx)
    LarsCV (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.9962382642612767
    LarsCV (EURUSD,H1)      MQL5:   Mean Absolute Error: 6.3477379221400145
    LarsCV (EURUSD,H1)      MQL5:   Mean Squared Error: 49.7781401721031642
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LarsCV (lars_cv_float.onnx)
    Python  Mean Absolute Error: 6.3477379221400145
    MQL5:   Mean Absolute Error: 6.3477378458460691
    
    Testing ONNX double: LarsCV (lars_cv_double.onnx)
    Python  Mean Absolute Error: 6.3477379221400145
    MQL5:   Mean Absolute Error: 6.3477379221400145

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.7.3. ONNX-представление моделей lars_cv_float.onnx и lars_cv_double.onnx

    Рис.28. ONNX-представление модели lars_cv_float.onnx в Netron

    Рис.28. ONNX-представление модели lars_cv_float.onnx в Netron


    Рис.29. ONNX-представление модели lars_cv_double.onnx в Netron

    Рис.29. ONNX-представление модели lars_cv_double.onnx в Netron



    2.1.8. sklearn.linear_model.Lasso

    Lasso (Least Absolute Shrinkage and Selection Operator) является методом регрессии, который применяется для выбора наиболее важных признаков и уменьшения размерности модели.

    Он достигает этого путем добавления штрафа на сумму абсолютных значений коэффициентов (L1-регуляризация) в задаче оптимизации линейной регрессии.

    Принцип работы Lasso:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Целевая функция: Целевая функция в Lasso включает в себя сумму квадратов ошибок регрессии, а также штраф на сумму абсолютных значений коэффициентов при признаках.
    3. Оптимизация: Модель Lasso обучается путем минимизации целевой функции, что приводит к тому, что некоторые коэффициенты становятся равными нулю, что эквивалентно исключению соответствующих признаков из модели.
    4. Выбор оптимального значения штрафа: В Lasso существует гиперпараметр, который определяет силу регуляризации. Выбор оптимального значения этого гиперпараметра может потребовать кросс-валидации.
    5. Получение прогноза: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества Lasso:

    Ограничения Lasso:

    Lasso полезен в задачах регрессии, где важно выбрать наиболее важные признаки и уменьшить размерность модели, при этом сохраняя интерпретируемость.


    2.1.8.1. Код создания модели Lasso и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.Lasso, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # Lasso.py
    # The code demonstrates the process of training Lasso model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import Lasso
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "Lasso"
    onnx_model_filename = data_path + "lasso"

    # create a Lasso model
    lasso_model = Lasso()

    # fit the model to the data
    lasso_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = lasso_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lasso_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lasso_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  Lasso Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962381735682287
    Python  Mean Absolute Error: 6.346393791922984
    Python  Mean Squared Error: 49.77934029129379
    Python  
    Python  Lasso ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962381720269486
    Python  Mean Absolute Error: 6.346395056911361
    Python  Mean Squared Error: 49.77936068668213
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  5
    Python  
    Python  Lasso ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962381735682287
    Python  Mean Absolute Error: 6.346393791922984
    Python  Mean Squared Error: 49.77934029129379
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    


    Рис.30. Результат работы скрипта Lasso.py (float ONNX)

    Рис.30. Результат работы скрипта Lasso.py (float ONNX)


    2.1.8.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lasso_float.onnx и lasso_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                        Lasso.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "Lasso"
    #define   ONNXFilenameFloat  "lasso_float.onnx"
    #define   ONNXFilenameDouble "lasso_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    Lasso (EURUSD,H1)       Testing ONNX float: Lasso (lasso_float.onnx)
    Lasso (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962381720269486
    Lasso (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3463950569113612
    Lasso (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7793606866821037
    Lasso (EURUSD,H1)       
    Lasso (EURUSD,H1)       Testing ONNX double: Lasso (lasso_double.onnx)
    Lasso (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962381735682287
    Lasso (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3463937919229840
    Lasso (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7793402912937850
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: Lasso (lasso_float.onnx)
    Python  Mean Absolute Error: 6.346393791922984
    MQL5:   Mean Absolute Error: 6.3463950569113612
    
    Testing ONNX double: Lasso (lasso_double.onnx)
    Python  Mean Absolute Error: 6.346393791922984
    MQL5:   Mean Absolute Error: 6.3463937919229840

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 15 знаков после запятой.


    2.1.8.3. ONNX-представление моделей lasso_float.onnx и lasso_double.onnx


    Рис.31. ONNX-представление модели lasso_float.onnx в Netron

    Рис.31. ONNX-представление модели lasso_float.onnx в Netron



    Рис.32. ONNX-представление модели lasso_double.onnx в Netron

    Рис.32. ONNX-представление модели lasso_double.onnx в Netron



    2.1.9. sklearn.linear_model.LassoCV

    LassoCV - это вариант метода Lasso (Least Absolute Shrinkage and Selection Operator), который автоматически выбирает оптимальное значение гиперпараметра регуляризации (alpha) с использованием кросс-валидации.

    Этот метод позволяет найти баланс между уменьшением размерности модели (выбором важных признаков) и предотвращением переобучения, что делает его полезным для задач регрессии.

    Принцип работы LassoCV:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: LassoCV инициализирует несколько различных значений гиперпараметра регуляризации (alpha), которые охватывают диапазон значений от низкого до высокого.
    3. Кросс-валидация: Для каждого значения alpha, LassoCV выполняет кросс-валидацию, чтобы оценить производительность модели. Обычно используется метрика, такая как среднеквадратичная ошибка (MSE) или коэффициент детерминации (R^2).
    4. Выбор оптимального alpha: LassoCV выбирает значение alpha, при котором модель достигает наилучшей производительности, как определено с использованием кросс-валидации.
    5. Обучение модели: Модель Lasso обучается с использованием выбранного значения alpha, исключая менее важные признаки и применяя L1-регуляризацию.
    6. Получение прогноза: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LassoCV:

    Ограничения LassoCV:

    LassoCV полезен в задачах регрессии, где важно выбрать наиболее важные признаки и уменьшить размерность модели, при этом сохраняя интерпретируемость и предотвращая переобучение.


    2.1.9.1. Код создания модели LassoCV и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LassoCV, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LassoCV.py
    # The code demonstrates the process of training LassoCV model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LassoCV
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LassoCV"
    onnx_model_filename = data_path + "lasso_cv"

    # create a LassoCV Regressor model
    lassocv_regressor_model = LassoCV()

    # fit the model to the data
    lassocv_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = lassocv_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lassocv_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lassocv_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LassoCV Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962241428413416
    Python  Mean Absolute Error: 6.33567334453819
    Python  Mean Squared Error: 49.96500551028169
    Python  
    Python  LassoCV ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_cv_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.996224142876629
    Python  Mean Absolute Error: 6.335673221332177
    Python  Mean Squared Error: 49.96500504333324
    Python  R^2 matching decimal places:  10
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  6
    Python  float ONNX model precision:  6
    Python  
    Python  LassoCV ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_cv_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962241428413416
    Python  Mean Absolute Error: 6.33567334453819
    Python  Mean Squared Error: 49.96500551028169
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  14
    

    Рис.33. Результат работы скрипта LassoCV.py (float ONNX)

    Рис.33. Результат работы скрипта LassoCV.py (float ONNX)



    2.1.9.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lasso_cv_float.onnx и lasso_cv_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                      LassoCV.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LassoCV"
    #define   ONNXFilenameFloat  "lasso_cv_float.onnx"
    #define   ONNXFilenameDouble "lasso_cv_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    2023.10.26 22:14:00.736 LassoCV (EURUSD,H1)     Testing ONNX float: LassoCV (lasso_cv_float.onnx)
    2023.10.26 22:14:00.739 LassoCV (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962241428766290
    2023.10.26 22:14:00.739 LassoCV (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3356732213321800
    2023.10.26 22:14:00.739 LassoCV (EURUSD,H1)     MQL5:   Mean Squared Error: 49.9650050433332211
    2023.10.26 22:14:00.748 LassoCV (EURUSD,H1)     
    2023.10.26 22:14:00.748 LassoCV (EURUSD,H1)     Testing ONNX double: LassoCV (lasso_cv_double.onnx)
    2023.10.26 22:14:00.753 LassoCV (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962241428413416
    2023.10.26 22:14:00.753 LassoCV (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3356733445381899
    2023.10.26 22:14:00.753 LassoCV (EURUSD,H1)     MQL5:   Mean Squared Error: 49.9650055102816992
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LassoCV (lasso_cv_float.onnx)
    Python  Mean Absolute Error: 6.33567334453819
    MQL5:   Mean Absolute Error: 6.3356732213321800
            
    Testing ONNX double: LassoCV (lasso_cv_double.onnx)
    Python  Mean Absolute Error: 6.33567334453819
    MQL5:   Mean Absolute Error: 6.3356733445381899

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.9.3. ONNX-представление моделей lasso_cv_float.onnx и lasso_cv_double.onnx

    Рис.34. ONNX-представление модели lasso_cv_float.onnx в Netron

    Рис.34. ONNX-представление модели lasso_cv_float.onnx в Netron


    Рис.35. ONNX-представление модели lasso_cv_double.onnx в Netron

    Рис.35. ONNX-представление модели lasso_cv_double.onnx в Netron



    2.1.10. sklearn.linear_model.LassoLars

    LassoLars - это комбинация двух методов: Lasso (Least Absolute Shrinkage and Selection Operator) и LARS (Least Angle Regression).

    Этот метод используется для задач регрессии и объединяет преимущества обоих алгоритмов, что позволяет одновременно выбирать важные признаки и уменьшать размерность модели.

    Принцип работы LassoLars:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: LassoLars начинает с нулевой модели, то есть без активных признаков. Все коэффициенты устанавливаются в ноль.
    3. Пошаговый выбор признаков: Аналогично методу LARS, LassoLars на каждом шаге выбирает признак, который наиболее коррелирует с остатками модели, и добавляет его в модель. Затем коэффициент этого признака настраивается с использованием метода наименьших квадратов.
    4. Применение L1-регуляризации: Одновременно с пошаговым выбором признаков LassoLars применяет L1-регуляризацию, добавляя штраф на сумму абсолютных значений коэффициентов. Это позволяет моделировать сложные зависимости и выбирать наиболее важные признаки.
    5. Получение прогноза: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LassoLars:

    Ограничения LassoLars:

    LassoLars полезен в задачах регрессии, где важно выбрать наиболее важные признаки, уменьшить размерность модели и сохранить интерпретируемость.


    2.1.10.1. Код создания модели LassoLars и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LassoLars, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LassoLars.py
    # The code demonstrates the process of training LassoLars model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LassoLars
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LassoLars"
    onnx_model_filename = data_path + "lasso_lars"

    # create a LassoLars Regressor model
    lassolars_regressor_model = LassoLars(alpha=0.1)

    # fit the model to the data
    lassolars_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = lassolars_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lassolars_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lassolars_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LassoLars Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382633544077
    Python  Mean Absolute Error: 6.3476035128950805
    Python  Mean Squared Error: 49.778152172481896
    Python  
    Python  LassoLars ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382635045889
    Python  Mean Absolute Error: 6.3476034814795375
    Python  Mean Squared Error: 49.77815018516975
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  LassoLars ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382633544077
    Python  Mean Absolute Error: 6.3476035128950805
    Python  Mean Squared Error: 49.778152172481896
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  15
    Python  double ONNX model precision:  16
    

    Рис.36. Результат работы скрипта LassoLars.py (float)

    Рис.36. Результат работы скрипта LassoLars.py (float)


    2.1.10.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lasso_lars_float.onnx и lasso_lars_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                    LassoLars.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LassoLars"
    #define   ONNXFilenameFloat  "lasso_lars_float.onnx"
    #define   ONNXFilenameDouble "lasso_lars_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LassoLars (EURUSD,H1)   Testing ONNX float: LassoLars (lasso_lars_float.onnx)
    LassoLars (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962382635045889
    LassoLars (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3476034814795375
    LassoLars (EURUSD,H1)   MQL5:   Mean Squared Error: 49.7781501851697357
    LassoLars (EURUSD,H1)   
    LassoLars (EURUSD,H1)   Testing ONNX double: LassoLars (lasso_lars_double.onnx)
    LassoLars (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962382633544077
    LassoLars (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3476035128950858
    LassoLars (EURUSD,H1)   MQL5:   Mean Squared Error: 49.7781521724819029
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LassoLars (lasso_lars_float.onnx)
    Python  Mean Absolute Error: 6.3476035128950805
    MQL5:   Mean Absolute Error: 6.3476034814795375
    
    Testing ONNX double: LassoLars (lasso_lars_double.onnx)
    Python  Mean Absolute Error: 6.3476035128950805
    MQL5:   Mean Absolute Error: 6.3476035128950858

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.10.3. ONNX-представление моделей lasso_lars_float.onnx и lasso_lars_double.onnx


    Рис.37. ONNX-представление модели lasso_lars_float.onnx в Netron

    Рис.37. ONNX-представление модели lasso_lars_float.onnx в Netron



    Рис.38. ONNX-представление модели lasso_lars_double.onnx в Netron

    Рис.38. ONNX-представление модели lasso_lars_double.onnx в Netron


    2.1.11. sklearn.linear_model.LassoLarsCV

    LassoLarsCV представляет собой комбинацию методов Lasso (Least Absolute Shrinkage and Selection Operator) и LARS (Least Angle Regression) с автоматическим выбором оптимального значения гиперпараметра регуляризации (alpha) с использованием кросс-валидации.

    Этот метод объединяет преимущества обоих алгоритмов и позволяет определить оптимальное значение alpha для модели с учетом выбора признаков и регуляризации.

    Принцип работы LassoLarsCV:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: LassoLarsCV начинает с нулевой модели, где все коэффициенты равны нулю.
    3. Определение диапазона alpha: Определяется диапазон значений гиперпараметра alpha, который будет рассмотрен в процессе выбора. Обычно используется логарифмическая шкала значений alpha.
    4. Кросс-валидация: Для каждого значения alpha из выбранного диапазона LassoLarsCV выполняет кросс-валидацию для оценки производительности модели с этим значением alpha. Обычно используется метрика, такая как среднеквадратичная ошибка (MSE) или коэффициент детерминации (R^2).
    5. Выбор оптимального alpha: LassoLarsCV выбирает значение alpha, при котором модель достигает наилучшей производительности на основе результатов кросс-валидации.
    6. Обучение модели: Модель LassoLars обучается с использованием выбранного значения alpha, исключая менее важные признаки и применяя L1-регуляризацию.
    7. Получение прогноза: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LassoLarsCV:

    Ограничения LassoLarsCV:

    LassoLarsCV полезен в задачах регрессии, где важно выбрать наиболее важные признаки, уменьшить размерность модели, предотвратить переобучение и автоматически настроить гиперпараметры модели.


    2.1.11.1. Код создания модели LassoLarsCV и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LassoLarsCV, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LassoLarsCV.py
    # The code demonstrates the process of training LassoLars model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LassoLarsCV
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LassoLarsCV"
    onnx_model_filename = data_path + "lasso_lars_cv"

    # create a LassoLarsCV Regressor model
    lassolars_cv_regressor_model = LassoLarsCV(cv=5)

    # fit the model to the data
    lassolars_cv_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = lassolars_cv_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lassolars_cv_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lassolars_cv_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LassoLarsCV Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642612767
    Python  Mean Absolute Error: 6.3477379221400145
    Python  Mean Squared Error: 49.77814017210321
    Python  
    Python  LassoLarsCV ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_cv_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382640824089
    Python  Mean Absolute Error: 6.347737845846069
    Python  Mean Squared Error: 49.778142539016564
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  LassoLarsCV ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_cv_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642612767
    Python  Mean Absolute Error: 6.3477379221400145
    Python  Mean Squared Error: 49.77814017210321
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  16
    


    Рис.39. Результат работы скрипта LassoLarsCV.py (float ONNX)

    Рис.39. Результат работы скрипта LassoLarsCV.py (float ONNX)


    2.1.11.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lasso_lars_cv_float.onnx и lasso_lars_cv_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                  LassoLarsCV.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LassoLarsCV"
    #define   ONNXFilenameFloat  "lasso_lars_cv_float.onnx"
    #define   ONNXFilenameDouble "lasso_lars_cv_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LassoLarsCV (EURUSD,H1) Testing ONNX float: LassoLarsCV (lasso_lars_cv_float.onnx)
    LassoLarsCV (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9962382640824089
    LassoLarsCV (EURUSD,H1) MQL5:   Mean Absolute Error: 6.3477378458460691
    LassoLarsCV (EURUSD,H1) MQL5:   Mean Squared Error: 49.7781425390165566
    LassoLarsCV (EURUSD,H1) 
    LassoLarsCV (EURUSD,H1) Testing ONNX double: LassoLarsCV (lasso_lars_cv_double.onnx)
    LassoLarsCV (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9962382642612767
    LassoLarsCV (EURUSD,H1) MQL5:   Mean Absolute Error: 6.3477379221400145
    LassoLarsCV (EURUSD,H1) MQL5:   Mean Squared Error: 49.7781401721031642
    

    Сравнение с оригиналом:

    Testing ONNX float: LassoLarsCV (lasso_lars_cv_float.onnx)
    Python  Mean Absolute Error: 6.3477379221400145
    MQL5:   Mean Absolute Error: 6.3477378458460691
            
    Testing ONNX double: LassoLarsCV (lasso_lars_cv_double.onnx)
    Python  Mean Absolute Error: 6.3477379221400145
    MQL5:   Mean Absolute Error: 6.3477379221400145
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.11.3. ONNX-представление моделей lasso_lars_cv_float.onnx и lasso_lars_cv_double.onnx


    Рис.40. ONNX-представление модели lasso_lars_cv_float.onnx в Netron

    Рис.40. ONNX-представление модели lasso_lars_cv_float.onnx в Netron


    Рис.41. ONNX-представление модели lasso_lars_cv_double.onnx в Netron

    Рис.41. ONNX-представление модели lasso_lars_cv_double.onnx в Netron



    2.1.12. sklearn.linear_model.LassoLarsIC

    LassoLarsIC - это метод регрессии, который объединяет Lasso (Least Absolute Shrinkage and Selection Operator) и информационный критерий (IC) для автоматического выбора оптимального набора признаков.

    Он использует информационные критерии, такие как AIC (Akaike Information Criterion) и BIC (Bayesian Information Criterion), чтобы определить, какие признаки следует включить в модель, и применяет L1-регуляризацию для оценки коэффициентов модели.

    Принцип работы LassoLarsIC:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Инициализация: LassoLarsIC начинает с нулевой модели, то есть без активных признаков. Все коэффициенты устанавливаются в ноль.
    3. Выбор признаков с использованием информационного критерия: Метод оценивает информационный критерий (например, AIC или BIC) для разных наборов признаков, начиная с пустой модели и постепенно включая признаки в модель. Информационный критерий позволяет оценить качество модели, учитывая компромисс между подгонкой данных и сложностью модели.
    4. Выбор оптимального набора признаков: LassoLarsIC выбирает набор признаков, для которого информационный критерий достигает наилучшего значения. Этот набор признаков будет включен в модель.
    5. Применение L1-регуляризации: Для выбранных признаков применяется L1-регуляризация, что помогает оценить коэффициенты модели.
    6. Получение прогноза: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества LassoLarsIC:

    Ограничения LassoLarsIC:

    LassoLarsIC полезен в задачах регрессии, где важно автоматически выбирать наилучший набор признаков и уменьшать размерность модели с учетом информационного критерия.


    2.1.12.1. Код создания модели LassoLarsIC и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LassoLarsIC, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LassoLarsIC.py
    # The code demonstrates the process of training LassoLarsIC model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LassoLarsIC
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name="LassoLarsIC"
    onnx_model_filename = data_path + "lasso_lars_ic"

    # create a LassoLarsIC Regressor model
    lasso_lars_ic_regressor_model = LassoLarsIC(criterion='aic')

    # fit the model to the data
    lasso_lars_ic_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = lasso_lars_ic_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(lasso_lars_ic_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(lasso_lars_ic_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LassoLarsIC Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336425
    Python  Mean Squared Error: 49.778140171281784
    Python  
    Python  LassoLarsIC ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_ic_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641628886
    Python  Mean Absolute Error: 6.3477377671679385
    Python  Mean Squared Error: 49.77814147404787
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  LassoLarsIC ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\lasso_lars_ic_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336425
    Python  Mean Squared Error: 49.778140171281784
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  15
    Python  double ONNX model precision:  15
    

    Рис.42. Результат работы скрипта LassoLarsIC.py (float ONNX)

    Рис.42. Результат работы скрипта LassoLarsIC.py (float ONNX)


    2.1.12.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели lasso_lars_ic_float.onnx и lasso_lars_ic_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                  LassoLarsIC.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LassoLarsIC"
    #define   ONNXFilenameFloat  "lasso_lars_ic_float.onnx"
    #define   ONNXFilenameDouble "lasso_lars_ic_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LassoLarsIC (EURUSD,H1) Testing ONNX float: LassoLarsIC (lasso_lars_ic_float.onnx)
    LassoLarsIC (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9962382641628886
    LassoLarsIC (EURUSD,H1) MQL5:   Mean Absolute Error: 6.3477377671679385
    LassoLarsIC (EURUSD,H1) MQL5:   Mean Squared Error: 49.7781414740478638
    LassoLarsIC (EURUSD,H1) 
    LassoLarsIC (EURUSD,H1) Testing ONNX double: LassoLarsIC (lasso_lars_ic_double.onnx)
    LassoLarsIC (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    LassoLarsIC (EURUSD,H1) MQL5:   Mean Absolute Error: 6.3477379263364302
    LassoLarsIC (EURUSD,H1) MQL5:   Mean Squared Error: 49.7781401712817768
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LassoLarsIC (lasso_lars_ic_float.onnx)
    Python  Mean Absolute Error: 6.347737926336425
    MQL5:   Mean Absolute Error: 6.3477377671679385
     
    Testing ONNX double: LassoLarsIC (lasso_lars_ic_double.onnx)
    Python  Mean Absolute Error: 6.347737926336425
    MQL5:   Mean Absolute Error: 6.3477379263364302
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.12.3. ONNX-представление моделей lasso_lars_ic_float.onnx и lasso_lars_ic_double.onnx


    Рис.43. ONNX-представление модели lasso_lars_ic_float.onnx в Netron

    Рис.43. ONNX-представление модели lasso_lars_ic_float.onnx в Netron


    Рис.44. ONNX-представление модели lasso_lars_ic_double.onnx в Netron

    Рис.44. ONNX-представление модели lasso_lars_ic_double.onnx в Netron




    2.1.13. sklearn.linear_model.LinearRegression

    LinearRegression - это один из наиболее простых и широко используемых методов в машинном обучении для задачи регрессии.

    Он используется для построения линейных моделей, которые предсказывают числовые значения (непрерывные) целевой переменной на основе линейной комбинации входных признаков.

    Принцип работы LinearRegression:

    1. Линейная модель: Модель LinearRegression предполагает, что существует линейная зависимость между независимыми переменными (признаками) и целевой переменной. Эта зависимость может быть выражена уравнением линейной регрессии: y = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ, где y - целевая переменная, β₀ - коэффициент сдвига (пересечения), β₁, β₂, ... βₚ - коэффициенты признаков, x₁, x₂, ... xₚ - значения признаков.
    2. Оценка параметров: Задача LinearRegression состоит в оценке коэффициентов β₀, β₁, β₂, ... βₚ, которые наилучшим образом соответствуют данным. Обычно это делается с использованием метода наименьших квадратов (OLS), который минимизирует сумму квадратов разницы между фактическими и предсказанными значениями.
    3. Оценка качества модели: Для оценки качества модели LinearRegression используются различные метрики, такие как средняя квадратическая ошибка (MSE), коэффициент детерминации (R²) и другие.

    Преимущества LinearRegression:

    Ограничения LinearRegression:

    LinearRegression - это простой и широко используемый метод регрессии, который строит линейную модель для предсказания числовых значений целевой переменной на основе линейной комбинации входных признаков. Он хорошо подходит для задач с линейной зависимостью и когда важна интерпретируемость модели.


    2.1.13.1. Код создания модели LinearRegression и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LinearRegression, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LinearRegression.py
    # The code demonstrates the process of training LinearRegression model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import LinearRegression
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LinearRegression"
    onnx_model_filename = data_path + "linear_regression"

    # create a Linear Regression model
    linear_model = LinearRegression()

    # fit the model to the data
    linear_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = linear_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(linear_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(linear_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LinearRegression Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336427
    Python  Mean Squared Error: 49.77814017128179
    Python  
    Python  LinearRegression ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\linear_regression_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641628886
    Python  Mean Absolute Error: 6.3477377671679385
    Python  Mean Squared Error: 49.77814147404787
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  LinearRegression ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\linear_regression_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336427
    Python  Mean Squared Error: 49.77814017128179
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.45. Результат работы скрипта LinearRegression.py (float ONNX)

    Рис.45. Результат работы скрипта LinearRegression.py (float ONNX)


    2.1.13.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели linear_regression_float.onnx и linear_regression_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                             LinearRegression.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LinearRegression"
    #define   ONNXFilenameFloat  "linear_regression_float.onnx"
    #define   ONNXFilenameDouble "linear_regression_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LinearRegression (EURUSD,H1)    Testing ONNX float: LinearRegression (linear_regression_float.onnx)
    LinearRegression (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9962382641628886
    LinearRegression (EURUSD,H1)    MQL5:   Mean Absolute Error: 6.3477377671679385
    LinearRegression (EURUSD,H1)    MQL5:   Mean Squared Error: 49.7781414740478638
    LinearRegression (EURUSD,H1)    
    LinearRegression (EURUSD,H1)    Testing ONNX double: LinearRegression (linear_regression_double.onnx)
    LinearRegression (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    LinearRegression (EURUSD,H1)    MQL5:   Mean Absolute Error: 6.3477379263364266
    LinearRegression (EURUSD,H1)    MQL5:   Mean Squared Error: 49.7781401712817768
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LinearRegression (linear_regression_float.onnx)
    Python  Mean Absolute Error: 6.347737926336427
    MQL5:   Mean Absolute Error: 6.3477377671679385
    
    Testing ONNX double: LinearRegression (linear_regression_double.onnx)
    Python  Mean Absolute Error: 6.347737926336427
    MQL5:   Mean Absolute Error: 6.3477379263364266
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.13.3. ONNX-представление моделей linear_regression_float.onnx и linear_regression_double.onnx


    Рис.46. ONNX-представление модели linear_regression_float.onnx в Netron

    Рис.46. ONNX-представление модели linear_regression_float.onnx в Netron



    Рис.47. ONNX-представление модели linear_regression_double.onnx в Netron

    Рис.47. ONNX-представление модели linear_regression_double.onnx в Netron



    Примечание к методам Ridge и RidgeCV

    Ridge и RidgeCV - это два связанных метода в машинном обучении, используемые для регрессии с регуляризацией методом Ридж. Они имеют общий функционал, но различаются в способе использования и настройки параметров.

    Принцип работы Ridge (Ridge Regression):

    • Ridge - это метод регрессии, который включает в себя L2-регуляризацию. Это означает, что он добавляет сумму квадратов коэффициентов (L2-норму) в функцию потерь, которую минимизирует модель. Этот дополнительный член регуляризации помогает уменьшить величины коэффициентов модели и тем самым предотвращает переобучение.
    • Использование параметра alpha: В методе Ridge параметр alpha (также называемый силой регуляризации) задается заранее и не изменяется автоматически. Пользователь должен выбрать подходящее значение alpha на основе своих знаний о данных и экспериментов.

    Принцип работы RidgeCV (Ridge Cross-Validation):

    • RidgeCV - это расширение метода Ridge, которое включает в себя автоматический подбор оптимального значения параметра alpha с использованием кросс-валидации. Вместо того чтобы пользователь задавал alpha вручную, RidgeCV выполняет перебор различных значений alpha и выбирает значение, которое дает наилучшую производительность на кросс-валидации.
    • Преимущество автоматической настройки: Основное преимущество RidgeCV заключается в том, что он автоматически находит оптимальное значение параметра alpha без необходимости вручную настраивать его. Это делает процесс настройки более удобным и предотвращает возможные ошибки выбора alpha.

    Основное отличие между Ridge и RidgeCV заключается в том, что Ridge требует, чтобы пользователь явно задал значение параметра alpha, в то время как RidgeCV автоматически находит оптимальное значение alpha с использованием кросс-валидации. RidgeCV обычно является более предпочтительным выбором, если у вас есть большое количество данных и вы хотите избежать ручной настройки параметров.


    2.1.14. sklearn.linear_model.Ridge

    Ridge - это метод регрессии, используемый в машинном обучении для решения задач регрессии. Он является частью семейства линейных моделей и представляет собой регуляризованную линейную регрессию.

    Главной особенностью Ridge-регрессии является добавление L2-регуляризации к стандартному методу наименьших квадратов (OLS).

    Принцип работы Ridge-регрессии:

    1. Линейная регрессия: Как и в обычной линейной регрессии, Ridge-регрессия стремится найти линейную связь между независимыми переменными (признаками) и целевой переменной.
    2. L2-регуляризация: Главное отличие Ridge-регрессии заключается в том, что она добавляет L2-регуляризацию к функции потерь. Это означает, что к сумме квадратов разностей между фактическими и предсказанными значениями добавляется штраф за большие значения коэффициентов регрессии.
    3. Штраф на коэффициенты: L2-регуляризация применяет штраф на значения коэффициентов регрессии. Это приводит к тому, что некоторые коэффициенты становятся ближе к нулю, что, в свою очередь, уменьшает переобучение и повышает устойчивость модели.
    4. Гиперпараметр α: Одним из важных параметров Ridge-регрессии является гиперпараметр α (альфа), который определяет степень регуляризации. Большие значения α приводят к более сильной регуляризации и, как следствие, к более простым моделям с более низкими значениями коэффициентов.

    Преимущества Ridge-регрессии:

    Ограничения Ridge-регрессии:

    Ridge-регрессия - это метод регрессии, который добавляет L2-регуляризацию к стандартной линейной регрессии, с целью уменьшения переобучения, увеличения устойчивости и решения проблем мультиколлинеарности. Этот метод полезен, когда требуется баланс между точностью и стабильностью модели.


    2.1.14.1. Код создания модели Ridge и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.Ridge, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # Ridge.py
    # The code demonstrates the process of training Ridge model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import Ridge
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "Ridge"
    onnx_model_filename = data_path + "ridge"

    # create a Ridge model
    regression_model = Ridge()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  Ridge Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382641178552
    Python  Mean Absolute Error: 6.347684462929819
    Python  Mean Squared Error: 49.77814206996523
    Python  
    Python  Ridge ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ridge_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382634837793
    Python  Mean Absolute Error: 6.347684915729416
    Python  Mean Squared Error: 49.77815046053819
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  6
    Python  
    Python  Ridge ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ridge_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641178552
    Python  Mean Absolute Error: 6.347684462929819
    Python  Mean Squared Error: 49.77814206996523
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.49. Результат работы скрипта Ridge.py (float ONNX)

    Рис.49. Результат работы скрипта Ridge.py (float ONNX)



    2.1.14.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели ridge_float.onnx и ridge_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                        Ridge.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "Ridge"
    #define   ONNXFilenameFloat  "ridge_float.onnx"
    #define   ONNXFilenameDouble "ridge_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    Ridge (EURUSD,H1)       Testing ONNX float: Ridge (ridge_float.onnx)
    Ridge (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382634837793
    Ridge (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3476849157294160
    Ridge (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781504605381784
    Ridge (EURUSD,H1)       
    Ridge (EURUSD,H1)       Testing ONNX double: Ridge (ridge_double.onnx)
    Ridge (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382641178552
    Ridge (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3476844629298235
    Ridge (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781420699652131
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: Ridge (ridge_float.onnx)
    Python  Mean Absolute Error: 6.347684462929819
    MQL5:   Mean Absolute Error: 6.3476849157294160
    
           
    Testing ONNX double: Ridge (ridge_double.onnx)
    Python  Mean Absolute Error: 6.347684462929819
    MQL5:   Mean Absolute Error: 6.3476844629298235

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.14.3. ONNX-представление моделей ridge_float.onnx и ridge_double.onnx

     Рис.50. ONNX-представление модели ridge_float.onnx в Netron

    Рис.50. ONNX-представление модели ridge_float.onnx в Netron



    Рис.51. ONNX-представление модели ridge_double.onnx в Netron

    Рис.51. ONNX-представление модели ridge_double.onnx в Netron



    2.1.15. sklearn.linear_model.RidgeCV

    RidgeCV - это расширение Ridge-регрессии, которое включает в себя автоматический выбор наилучшего значения гиперпараметра α (альфа), который определяет степень регуляризации в Ridge-регрессии. Гиперпараметр α контролирует баланс между минимизацией суммы квадратов ошибок (как в обычной линейной регрессии) и минимизацией значения коэффициентов регрессии (регуляризацией). RidgeCV автоматически подбирает оптимальное значение α на основе заданных параметров и критериев. Вот ключевые аспекты RidgeCV:

    Принцип работы RidgeCV:

    1. Входные данные: RidgeCV принимает на вход данные, состоящие из признаков (независимых переменных) и целевой переменной (непрерывной).
    2. Выбор α: Ridge-регрессия требует выбора гиперпараметра α, который определяет степень регуляризации. RidgeCV автоматически подбирает оптимальное значение α из заданного диапазона.
    3. Кросс-валидация: RidgeCV использует кросс-валидацию, например, k-кратную перекрестную проверку (k-fold cross-validation), чтобы оценить, какое значение α обеспечивает наилучшее обобщение модели на независимых данных.
    4. Оптимальное α: По завершении процесса обучения RidgeCV выбирает значение α, которое дает наилучшую производительность на кросс-валидации, и использует это значение для обучения окончательной Ridge-регрессионной модели.

    Преимущества RidgeCV:

    Ограничения RidgeCV:

    RidgeCV - это метод Ridge-регрессии с автоматическим выбором оптимального значения гиперпараметра α с использованием кросс-валидации. Этот метод упрощает процесс выбора гиперпараметра и позволяет найти наилучший баланс между регуляризацией и производительностью модели.


    2.1.15.1. Код создания модели RidgeCV и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.RidgeCV, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # RidgeCV.py
    # The code demonstrates the process of training RidgeCV model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import RidgeCV
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "RidgeCV"
    onnx_model_filename = data_path + "ridge_cv"

    # create a RidgeCV model
    regression_model = RidgeCV()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  RidgeCV Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382499160807
    Python  Mean Absolute Error: 6.34720334999352
    Python  Mean Squared Error: 49.77832999861571
    Python  
    Python  RidgeCV ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ridge_cv_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382499108485
    Python  Mean Absolute Error: 6.3472036427935485
    Python  Mean Squared Error: 49.77833006785168
    Python  R^2 matching decimal places:  11
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  6
    Python  
    Python  RidgeCV ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ridge_cv_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382499160807
    Python  Mean Absolute Error: 6.34720334999352
    Python  Mean Squared Error: 49.77832999861571
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  14
    

    Рис.52. Результат работы скрипта RidgeCV.py (float ONNX)

    Рис.52. Результат работы скрипта RidgeCV.py (float ONNX)


    2.1.15.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели ridge_cv_float.onnx и ridge_cv_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                      RidgeCV.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "RidgeCV"
    #define   ONNXFilenameFloat  "ridge_cv_float.onnx"
    #define   ONNXFilenameDouble "ridge_cv_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    RidgeCV (EURUSD,H1)     Testing ONNX float: RidgeCV (ridge_cv_float.onnx)
    RidgeCV (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962382499108485
    RidgeCV (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3472036427935485
    RidgeCV (EURUSD,H1)     MQL5:   Mean Squared Error: 49.7783300678516909
    RidgeCV (EURUSD,H1)     
    RidgeCV (EURUSD,H1)     Testing ONNX double: RidgeCV (ridge_cv_double.onnx)
    RidgeCV (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962382499160807
    RidgeCV (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3472033499935216
    RidgeCV (EURUSD,H1)     MQL5:   Mean Squared Error: 49.7783299986157246
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: RidgeCV (ridge_cv_float.onnx)
    Python  Mean Absolute Error: 6.34720334999352
    MQL5:   Mean Absolute Error: 6.3472036427935485
    
    Testing ONNX double: RidgeCV (ridge_cv_double.onnx)
    Python  Mean Absolute Error: 6.34720334999352
    MQL5:   Mean Absolute Error: 6.3472033499935216
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.15.3. ONNX-представление моделей ridge_cv_float.onnx и ridge_cv_double.onnx


    Рис.53. ONNX-представление модели ridge_cv_float.onnx в Netron

    Рис.53. ONNX-представление модели ridge_cv_float.onnx в Netron



    Рис.54. ONNX-представление модели ridge_cv_double.onnx в Netron

    Рис.54. ONNX-представление модели ridge_cv_double.onnx в Netron


    2.1.16. sklearn.linear_model.OrthogonalMatchingPursuit

    OrthogonalMatchingPursuit (OMP) - это алгоритм для решения задачи отбора признаков и решения линейной регрессии.

    Он является одним из методов решения проблемы подбора наиболее важных признаков, что может быть полезным для снижения размерности данных и улучшения обобщающей способности модели.

    Принцип работы OrthogonalMatchingPursuit:

    1. Входные данные: Начнем с набора данных, который включает признаки (независимые переменные) и значения целевой переменной (непрерывные).
    2. Выбор числа признаков: Одним из первых шагов при использовании OrthogonalMatchingPursuit является определение количества признаков, которые вы хотите включить в модель. Это количество может быть задано заранее или выбрано с использованием критериев, таких как критерий информационного критерия Акаике (AIC) или критерий минимальной ошибки.
    3. Итеративное добавление признаков: Алгоритм начинает с пустой модели и итеративно добавляет признаки, которые наилучшим образом объясняют остатки модели. В каждой итерации выбирается новый признак так, чтобы он был ортогонален к остальным выбранным признакам. Оптимальный признак выбирается на основе корреляции с остатками модели.
    4. Обучение модели: После добавления заданного числа признаков модель обучается на данных с учетом только этих признаков.
    5. Получение прогнозов: После обучения модель может быть использована для предсказания значений целевой переменной на новых данных.

    Преимущества OrthogonalMatchingPursuit:

    Ограничения OrthogonalMatchingPursuit:

    OrthogonalMatchingPursuit - это алгоритм для решения задачи отбора признаков и линейной регрессии, который позволяет выбирать наиболее информативные признаки для модели. Этот метод может быть полезным для снижения размерности данных и улучшения интерпретируемости модели.


    2.1.16.1. Код создания модели OrthogonalMatchingPursuit и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.OrthogonalMatchingPursuit, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # OrthogonalMatchingPursuit.py
    # The code demonstrates the process of training OrthogonalMatchingPursuit model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import OrthogonalMatchingPursuit
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "OrthogonalMatchingPursuit"
    onnx_model_filename = data_path + "orthogonal_matching_pursuit"

    # create an OrthogonalMatchingPursuit model
    regression_model = OrthogonalMatchingPursuit()

    # fit the model to the data
    regression_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  OrthogonalMatchingPursuit Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.3477379263364275
    Python  Mean Squared Error: 49.778140171281784
    Python  
    Python  OrthogonalMatchingPursuit ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\orthogonal_matching_pursuit_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641628886
    Python  Mean Absolute Error: 6.3477377671679385
    Python  Mean Squared Error: 49.77814147404787
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  OrthogonalMatchingPursuit ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\orthogonal_matching_pursuit_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.3477379263364275
    Python  Mean Squared Error: 49.778140171281784
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  15
    Python  double ONNX model precision:  16
    

     Рис.55. Результат работы скрипта OrthogonalMatchingPursuit.py (float ONNX)

    Рис.55. Результат работы скрипта OrthogonalMatchingPursuit.py (float ONNX)


    2.1.16.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели orthogonal_matching_pursuit_float.onnx и orthogonal_matching_pursuit_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                    OrthogonalMatchingPursuit.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "OrthogonalMatchingPursuit"
    #define   ONNXFilenameFloat  "orthogonal_matching_pursuit_float.onnx"
    #define   ONNXFilenameDouble "orthogonal_matching_pursuit_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    OrthogonalMatchingPursuit (EURUSD,H1)   Testing ONNX float: OrthogonalMatchingPursuit (orthogonal_matching_pursuit_float.onnx)
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962382641628886
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3477377671679385
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   Mean Squared Error: 49.7781414740478638
    OrthogonalMatchingPursuit (EURUSD,H1)   
    OrthogonalMatchingPursuit (EURUSD,H1)   Testing ONNX double: OrthogonalMatchingPursuit (orthogonal_matching_pursuit_double.onnx)
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3477379263364275
    OrthogonalMatchingPursuit (EURUSD,H1)   MQL5:   Mean Squared Error: 49.7781401712817768
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: OrthogonalMatchingPursuit (orthogonal_matching_pursuit_float.onnx)
    Python  Mean Absolute Error: 6.3477379263364275
    MQL5:   Mean Absolute Error: 6.3477377671679385
            
    Testing ONNX double: OrthogonalMatchingPursuit (orthogonal_matching_pursuit_double.onnx)
    Python  Mean Absolute Error: 6.3477379263364275
    MQL5:   Mean Absolute Error: 6.3477379263364275

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.16.3. ONNX-представление моделей orthogonal_matching_pursuit_float.onnx и orthogonal_matching_pursuit_double.onnx


    Рис.56. ONNX-представление модели orthogonal_matching_pursuit_float.onnx в Netron

    Рис.56. ONNX-представление модели orthogonal_matching_pursuit_float.onnx в Netron



    Рис.57. ONNX-представление модели orthogonal_matching_pursuit_double.onnx в Netron

    Рис.57. ONNX-представление модели orthogonal_matching_pursuit_double.onnx в Netron

    2.1.17. sklearn.linear_model.PassiveAggressiveRegressor

    PassiveAggressiveRegressor - это метод машинного обучения, который используется для решения задачи регрессии.

    Этот метод представляет собой вариант алгоритма Passive-Aggressive (PA), который может использоваться для обучения модели, способной предсказывать непрерывные значения целевой переменной.

    Принцип работы PassiveAggressiveRegressor:

    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и значения целевой переменной (непрерывные).
    2. Обучение с учителем: PassiveAggressiveRegressor - это метод обучения с учителем, который обучается на парах (X, y), где X - признаки, y - соответствующие значения целевой переменной.
    3. Адаптивное обучение: Основная идея метода Passive-Aggressive заключается в том, что модель обучается адаптивно, минимизируя ошибку прогноза на каждом обучающем примере. Модель обновляется путем коррекции весов, чтобы уменьшить ошибку предсказания.
    4. Параметр C: PassiveAggressiveRegressor имеет гиперпараметр C, который контролирует то, насколько сильно модель адаптируется к ошибкам. Большее значение C означает более агрессивное обновление весов, в то время как меньшее значение C делает модель менее агрессивной.
    5. Предсказание: После обучения модель может быть использована для предсказания значений целевой переменной на новых данных.

    Преимущества PassiveAggressiveRegressor:

    Ограничения PassiveAggressiveRegressor:

    PassiveAggressiveRegressor - это метод машинного обучения для задачи регрессии, который обучается адаптивно, минимизируя ошибку предсказания на обучающих данных. Этот метод может быть полезным для обработки больших объемов данных и требует настройки параметра C для достижения оптимальной производительности.


    2.1.17.1. Код создания модели PassiveAggressiveRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.PassiveAggressiveRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # PassiveAggressiveRegressor.py
    # The code demonstrates the process of training PassiveAggressiveRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import PassiveAggressiveRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "PassiveAggressiveRegressor"
    onnx_model_filename = data_path + "passive_aggressive_regressor"

    # create a PassiveAggressiveRegressor model
    regression_model = PassiveAggressiveRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  PassiveAggressiveRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9894376841493092
    Python  Mean Absolute Error: 9.64524669506544
    Python  Mean Squared Error: 139.76857373191007
    Python  
    Python  PassiveAggressiveRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\passive_aggressive_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9894376801868329
    Python  Mean Absolute Error: 9.645248834431873
    Python  Mean Squared Error: 139.76862616640122
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  3
    Python  float ONNX model precision:  5
    Python  
    Python  PassiveAggressiveRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\passive_aggressive_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9894376841493092
    Python  Mean Absolute Error: 9.64524669506544
    Python  Mean Squared Error: 139.76857373191007
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  14
    

    Рис.58. Результат работы скрипта PassiveAggressiveRegressor.py (double ONNX)

    Рис.58. Результат работы скрипта PassiveAggressiveRegressor.py (double ONNX)


    2.1.17.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели passive_aggressive_regressor_float.onnx и passive_aggressive_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                   PassiveAggressiveRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "PassiveAggressiveRegressor"
    #define   ONNXFilenameFloat  "passive_aggressive_regressor_float.onnx"
    #define   ONNXFilenameDouble "passive_aggressive_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    PassiveAggressiveRegressor (EURUSD,H1)  Testing ONNX float: PassiveAggressiveRegressor (passive_aggressive_regressor_float.onnx)
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   R-Squared (Coefficient of determination): 0.9894376801868329
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   Mean Absolute Error: 9.6452488344318716
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   Mean Squared Error: 139.7686261664012761
    PassiveAggressiveRegressor (EURUSD,H1)  
    PassiveAggressiveRegressor (EURUSD,H1)  Testing ONNX double: PassiveAggressiveRegressor (passive_aggressive_regressor_double.onnx)
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   R-Squared (Coefficient of determination): 0.9894376841493092
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   Mean Absolute Error: 9.6452466950654419
    PassiveAggressiveRegressor (EURUSD,H1)  MQL5:   Mean Squared Error: 139.7685737319100667
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: PassiveAggressiveRegressor (passive_aggressive_regressor_float.onnx)
    Python  Mean Absolute Error: 9.64524669506544
    MQL5:   Mean Absolute Error: 9.6452488344318716
            
    Testing ONNX double: PassiveAggressiveRegressor (passive_aggressive_regressor_double.onnx)
    Python  Mean Absolute Error: 9.64524669506544
    MQL5:   Mean Absolute Error: 9.6452466950654419

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.17.3. ONNX-представление моделей passive_aggressive_regressor_float.onnx и passive_aggressive_regressor_double.onnx


    Рис.59. ONNX-представление модели passive_aggressive_regressor_float.onnx в Netron

    Рис.59. ONNX-представление модели passive_aggressive_regressor_float.onnx в Netron



    Рис.60. ONNX-представление модели passive_aggressive_regressor_double.onnx в Netron

    Рис.60. ONNX-представление модели passive_aggressive_regressor_double.onnx в Netron



    2.1.18. sklearn.linear_model.QuantileRegressor

    QuantileRegressor - это метод машинного обучения, который используется для оценки квантилей (конкретных процентилей) целевой переменной в задачах регрессии.

    Вместо предсказания среднего значения целевой переменной, как это делается в типичных задачах регрессии, QuantileRegressor предсказывает значения, соответствующие заданным квантилям, таким как медиана (50-й процентиль) или 25-й и 75-й процентили.

    Принцип работы QuantileRegressor:

    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и целевую переменную (непрерывную).
    2. Цель - квантили: Вместо предсказания точечных значений целевой переменной, QuantileRegressor моделирует условное распределение целевой переменной и предсказывает значения для определенных квантилей этого распределения.
    3. Обучение для разных квантилей: Обучение модели QuantileRegressor включает в себя обучение отдельных моделей для каждого желаемого квантиля. Каждая из этих моделей предсказывает значение, соответствующее своему квантилю.
    4. Параметр квантиля: Основным параметром для этого метода является выбор желаемых квантилей, для которых вы хотите получить предсказания. Например, если вам нужны предсказания медианы, вам потребуется обучить модель на 50-й процентиль.
    5. Предсказание квантилей: После обучения модель может быть использована для предсказания значений, соответствующих заданным квантилям, на новых данных.

    Преимущества QuantileRegressor:

    Ограничения QuantileRegressor:

    QuantileRegressor - это метод машинного обучения, который предназначен для предсказания значений, соответствующих заданным квантилям целевой переменной. Этот метод может быть полезным в задачах, где интересуют различные процентили распределения, а также в задачах, где данные могут содержать выбросы.


    2.1.18.1. Код создания модели QuantileRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.QuantileRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # QuantileRegressor.py
    # The code demonstrates the process of training QuantileRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import QuantileRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "QuantileRegressor"
    onnx_model_filename = data_path + "quantile_regressor"

    # create a QuantileRegressor model
    regression_model = QuantileRegressor(solver='highs')

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  QuantileRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9959915738839231
    Python  Mean Absolute Error: 6.3693091850025185
    Python  Mean Squared Error: 53.0425343337143
    Python  
    Python  QuantileRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\quantile_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9959915739158818
    Python  Mean Absolute Error: 6.3693091422201125
    Python  Mean Squared Error: 53.042533910812814
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  7
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  7
    Python  
    Python  QuantileRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\quantile_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9959915738839231
    Python  Mean Absolute Error: 6.3693091850025185
    Python  Mean Squared Error: 53.0425343337143
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  13
    Python  double ONNX model precision:  16
    

    Рис.61. Результат работы скрипта QuantileRegressor.py (float ONNX)

    Рис.61. Результат работы скрипта QuantileRegressor.py (float ONNX)


    2.1.18.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели quantile_regressor_float.onnx и quantile_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                            QuantileRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "QuantileRegressor"
    #define   ONNXFilenameFloat  "quantile_regressor_float.onnx"
    #define   ONNXFilenameDouble "quantile_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    QuantileRegressor (EURUSD,H1)   Testing ONNX float: QuantileRegressor (quantile_regressor_float.onnx)
    QuantileRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9959915739158818
    QuantileRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3693091422201169
    QuantileRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 53.0425339108128071
    QuantileRegressor (EURUSD,H1)   
    QuantileRegressor (EURUSD,H1)   Testing ONNX double: QuantileRegressor (quantile_regressor_double.onnx)
    QuantileRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9959915738839231
    QuantileRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3693091850025185
    QuantileRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 53.0425343337142721
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: QuantileRegressor (quantile_regressor_float.onnx)
    Python  Mean Absolute Error: 6.3693091850025185
    MQL5:   Mean Absolute Error: 6.3693091422201169
    
    Testing ONNX double: QuantileRegressor (quantile_regressor_double.onnx)
    Python  Mean Absolute Error: 6.3693091850025185
    MQL5:   Mean Absolute Error: 6.3693091850025185

    Точность MAE ONNX float: 7 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.18.3. ONNX-представление моделей quantile_regressor_float.onnx и quantile_regressor_double.onnx


    Рис.62. ONNX-представление модели quantile_regressor_float.onnx в Netron

    Рис.62. ONNX-представление модели quantile_regressor_float.onnx в Netron


    Рис.63. ONNX-представление модели quantile_regressor_double.onnx в Netron

    Рис.63. ONNX-представление модели quantile_regressor_double.onnx в Netron



    2.1.19. sklearn.linear_model.RANSACRegressor

    RANSACRegressor - это метод машинного обучения, который применяется для решения задачи регрессии с использованием метода RANSAC (Random Sample Consensus).

    Метод RANSAC предназначен для обработки данных, которые содержат выбросы или несовершенства, и позволяет создать более устойчивую регрессионную модель путем исключения влияния выбросов.

    Принцип работы RANSACRegressor:

    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и целевую переменную (непрерывную).
    2. Выбор случайных подмножеств: Метод RANSAC начинает работу с выбора случайных подмножеств данных, которые используются для обучения регрессионной модели. Эти подмножества называются "гипотезами".
    3. Подгонка модели к гипотезам: Для каждой выбранной гипотезы обучается регрессионная модель. В случае RANSACRegressor, обычно используется линейная регрессия, и модель подгоняется к подмножеству данных.
    4. Оценка выбросов: После обучения модели оценивается, насколько хорошо она соответствует всем данным. Для каждой точки данных вычисляется ошибка между предсказанным значением и фактическим значением.
    5. Идентификация выбросов: Точки данных, которые имеют ошибку, превышающую некоторый заданный порог, считаются выбросами. Эти выбросы могут влиять на обучение модели и искажать результаты.
    6. Обновление модели: Все точки данных, которые не считаются выбросами, используются для обновления регрессионной модели. Этот процесс может повторяться несколько раз с различными случайными гипотезами.
    7. Финальная модель: После нескольких итераций, RANSACRegressor выбирает наилучшую модель, которая была обучена на подмножестве данных, и возвращает ее в качестве финальной регрессионной модели.

    Преимущества RANSACRegressor:

    Ограничения RANSACRegressor:

    RANSACRegressor - это метод машинного обучения, который используется для решения задачи регрессии, основанный на методе RANSAC. Этот метод позволяет создать более устойчивую регрессионную модель в случае, когда данные содержат выбросы или несовершенства, путем исключения их влияния на модель.


    2.1.19.1. Код создания модели RANSACRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.RANSACRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # RANSACRegressor.py
    # The code demonstrates the process of training RANSACRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import RANSACRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "RANSACRegressor"
    onnx_model_filename = data_path + "ransac_regressor"

    # create a RANSACRegressor model
    regression_model = RANSACRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("ONNX: MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  RANSACRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336427
    Python  Mean Squared Error: 49.77814017128179
    Python  
    Python  RANSACRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ransac_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382641628886
    Python  Mean Absolute Error: 6.3477377671679385
    Python  Mean Squared Error: 49.77814147404787
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  ONNX: MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  RANSACRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\ransac_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.347737926336427
    Python  Mean Squared Error: 49.77814017128179
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.64. Результат работы скрипта RANSACRegressor.py (float ONNX)

    Рис.64. Результат работы скрипта RANSACRegressor.py (float ONNX)


    2.1.19.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели ransac_regressor_float.onnx и ransac_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                              RANSACRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "RANSACRegressor"
    #define   ONNXFilenameFloat  "ransac_regressor_float.onnx"
    #define   ONNXFilenameDouble "ransac_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    RANSACRegressor (EURUSD,H1)     Testing ONNX float: RANSACRegressor (ransac_regressor_float.onnx)
    RANSACRegressor (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962382641628886
    RANSACRegressor (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3477377671679385
    RANSACRegressor (EURUSD,H1)     MQL5:   Mean Squared Error: 49.7781414740478638
    RANSACRegressor (EURUSD,H1)     
    RANSACRegressor (EURUSD,H1)     Testing ONNX double: RANSACRegressor (ransac_regressor_double.onnx)
    RANSACRegressor (EURUSD,H1)     MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    RANSACRegressor (EURUSD,H1)     MQL5:   Mean Absolute Error: 6.3477379263364266
    RANSACRegressor (EURUSD,H1)     MQL5:   Mean Squared Error: 49.7781401712817768
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: RANSACRegressor (ransac_regressor_float.onnx)
    Python  Mean Absolute Error: 6.347737926336427
    MQL5:   Mean Absolute Error: 6.3477377671679385
         
    Testing ONNX double: RANSACRegressor (ransac_regressor_double.onnx)
    Python  Mean Absolute Error: 6.347737926336427
    MQL5:   Mean Absolute Error: 6.3477379263364266

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.19.3. ONNX-представление моделей ransac_regressor_float.onnx и ransac_regressor_double.onnx


    Рис.65. ONNX-представление модели ransac_regressor_float.onnx в Netron

    Рис.65. ONNX-представление модели ransac_regressor_float.onnx в Netron


    Рис.66. ONNX-представление модели ransac_regressor_double.onnx в Netron

    Рис.66. ONNX-представление модели ransac_regressor_double.onnx в Netron



    2.1.20. sklearn.linear_model.TheilSenRegressor

    Theil-Sen регрессия (Theil-Sen estimator) - это метод оценки регрессии, который используется для приближенного поиска линейной зависимости между независимыми переменными и целевой переменной.

    Он предлагает более устойчивую оценку, чем обычная линейная регрессия в случае наличия выбросов и шума в данных.

    Принцип работы Theil-Sen регрессии:

    1. Выбор точек: Для начала, метод Theil-Sen выбирает случайные пары точек данных из набора обучающих данных.
    2. Расчет наклона (наклонов): Для каждой пары точек данных, метод вычисляет наклон линии, проходящей через эти точки. Это создает множество наклонов.
    3. Медианный наклон: Затем, метод находит медианный наклон из множества наклонов. Этот медианный наклон используется в качестве оценки наклона линейной регрессии.
    4. Медиана отклонений: Для каждой точки данных, метод вычисляет отклонение (разницу между фактическим значением и предсказанным значением на основе медианного наклона) и находит медиану этих отклонений. Это создает оценку для коэффициента сдвига (пересечения) линейной регрессии.
    5. Окончательная оценка: Итоговые оценки наклона и коэффициента сдвига используются для построения линейной регрессионной модели.

    Преимущества Theil-Sen регрессии:

    Ограничения Theil-Sen регрессии:

    Theil-Sen регрессия - это метод оценки регрессии, который предоставляет устойчивую оценку линейной зависимости между независимыми переменными и целевой переменной, особенно в случае наличия выбросов и шума в данных. Этот метод полезен, когда требуется устойчивая оценка в условиях реальных данных.


    2.1.20.1. Код создания модели TheilSenRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.TheilSenRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # TheilSenRegressor.py
    # The code demonstrates the process of training TheilSenRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import TheilSenRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "TheilSenRegressor"
    onnx_model_filename = data_path + "theil_sen_regressor"

    # create a TheilSen Regressor model
    regression_model = TheilSenRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  TheilSenRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962329196940459
    Python  Mean Absolute Error: 6.338686004537594
    Python  Mean Squared Error: 49.84886353898735
    Python  
    Python  TheilSenRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\theil_sen_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.996232919516505
    Python  Mean Absolute Error: 6.338686370832071
    Python  Mean Squared Error: 49.84886588834327
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  6
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  6
    Python  
    Python  TheilSenRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\theil_sen_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962329196940459
    Python  Mean Absolute Error: 6.338686004537594
    Python  Mean Squared Error: 49.84886353898735
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.67. Результат работы скрипта TheilSenRegressor.py (float ONNX)

    Рис.67. Результат работы скрипта TheilSenRegressor.py (float ONNX)


    2.1.20.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели theil_sen_regressor_float.onnx и theil_sen_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                            TheilSenRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "TheilSenRegressor"
    #define   ONNXFilenameFloat  "theil_sen_regressor_float.onnx"
    #define   ONNXFilenameDouble "theil_sen_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    TheilSenRegressor (EURUSD,H1)   Testing ONNX float: TheilSenRegressor (theil_sen_regressor_float.onnx)
    TheilSenRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962329195165051
    TheilSenRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3386863708320735
    TheilSenRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 49.8488658883432691
    TheilSenRegressor (EURUSD,H1)   
    TheilSenRegressor (EURUSD,H1)   Testing ONNX double: TheilSenRegressor (theil_sen_regressor_double.onnx)
    TheilSenRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9962329196940459
    TheilSenRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 6.3386860045375943
    TheilSenRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 49.8488635389873735
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: TheilSenRegressor (theil_sen_regressor_float.onnx)
    Python  Mean Absolute Error: 6.338686004537594
    MQL5:   Mean Absolute Error: 6.3386863708320735
            
    Testing ONNX double: TheilSenRegressor (theil_sen_regressor_double.onnx)
    Python  Mean Absolute Error: 6.338686004537594
    MQL5:   Mean Absolute Error: 6.3386860045375943

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 15 знаков после запятой.


    2.1.20.3. ONNX-представление моделей theil_sen_regressor_float.onnx и theil_sen_regressor_double.onnx


    Рис.68. ONNX-представление модели theil_sen_regressor_float.onnx в Netron

    Рис.68. ONNX-представление модели theil_sen_regressor_float.onnx в Netron


    Рис.69. ONNX-представление модели theil_sen_regressor_double.onnx в Netron

    Рис.69. ONNX-представление модели theil_sen_regressor_double.onnx в Netron



    2.1.21. sklearn.linear_model.LinearSVR

    LinearSVR (Linear Support Vector Regression) - это модель машинного обучения для задачи регрессии, основанная на методе опорных векторов (Support Vector Machines, SVM).

    Этот метод применяется для нахождения линейных отношений между признаками и целевой переменной с использованием линейного ядра.

    Принцип работы LinearSVR:

    1. Входные данные: LinearSVR начинает с набора данных, включающего признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Выбор линейной модели: Модель предполагает, что существует линейная зависимость между признаками и целевой переменной, описываемая уравнением линейной регрессии.
    3. Обучение модели: LinearSVR находит оптимальные значения коэффициентов модели, минимизируя функцию потерь, которая учитывает ошибку предсказания и допустимую ошибку (эпсилон).
    4. Получение прогнозов: После обучения, модель может быть использована для предсказания значений целевой переменной для новых данных на основе найденных коэффициентов.

    Преимущества LinearSVR:

    Ограничения LinearSVR:

    LinearSVR - это модель машинного обучения для регрессии, которая применяет метод опорных векторов для поиска линейных отношений между признаками и целевой переменной. Она поддерживает регуляризацию и может использоваться в задачах, где важен контроль допустимой ошибки. Однако модель ограничена линейной зависимостью и может быть чувствительна к выбросам.


    2.1.21.1. Код создания модели LinearSVR и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.LinearSVR, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # LinearSVR.py
    # The code demonstrates the process of training LinearSVR model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.svm import LinearSVR
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import
    onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "LinearSVR"
    onnx_model_filename = data_path + "linear_svr"

    # create a Linear SVR model
    linear_svr_model = LinearSVR()

    # fit the model to the data
    linear_svr_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = linear_svr_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(linear_svr_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(linear_svr_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  LinearSVR Original model (double)
    Python  R-squared (Coefficient of determination): 0.9944935515149387
    Python  Mean Absolute Error: 7.026852359381935
    Python  Mean Squared Error: 72.86550241109444
    Python  
    Python  LinearSVR ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\linear_svr_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9944935580726729
    Python  Mean Absolute Error: 7.026849848037511
    Python  Mean Squared Error: 72.86541563418206
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  4
    Python  MSE matching decimal places:  3
    Python  float ONNX model precision:  4
    Python  
    Python  LinearSVR ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\linear_svr_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9944935515149387
    Python  Mean Absolute Error: 7.026852359381935
    Python  Mean Squared Error: 72.86550241109444
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  14
    Python  double ONNX model precision:  15
    

    Рис.70. Результат работы скрипта LinearSVR.py (float ONNX)

    Рис.70. Результат работы скрипта LinearSVR.py (float ONNX)


    2.1.21.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели linear_svr_float.onnx и linear_svr_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                    LinearSVR.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "LinearSVR"
    #define   ONNXFilenameFloat  "linear_svr_float.onnx"
    #define   ONNXFilenameDouble "linear_svr_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    LinearSVR (EURUSD,H1)   Testing ONNX float: LinearSVR (linear_svr_float.onnx)
    LinearSVR (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9944935580726729
    LinearSVR (EURUSD,H1)   MQL5:   Mean Absolute Error: 7.0268498480375108
    LinearSVR (EURUSD,H1)   MQL5:   Mean Squared Error: 72.8654156341820567
    LinearSVR (EURUSD,H1)   
    LinearSVR (EURUSD,H1)   Testing ONNX double: LinearSVR (linear_svr_double.onnx)
    LinearSVR (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9944935515149387
    LinearSVR (EURUSD,H1)   MQL5:   Mean Absolute Error: 7.0268523593819374
    LinearSVR (EURUSD,H1)   MQL5:   Mean Squared Error: 72.8655024110944680
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: LinearSVR (linear_svr_float.onnx)
    Python  Mean Absolute Error: 7.026852359381935
    MQL5:   Mean Absolute Error: 7.0268498480375108
       
    Testing ONNX double: LinearSVR (linear_svr_double.onnx)
    Python  Mean Absolute Error: 7.026852359381935
    MQL5:   Mean Absolute Error: 7.0268523593819374

    Точность MAE ONNX float: 4 знака после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.21.3. ONNX-представление моделей linear_svr_float.onnx и linear_svr_double.onnx


    Рис.71. ONNX-представление модели linear_svr_float.onnx в Netron

    Рис.71. ONNX-представление модели linear_svr_float.onnx в Netron


    Рис.72. ONNX-представление модели linear_svr_double.onnx в Netron

    Рис.72. ONNX-представление модели linear_svr_double.onnx в Netron


    2.1.22. sklearn.neural_network.MLPRegressor

    MLPRegressor (Multi-Layer Perceptron Regressor) - это модель машинного обучения, которая использует искусственные нейронные сети для задачи регрессии.

    Она представляет собой многослойную нейронную сеть, состоящую из нескольких слоев нейронов (включая входной, скрытые и выходной слои), которая обучается предсказывать непрерывные значения целевой переменной.

    Принцип работы MLPRegressor:

    1. Входные данные: Начнем с набора данных, который включает признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Создание многослойной нейронной сети: MLPRegressor использует многослойную нейронную сеть с несколькими скрытыми слоями нейронов. Эти нейроны связаны взвешенными связями и активационными функциями.
    3. Обучение модели: MLPRegressor обучает нейронную сеть, настраивая веса и смещения так, чтобы минимизировать функцию потерь, которая измеряет разницу между прогнозами сети и реальными значениями целевой переменной. Это происходит с использованием алгоритмов обратного распространения ошибки (backpropagation).
    4. Получение прогнозов: После обучения модель может быть использована для предсказания значений целевой переменной для новых данных.

    Преимущества MLPRegressor:

    Ограничения MLPRegressor:

    MLPRegressor представляет собой мощную модель машинного обучения, основанную на многослойных нейронных сетях, и может быть использована для решения разнообразных задач регрессии. Эта модель гибкая, но требует тщательной настройки и обучения на больших объемах данных для достижения оптимальных результатов.


    2.1.22.1. Код создания модели MLPRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.neural_network.MLPRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # MLPRegressor.py
    # The code demonstrates the process of training MLPRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.neural_network import MLPRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "MLPRegressor"
    onnx_model_filename = data_path + "mlp_regressor"

    # create an MLP Regressor model
    mlp_regressor_model = MLPRegressor(hidden_layer_sizes=(100, 50), activation='relu', max_iter=1000)

    # fit the model to the data
    mlp_regressor_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = mlp_regressor_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(mlp_regressor_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)
    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType
    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(mlp_regressor_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  MLPRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9874070836467945
    Python  Mean Absolute Error: 10.62249788982753
    Python  Mean Squared Error: 166.63901957615224
    Python  
    Python  MLPRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\mlp_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9874070821340352
    Python  Mean Absolute Error: 10.62249972216809
    Python  Mean Squared Error: 166.63903959413219
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  5
    Python  
    Python  MLPRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\mlp_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9874070836467945
    Python  Mean Absolute Error: 10.622497889827532
    Python  Mean Squared Error: 166.63901957615244
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  12
    Python  double ONNX model precision:  14
    

     Рис.73. Результат работы скрипта MLPRegressor.py (float ONNX)

    Рис.73. Результат работы скрипта MLPRegressor.py (float ONNX)


    2.1.22.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели mlp_regressor_float.onnx и mlp_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                 MLPRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "MLPRegressor"
    #define   ONNXFilenameFloat  "mlp_regressor_float.onnx"
    #define   ONNXFilenameDouble "mlp_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    MLPRegressor (EURUSD,H1)        Testing ONNX float: MLPRegressor (mlp_regressor_float.onnx)
    MLPRegressor (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9875198695654352
    MLPRegressor (EURUSD,H1)        MQL5:   Mean Absolute Error: 10.5596681685341309
    MLPRegressor (EURUSD,H1)        MQL5:   Mean Squared Error: 165.1465507645494597
    MLPRegressor (EURUSD,H1)        
    MLPRegressor (EURUSD,H1)        Testing ONNX double: MLPRegressor (mlp_regressor_double.onnx)
    MLPRegressor (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9875198617341387
    MLPRegressor (EURUSD,H1)        MQL5:   Mean Absolute Error: 10.5596715833884609
    MLPRegressor (EURUSD,H1)        MQL5:   Mean Squared Error: 165.1466543942046599
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: MLPRegressor (mlp_regressor_float.onnx)
    Python  Mean Absolute Error: 10.62249788982753
    MQL5:   Mean Absolute Error: 10.6224997221680901
    
    Testing ONNX double: MLPRegressor (mlp_regressor_double.onnx)
    Python  Mean Absolute Error: 10.62249788982753
    MQL5:   Mean Absolute Error: 10.6224978898275282

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.22.3. ONNX-представление моделей mlp_regressor_float.onnx и mlp_regressor_double.onnx


    Рис.74. ONNX-представление модели mlp_regressor_float.onnx в Netron

    Рис.74. ONNX-представление модели mlp_regressor_float.onnx в Netron


    Рис.75. ONNX-представление модели mlp_regressor_double.onnx в Netron

    Рис.75. ONNX-представление модели mlp_regressor_double.onnx в Netron






    2.1.23. sklearn.cross_decomposition.PLSRegression

    PLSRegression (Partial Least Squares Regression) - это метод машинного обучения, который используется для решения задачи регрессии.

    Он является частью семейства методов PLS и применяется для анализа и моделирования отношений между двумя наборами переменных, где один набор является предикторами, а другой - целевыми переменными.

    Принцип работы PLSRegression:

    1. Входные данные: Начнем с двух наборов данных, обозначенных как X и Y. Набор X содержит независимые переменные (предикторы), а набор Y содержит целевые переменные (зависимые).
    2. Подбор линейных комбинаций: PLSRegression находит линейные комбинации (компоненты) в наборах X и Y, которые максимизируют ковариацию между ними. Эти компоненты называются PLS-компонентами.
    3. Поиск максимальной ковариации: Основная цель PLSRegression - найти такие PLS-компоненты, которые максимизируют ковариацию между X и Y. Это позволяет выделить наиболее информативные связи между предикторами и целевыми переменными.
    4. Обучение модели: Как только PLS-компоненты найдены, вы можете использовать их для создания модели, которая может прогнозировать значения Y на основе X.
    5. Получение прогнозов: После обучения модель может быть использована для предсказания значений Y на новых данных, используя соответствующие значения X.

    Преимущества PLSRegression:

    Ограничения PLSRegression:

    PLSRegression - это метод машинного обучения, используемый для анализа и моделирования корреляций между двумя наборами переменных, где один набор служит предикторами, а другой - целевыми переменными. Этот метод позволяет изучать взаимосвязи между данными и может быть полезным для снижения размерности данных и предсказания значений целевых переменных на основе предикторов.


    2.1.23.1. Код создания модели PLSRegression и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.cross_decomposition.PLSRegression, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # PLSRegression.py
    # The code demonstrates the process of training PLSRegression model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places

        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cross_decomposition import PLSRegression
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "PLSRegression"
    onnx_model_filename = data_path + "pls_regression"

    # create a PLSRegression model
    pls_model = PLSRegression(n_components=1)

    # fit the model to the data
    pls_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = pls_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(pls_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(pls_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  PLSRegression Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962382642613388
    Python  Mean Absolute Error: 6.3477379263364275
    Python  Mean Squared Error: 49.778140171281805
    Python  
    Python  PLSRegression ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\pls_regression_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382638567003
    Python  Mean Absolute Error: 6.3477379221400145
    Python  Mean Squared Error: 49.778145525764096
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  8
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  8
    Python  
    Python  PLSRegression ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\pls_regression_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9962382642613388
    Python  Mean Absolute Error: 6.3477379263364275
    Python  Mean Squared Error: 49.778140171281805
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  15
    Python  double ONNX model precision:  16
    

    Рис.76. Результат работы скрипта PLSRegression.py (float ONNX)

    Рис.76. Результат работы скрипта PLSRegression.py (float ONNX)


    2.1.23.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели pls_regression_float.onnx и pls_regression_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                PLSRegression.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "PLSRegression"
    #define   ONNXFilenameFloat  "pls_regression_float.onnx"
    #define   ONNXFilenameDouble "pls_regression_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    PLSRegression (EURUSD,H1)       Testing ONNX float: PLSRegression (pls_regression_float.onnx)
    PLSRegression (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382638567003
    PLSRegression (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3477379221400145
    PLSRegression (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781455257640815
    PLSRegression (EURUSD,H1)       
    PLSRegression (EURUSD,H1)       Testing ONNX double: PLSRegression (pls_regression_double.onnx)
    PLSRegression (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9962382642613388
    PLSRegression (EURUSD,H1)       MQL5:   Mean Absolute Error: 6.3477379263364275
    PLSRegression (EURUSD,H1)       MQL5:   Mean Squared Error: 49.7781401712817839
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: PLSRegression (pls_regression_float.onnx)
    Python  Mean Absolute Error: 6.3477379263364275
    MQL5:   Mean Absolute Error: 6.3477379221400145
           
    Testing ONNX double: PLSRegression (pls_regression_double.onnx)
    Python  Mean Absolute Error: 6.3477379263364275
    MQL5:   Mean Absolute Error: 6.3477379263364275

    Точность MAE ONNX float: 8 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.23.3. ONNX-представление моделей pls_regression_float.onnx и pls_regression_double.onnx


    Рис.77. ONNX-представление модели pls_regression_float.onnx в Netron

    Рис.77. ONNX-представление модели pls_regression_float.onnx в Netron


    Рис.78. ONNX-представление модели pls_regression_double.onnx в Netron

    Рис.78. ONNX-представление модели pls_regression_double.onnx в Netron



    2.1.24. sklearn.linear_model.TweedieRegressor

    TweedieRegressor - это метод регрессии, который предназначен для решения задачи регрессии с использованием распределения Твиди (Tweedie distribution). Распределение Твиди - это вероятностное распределение, которое может описывать широкий спектр данных, включая данные с разной структурой дисперсии. TweedieRegressor применяется в задачах регрессии, где целевая переменная имеет характеристики, которые соответствуют распределению Твиди.

    Принцип работы TweedieRegressor:

    1. Целевая переменная и распределение Твиди: TweedieRegressor предполагает, что целевая переменная имеет распределение Твиди. Распределение Твиди зависит от параметра p, который определяет форму распределения и степень дисперсии.
    2. Обучение модели: TweedieRegressor обучает регрессионную модель, которая предсказывает целевую переменную на основе независимых переменных (признаков). Модель максимизирует правдоподобие для данных, соответствующих распределению Твиди.
    3. Выбор параметра p: Выбор параметра p является важным аспектом при использовании TweedieRegressor. Этот параметр определяет форму распределения и степень дисперсии. Разные значения p могут соответствовать различным типам данных, например, p=1 соответствует распределению Пуассона, а p=2 - нормальному распределению.
    4. Преобразование ответов: Иногда модель может требовать преобразования ответов (целевой переменной) перед обучением. Это преобразование связано с параметром p и может включать логарифмирование или другие преобразования, чтобы обеспечить соответствие распределению Твиди.

    Преимущества TweedieRegressor:

    Ограничения TweedieRegressor:

    TweedieRegressor - это метод регрессии, который использует распределение Твиди для моделирования данных с разной структурой дисперсии. Этот метод полезен в задачах регрессии, где целевая переменная соответствует распределению Твиди и может быть настроен с различными значениями параметра p для лучшей адаптации к данным.


    2.1.24.1. Код создания модели TweedieRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.TweedieRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # TweedieRegressor.py
    # The code demonstrates the process of training TweedieRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import TweedieRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "TweedieRegressor"
    onnx_model_filename = data_path + "tweedie_regressor"

    # create a Tweedie Regressor model
    regression_model = TweedieRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    2023.10.31 11:39:36.223 Python  TweedieRegressor Original model (double)
    2023.10.31 11:39:36.223 Python  R-squared (Coefficient of determination): 0.9962368328117072
    2023.10.31 11:39:36.223 Python  Mean Absolute Error: 6.342397897667562
    2023.10.31 11:39:36.223 Python  Mean Squared Error: 49.797082198408745
    2023.10.31 11:39:36.223 Python  
    2023.10.31 11:39:36.223 Python  TweedieRegressor ONNX model (float)
    2023.10.31 11:39:36.223 Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\tweedie_regressor_float.onnx
    2023.10.31 11:39:36.253 Python  Information about input tensors in ONNX:
    2023.10.31 11:39:36.253 Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    2023.10.31 11:39:36.253 Python  Information about output tensors in ONNX:
    2023.10.31 11:39:36.253 Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    2023.10.31 11:39:36.253 Python  R-squared (Coefficient of determination) 0.9962368338709323
    2023.10.31 11:39:36.253 Python  Mean Absolute Error: 6.342397072978867
    2023.10.31 11:39:36.253 Python  Mean Squared Error: 49.797068181938165
    2023.10.31 11:39:36.253 Python  R^2 matching decimal places:  8
    2023.10.31 11:39:36.253 Python  MAE matching decimal places:  6
    2023.10.31 11:39:36.253 Python  MSE matching decimal places:  4
    2023.10.31 11:39:36.253 Python  float ONNX model precision:  6
    2023.10.31 11:39:36.613 Python  
    2023.10.31 11:39:36.613 Python  TweedieRegressor ONNX model (double)
    2023.10.31 11:39:36.613 Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\tweedie_regressor_double.onnx
    2023.10.31 11:39:36.613 Python  Information about input tensors in ONNX:
    2023.10.31 11:39:36.613 Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    2023.10.31 11:39:36.613 Python  Information about output tensors in ONNX:
    2023.10.31 11:39:36.628 Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    2023.10.31 11:39:36.628 Python  R-squared (Coefficient of determination) 0.9962368328117072
    2023.10.31 11:39:36.628 Python  Mean Absolute Error: 6.342397897667562
    2023.10.31 11:39:36.628 Python  Mean Squared Error: 49.797082198408745
    2023.10.31 11:39:36.628 Python  R^2 matching decimal places:  16
    2023.10.31 11:39:36.628 Python  MAE matching decimal places:  15
    2023.10.31 11:39:36.628 Python  MSE matching decimal places:  15
    2023.10.31 11:39:36.628 Python  double ONNX model precision:  15
    

    Рис.79. Результат работы скрипта TweedieRegressor.py (float ONNX)

    Рис.79. Результат работы скрипта TweedieRegressor.py (float ONNX)


    2.1.24.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели tweedie_regressor_float.onnx и tweedie_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                             TweedieRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "TweedieRegressor"
    #define   ONNXFilenameFloat  "tweedie_regressor_float.onnx"
    #define   ONNXFilenameDouble "tweedie_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    2023.10.31 11:42:20.113 TweedieRegressor (EURUSD,H1)    Testing ONNX float: TweedieRegressor (tweedie_regressor_float.onnx)
    2023.10.31 11:42:20.119 TweedieRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9962368338709323
    2023.10.31 11:42:20.119 TweedieRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 6.3423970729788666
    2023.10.31 11:42:20.119 TweedieRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 49.7970681819381653
    2023.10.31 11:42:20.125 TweedieRegressor (EURUSD,H1)    
    2023.10.31 11:42:20.125 TweedieRegressor (EURUSD,H1)    Testing ONNX double: TweedieRegressor (tweedie_regressor_double.onnx)
    2023.10.31 11:42:20.130 TweedieRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9962368328117072
    2023.10.31 11:42:20.130 TweedieRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 6.3423978976675608
    2023.10.31 11:42:20.130 TweedieRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 49.7970821984087593
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: TweedieRegressor (tweedie_regressor_float.onnx)
    Python  Mean Absolute Error: 6.342397897667562
    MQL5:   Mean Absolute Error: 6.3423970729788666
    
    Testing ONNX double: TweedieRegressor (tweedie_regressor_double.onnx)
    Python  Mean Absolute Error: 6.342397897667562
    MQL5:   Mean Absolute Error: 6.3423978976675608
    

    Точность MAE ONNX float: 6 знаков после запятой, точность MAE ONNX double 14 знаков после запятой.


    2.1.24.3. ONNX-представление моделей tweedie_regressor_float.onnx и tweedie_regressor_double.onnx


    Рис.80. ONNX-представление модели tweedie_regressor_float.onnx в Netron

    Рис.80. ONNX-представление модели tweedie_regressor_float.onnx в Netron


    Рис.81. ONNX-представление модели tweedie_regressor_double.onnx в Netron

    Рис.81. ONNX-представление модели tweedie_regressor_double.onnx в Netron



    2.1.25. sklearn.linear_model.PoissonRegressor

    PoissonRegressor - это метод машинного обучения, который применяется для решения задачи регрессии, основанной на распределении Пуассона.

    Этот метод подходит, когда зависимая переменная (целевая переменная) является счетной, то есть представляет собой количество событий, произошедших в фиксированный период времени или в фиксированном интервале пространства. PoissonRegressor моделирует связь между предикторами (независимыми переменными) и целевой переменной, предполагая, что эта связь соответствует распределению Пуассона.

    Принцип работы PoissonRegressor:

    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и целевую переменную, представляющую количество событий.
    2. Распределение Пуассона: Метод PoissonRegressor моделирует целевую переменную, предполагая, что она следует распределению Пуассона. Распределение Пуассона подходит для моделирования событий, которые происходят с фиксированной средней интенсивностью в заданном временном интервале или пространственном отрезке.
    3. Обучение модели: PoissonRegressor обучает модель, которая оценивает параметры распределения Пуассона, учитывая предикторы. Модель старается найти наилучшую подгонку для наблюдаемых данных с использованием функции правдоподобия, соответствующей распределению Пуассона.
    4. Предсказание счетных значений: После обучения модель может использоваться для предсказания счетных значений (количество событий) на новых данных, и эти предсказания также следуют распределению Пуассона.

    Преимущества PoissonRegressor:

    Ограничения PoissonRegressor:

    PoissonRegressor - это метод машинного обучения, используемый для решения задачи регрессии, когда целевая переменная представляет собой счетные данные, и она моделируется с использованием распределения Пуассона. Этот метод полезен для задач, связанных с событиями, которые происходят с фиксированной интенсивностью в определенных интервалах времени или пространства.


    2.1.25.1. Код создания модели PoissonRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.PoissonRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # PoissonRegressor.py
    # The code demonstrates the process of training PoissonRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places

    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import PoissonRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "PoissonRegressor"
    onnx_model_filename = data_path + "poisson_regressor"

    # create a PoissonRegressor model
    regression_model = PoissonRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  PoissonRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9204304782362495
    Python  Mean Absolute Error: 27.59790466048524
    Python  Mean Squared Error: 1052.9242570153044
    Python  
    Python  PoissonRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\poisson_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9204305082536851
    Python  Mean Absolute Error: 27.59790825165078
    Python  Mean Squared Error: 1052.9238598018305
    Python  R^2 matching decimal places:  6
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  2
    Python  float ONNX model precision:  5
    Python  
    Python  PoissonRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\poisson_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9204304782362495
    Python  Mean Absolute Error: 27.59790466048524
    Python  Mean Squared Error: 1052.9242570153044
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  13
    Python  double ONNX model precision:  14
    


    Рис.82. Результат работы скрипта PoissonRegressor.py (float ONNX)

    Рис.82. Результат работы скрипта PoissonRegressor.py (float ONNX)


    2.1.25.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели poisson_regressor_float.onnx и poisson_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                             PoissonRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "PoissonRegressor"
    #define   ONNXFilenameFloat  "poisson_regressor_float.onnx"
    #define   ONNXFilenameDouble "poisson_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    PoissonRegressor (EURUSD,H1)    Testing ONNX float: PoissonRegressor (poisson_regressor_float.onnx)
    PoissonRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9204305082536851
    PoissonRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 27.5979082516507788
    PoissonRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 1052.9238598018305311
    PoissonRegressor (EURUSD,H1)    
    PoissonRegressor (EURUSD,H1)    Testing ONNX double: PoissonRegressor (poisson_regressor_double.onnx)
    PoissonRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9204304782362493
    PoissonRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 27.5979046604852343
    PoissonRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 1052.9242570153051020
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: PoissonRegressor (poisson_regressor_float.onnx)
    Python  Mean Absolute Error: 27.59790466048524
    MQL5:   Mean Absolute Error: 27.5979082516507788
        
    Testing ONNX double: PoissonRegressor (poisson_regressor_double.onnx)
    Python  Mean Absolute Error: 27.59790466048524
    MQL5:   Mean Absolute Error: 27.5979046604852343

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.25.3. ONNX-представление моделей poisson_regressor_float.onnx и poisson_regressor_double.onnx


    Рис.83. ONNX-представление модели poisson_regressor_float.onnx в Netron

    Рис.83. ONNX-представление модели poisson_regressor_float.onnx в Netron


    Рис.84. ONNX-представление модели poisson_regressor_double.onnx в Netron

    Рис.84. ONNX-представление модели poisson_regressor_double.onnx в Netron



    2.1.26. sklearn.neighbors.RadiusNeighborsRegressor

    RadiusNeighborsRegressor - это метод машинного обучения, который используется для задачи регрессии. Он является вариантом метода ближайших соседей (k-NN) и предназначен для прогнозирования значений целевой переменной на основе ближайших соседей в пространстве признаков. Однако вместо фиксированного числа соседей (как в методе k-NN), RadiusNeighborsRegressor использует фиксированный радиус для определения соседей для каждого примера. Вот ключевые аспекты RadiusNeighborsRegressor:

    Принцип работы RadiusNeighborsRegressor:
    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и целевую переменную (непрерывную).
    2. Задание радиуса: RadiusNeighborsRegressor требует задания фиксированного радиуса, который будет использоваться для определения ближайших соседей для каждого примера в пространстве признаков.
    3. Определение соседей: Для каждого примера определяются все точки данных, находящиеся внутри заданного радиуса. Эти точки становятся соседями данного примера.
    4. Взвешенное усреднение: Для прогноза значения целевой переменной для каждого примера используются значения целевой переменной его соседей. Часто это делается с использованием взвешенного усреднения, где веса зависят от расстояния между примерами.
    5. Предсказание: После обучения модель может быть использована для предсказания значений целевой переменной на новых данных, основываясь на ближайших соседях в пространстве признаков.
    Преимущества RadiusNeighborsRegressor:
    Ограничения RadiusNeighborsRegressor:
    RadiusNeighborsRegressor - это метод машинного обучения, который используется для задачи регрессии на основе метода ближайших соседей с фиксированным радиусом. Этот метод может быть полезным в ситуациях, где количество соседей может меняться в зависимости от радиуса, и в случаях, когда данные содержат выбросы.


    2.1.26.1. Код создания модели RadiusNeighborsRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.neighbors.RadiusNeighborsRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # RadiusNeighborsRegressor.py
    # The code demonstrates the process of training RadiusNeighborsRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com

    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.neighbors import RadiusNeighborsRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "RadiusNeighborsRegressor"
    onnx_model_filename = data_path + "radius_neighbors_regressor"

    # create a RadiusNeighborsRegressor model
    regression_model = RadiusNeighborsRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  RadiusNeighborsRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9999521132921395
    Python  Mean Absolute Error: 0.591458244376554
    Python  Mean Squared Error: 0.6336732353950723
    Python  
    Python  RadiusNeighborsRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\radius_neighbors_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9999999999999971
    Python  Mean Absolute Error: 4.393654615473253e-06
    Python  Mean Squared Error: 3.829042036424747e-11
    Python  R^2 matching decimal places:  4
    Python  MAE matching decimal places:  0
    Python  MSE matching decimal places:  0
    Python  float ONNX model precision:  0
    Python  
    Python  RadiusNeighborsRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\radius_neighbors_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 1.0
    Python  Mean Absolute Error: 0.0
    Python  Mean Squared Error: 0.0
    Python  R^2 matching decimal places:  0
    Python  MAE matching decimal places:  0
    Python  MSE matching decimal places:  0
    Python  double ONNX model precision:  0
    

    Рис.85. Результат работы скрипта RadiusNeighborsRegressor.py (float ONNX)

    Рис.85. Результат работы скрипта RadiusNeighborsRegressor.py (float ONNX)


    2.1.26.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели radius_neighbors_regressor_float.onnx и radius_neighbors_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                     RadiusNeighborsRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "RadiusNeighborsRegressor"
    #define   ONNXFilenameFloat  "radius_neighbors_regressor_float.onnx"
    #define   ONNXFilenameDouble "radius_neighbors_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    RadiusNeighborsRegressor (EURUSD,H1)    Testing ONNX float: RadiusNeighborsRegressor (radius_neighbors_regressor_float.onnx)
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9999999999999971
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 0.0000043936546155
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 0.0000000000382904
    RadiusNeighborsRegressor (EURUSD,H1)    
    RadiusNeighborsRegressor (EURUSD,H1)    Testing ONNX double: RadiusNeighborsRegressor (radius_neighbors_regressor_double.onnx)
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 1.0000000000000000
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 0.0000000000000000
    RadiusNeighborsRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 0.0000000000000000
    

    2.1.26.3. ONNX-представление моделей radius_neighbors_regressor_float.onnx и radius_neighbors_regressor_double.onnx


    Рис.86. ONNX-представление модели radius_neighbors_regressor_float.onnx в Netron

    Рис.86. ONNX-представление модели radius_neighbors_regressor_float.onnx в Netron


    Рис.87. ONNX-представление модели radius_neighbors_regressor_double.onnx в Netron

    Рис.87. ONNX-представление модели radius_neighbors_regressor_double.onnx в Netron



    2.1.27. sklearn.neighbors.KNeighborsRegressor

    KNeighborsRegressor - это метод машинного обучения, используемый для задачи регрессии.

    Он относится к категории алгоритмов на основе ближайших соседей (k-Nearest Neighbors, k-NN), и используется для предсказания числовых значений целевой переменной на основе близости (сходства) между объектами в обучающем наборе данных.

    Принцип работы KNeighborsRegressor:

    1. Входные данные: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Выбор числа соседей (k): Необходимо выбрать количество ближайших соседей (k), которые будут учитываться при прогнозировании. Это число является одним из гиперпараметров модели.
    3. Расчет близости: Для новых данных (точек, для которых необходимо сделать предсказания), рассчитывается расстояние или сходство между этими данными и всеми объектами обучающего набора данных.
    4. Выбор k ближайших соседей: Выбираются k объектов из обучающего набора данных, которые наиболее близки к новым данным.
    5. Прогноз: Для задачи регрессии, предсказание значения целевой переменной для новых данных вычисляется как среднее значение целевых переменных k ближайших соседей.

    Преимущества KNeighborsRegressor:

    Ограничения KNeighborsRegressor:

    KNeighborsRegressor полезен в задачах регрессии, когда важно учитывать окрестность объектов для предсказания целевой переменной. Он может быть особенно полезен в ситуациях, где зависимость между признаками и целевой переменной нелинейна и сложна.


    2.1.27.1. Код создания модели KNeighborsRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.neighbors.KNeighborsRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # KNeighborsRegressor.py
    # The code demonstrates the process of training KNeighborsRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.neighbors import KNeighborsRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "KNeighborsRegressor"
    onnx_model_filename = data_path + "kneighbors_regressor"

    # create a KNeighbors Regressor model
    kneighbors_model = KNeighborsRegressor(n_neighbors=5)

    # fit the model to the data
    kneighbors_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = kneighbors_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(kneighbors_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(kneighbors_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  KNeighborsRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9995599863346534
    Python  Mean Absolute Error: 1.7414210057117578
    Python  Mean Squared Error: 5.822594523532273
    Python  
    Python  KNeighborsRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\kneighbors_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9995599867417418
    Python  Mean Absolute Error: 1.7414195457976402
    Python  Mean Squared Error: 5.8225891366283875
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  4
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  4
    Python  
    Python  KNeighborsRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\kneighbors_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9995599863346534
    Python  Mean Absolute Error: 1.7414210057117583
    Python  Mean Squared Error: 5.822594523532269
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  14
    Python  MSE matching decimal places:  13
    Python  double ONNX model precision:  14
    

    Рис.88. Результат работы скрипта KNeighborsRegressor.py (float ONNX)

    Рис.88. Результат работы скрипта KNeighborsRegressor.py (float ONNX)



    2.1.27.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели kneighbors_regressor_float.onnx и kneighbors_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                          KNeighborsRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "KNeighborsRegressor"
    #define   ONNXFilenameFloat  "kneighbors_regressor_float.onnx"
    #define   ONNXFilenameDouble "kneighbors_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    KNeighborsRegressor (EURUSD,H1) Testing ONNX float: KNeighborsRegressor (kneighbors_regressor_float.onnx)
    KNeighborsRegressor (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9995599860116634
    KNeighborsRegressor (EURUSD,H1) MQL5:   Mean Absolute Error: 1.7414200607817711
    KNeighborsRegressor (EURUSD,H1) MQL5:   Mean Squared Error: 5.8225987975798184
    KNeighborsRegressor (EURUSD,H1) 
    KNeighborsRegressor (EURUSD,H1) Testing ONNX double: KNeighborsRegressor (kneighbors_regressor_double.onnx)
    KNeighborsRegressor (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9995599863346534
    KNeighborsRegressor (EURUSD,H1) MQL5:   Mean Absolute Error: 1.7414210057117601
    KNeighborsRegressor (EURUSD,H1) MQL5:   Mean Squared Error: 5.8225945235322705
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: KNeighborsRegressor (kneighbors_regressor_float.onnx)
    Python  Mean Absolute Error: 1.7414210057117578
    MQL5:   Mean Absolute Error: 1.7414200607817711
     
    Testing ONNX double: KNeighborsRegressor (kneighbors_regressor_double.onnx)
    Python  Mean Absolute Error: 1.7414210057117578
    MQL5:   Mean Absolute Error: 1.7414210057117601

    Точность MAE ONNX float: 5 знаков после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.27.3. ONNX-представление моделей kneighbors_regressor_float.onnx и kneighbors_regressor_double.onnx


    Рис.89. ONNX-представление модели kneighbors_regressor_float.onnx в Netron

    Рис.89. ONNX-представление модели kneighbors_regressor_float.onnx в Netron


    Рис.90. ONNX-представление модели kneighbors_regressor_double.onnx в Netron

    Рис.90. ONNX-представление модели kneighbors_regressor_double.onnx в Netron



    2.1.28. sklearn.gaussian_process.GaussianProcessRegressor

    GaussianProcessRegressor (регрессия на основе гауссовских процессов) - это метод машинного обучения, используемый для задачи регрессии, который позволяет моделировать неопределенность в предсказаниях.

    Gaussian Process (GP) является мощным инструментом байесовского машинного обучения и используется для моделирования сложных функций и предсказания значений целевой переменной с учетом неопределенности.

    Принцип работы GaussianProcessRegressor:

    1. Ввод данных: Начинаем с исходного набора данных, включая признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Моделирование гауссовского процесса: В методе Gaussian Process используется гауссовский процесс, который представляет собой коллекцию случайных переменных, описываемых вероятностным распределением Гаусса (нормальным распределением). GP моделирует не только средние значения для каждой точки данных, но и ковариацию (или похожесть) между этими точками.
    3. Выбор ковариационной функции: Одним из важных аспектов GP является выбор ковариационной функции (или ядра), которая определяет, какие точки данных взаимосвязаны между собой и насколько сильно. Различные ковариационные функции могут использоваться в зависимости от характера данных и задачи.
    4. Обучение модели: GaussianProcessRegressor обучает GP с использованием обучающих данных. Во время обучения модель настраивает параметры ковариационной функции и оценивает неопределенность в предсказаниях.
    5. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных. Важной особенностью GP является то, что она предсказывает не только среднее значение, но и доверительный интервал, который оценивает степень уверенности в предсказаниях.

    Преимущества GaussianProcessRegressor:

    Ограничения GaussianProcessRegressor:

    GaussianProcessRegressor полезен в задачах регрессии, где важно моделировать неопределенность и обеспечивать надежные предсказания. Этот метод часто используется в байесовском машинном обучении и мета-анализе.


    2.1.28.1. Код создания модели GaussianProcessRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.gaussian_process.GaussianProcessRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # GaussianProcessRegressor.py
    # The code demonstrates the process of training GaussianProcessRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings

        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.gaussian_process import GaussianProcessRegressor
    from sklearn.gaussian_process.kernels import RBF
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "GaussianProcessRegressor"
    onnx_model_filename = data_path + "gaussian_process_regressor"

    # create a GaussianProcessRegressor model
    kernel = 1.0 * RBF()
    gp_model = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=10)

    # fit the model to the data
    gp_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = gp_model.predict(X, return_std=False)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(gp_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("ONNX: MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(gp_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  GaussianProcessRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 1.0
    Python  Mean Absolute Error: 3.504041501400934e-13
    Python  Mean Squared Error: 1.6396606443650807e-25
    Python  
    Python  GaussianProcessRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gaussian_process_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: GPmean, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9999999999999936
    Python  Mean Absolute Error: 6.454076974495848e-06
    Python  Mean Squared Error: 8.493606782250733e-11
    Python  R^2 matching decimal places:  0
    Python  MAE matching decimal places:  0
    Python  MSE matching decimal places:  0
    Python  float ONNX model precision:  0
    Python  
    Python  GaussianProcessRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gaussian_process_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: GPmean, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 1.0
    Python  Mean Absolute Error: 3.504041501400934e-13
    Python  Mean Squared Error: 1.6396606443650807e-25
    Python  R^2 matching decimal places:  1
    Python  MAE matching decimal places:  19
    Python  MSE matching decimal places:  20
    Python  double ONNX model precision:  19
    

    Рис.91. Результат работы скрипта GaussianProcessRegressor.py (float ONNX)

    Рис.91. Результат работы скрипта GaussianProcessRegressor.py (float ONNX)


    2.1.28.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели gaussian_process_regressor_float.onnx и gaussian_process_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                     GaussianProcessRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "GaussianProcessRegressor"
    #define   ONNXFilenameFloat  "gaussian_process_regressor_float.onnx"
    #define   ONNXFilenameDouble "gaussian_process_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    GaussianProcessRegressor (EURUSD,H1)    Testing ONNX float: GaussianProcessRegressor (gaussian_process_regressor_float.onnx)
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9999999999999936
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 0.0000064540769745
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 0.0000000000849361
    GaussianProcessRegressor (EURUSD,H1)    
    GaussianProcessRegressor (EURUSD,H1)    Testing ONNX double: GaussianProcessRegressor (gaussian_process_regressor_double.onnx)
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 1.0000000000000000
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 0.0000000000003504
    GaussianProcessRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 0.0000000000000000
    



    2.1.28.3. ONNX-представление моделей gaussian_process_regressor_float.onnx и gaussian_process_regressor_double.onnx


    Рис.92. ONNX-представление модели gaussian_process_regressor_float.onnx в Netron

    Рис.92. ONNX-представление модели gaussian_process_regressor_float.onnx в Netron


    Рис.93. ONNX-представление модели gaussian_process_regressor_double.onnx в Netron

    Рис.93. ONNX-представление модели gaussian_process_regressor_double.onnx в Netron




    2.1.29. sklearn.linear_model.GammaRegressor

    GammaRegressor - это метод машинного обучения, предназначенный для задачи регрессии, в которой целевая переменная следует гамма-распределению.

    Гамма-распределение является вероятностным распределением, используемым для моделирования положительных, непрерывных случайных величин. Этот метод позволяет моделировать и предсказывать положительные числовые значения, такие как стоимость, время или доли.

    Принцип работы GammaRegressor:

    1. Входные данные: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной, которая следует гамма-распределению.
    2. Выбор функции потерь: GammaRegressor использует функцию потерь, которая соответствует гамма-распределению и учитывает особенности этого распределения. Это позволяет моделировать данные с учетом неотрицательности и правого наклона гамма-распределения.
    3. Обучение модели: Модель обучается на данных с использованием выбранной функции потерь. Во время обучения она настраивает параметры модели таким образом, чтобы минимизировать функцию потерь.
    4. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных.

    Преимущества GammaRegressor:

    Ограничения GammaRegressor:

    GammaRegressor полезен в задачах, где необходимо моделировать и предсказывать положительные числовые значения, соответствующие гамма-распределению.


    2.1.29.1. Код создания модели GammaRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.GammaRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # GammaRegressor.py
    # The code demonstrates the process of training GammaRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import GammaRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 10+4*X + 10*np.sin(X*0.5)

    model_name = "GammaRegressor"
    onnx_model_filename = data_path + "gamma_regressor"

    # create a Gamma Regressor model
    regression_model = GammaRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  GammaRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.7963797339354436
    Python  Mean Absolute Error: 37.266200319422815
    Python  Mean Squared Error: 2694.457784927322
    Python  
    Python  GammaRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gamma_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.7963795030042045
    Python  Mean Absolute Error: 37.266211754095956
    Python  Mean Squared Error: 2694.4608407846144
    Python  R^2 matching decimal places:  6
    Python  MAE matching decimal places:  4
    Python  MSE matching decimal places:  1
    Python  float ONNX model precision:  4
    Python  
    Python  GammaRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gamma_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.7963797339354436
    Python  Mean Absolute Error: 37.266200319422815
    Python  Mean Squared Error: 2694.457784927322
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  15
    Python  MSE matching decimal places:  12
    Python  double ONNX model precision:  15
    


     Рис.94. Результат работы скрипта GammaRegressor.py (float ONNX)

    Рис.94. Результат работы скрипта GammaRegressor.py (float ONNX)


    2.1.29.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели gamma_regressor_float.onnx и gamma_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                               GammaRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "GammaRegressor"
    #define   ONNXFilenameFloat  "gamma_regressor_float.onnx"
    #define   ONNXFilenameDouble "gamma_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(10+4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    GammaRegressor (EURUSD,H1)      Testing ONNX float: GammaRegressor (gamma_regressor_float.onnx)
    GammaRegressor (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.7963795030042045
    GammaRegressor (EURUSD,H1)      MQL5:   Mean Absolute Error: 37.2662117540959628
    GammaRegressor (EURUSD,H1)      MQL5:   Mean Squared Error: 2694.4608407846144473
    GammaRegressor (EURUSD,H1)      
    GammaRegressor (EURUSD,H1)      Testing ONNX double: GammaRegressor (gamma_regressor_double.onnx)
    GammaRegressor (EURUSD,H1)      MQL5:   R-Squared (Coefficient of determination): 0.7963797339354435
    GammaRegressor (EURUSD,H1)      MQL5:   Mean Absolute Error: 37.2662003194228220
    GammaRegressor (EURUSD,H1)      MQL5:   Mean Squared Error: 2694.4577849273218817
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: GammaRegressor (gamma_regressor_float.onnx)
    Python  Mean Absolute Error: 37.266200319422815
    MQL5:   Mean Absolute Error: 37.2662117540959628
          
    Testing ONNX double: GammaRegressor (gamma_regressor_double.onnx)
    Python  Mean Absolute Error: 37.266200319422815
    MQL5:   Mean Absolute Error: 37.2662003194228220

    Точность MAE ONNX float: 4 знака после запятой, точность MAE ONNX double 13 знаков после запятой.


    2.1.29.3. ONNX-представление моделей gamma_regressor_float.onnx и gamma_regressor_double.onnx


    Рис.95. ONNX-представление модели gamma_regressor_float.onnx в Netron

    Рис.95. ONNX-представление модели gamma_regressor_float.onnx в Netron


    Рис.96. ONNX-представление модели gamma_regressor_double.onnx в Netron
    Рис.96. ONNX-представление модели gamma_regressor_double.onnx в Netron



    2.1.30. sklearn.linear_model.SGDRegressor

    SGDRegressor представляет собой метод регрессии, который использует стохастический градиентный спуск (Stochastic Gradient Descent, SGD) для обучения регрессионной модели. Он является частью семейства линейных моделей и может использоваться для решения задач регрессии. Важными характеристиками SGDRegressor являются эффективность и способность работать с большими объемами данных.

    Принцип работы SGDRegressor:

    1. Линейная регрессия: SGDRegressor как и Ridge и Lasso, стремится найти линейную зависимость между независимыми переменными (признаками) и целевой переменной в задаче регрессии.
    2. Стохастический градиентный спуск: Основой SGDRegressor является стохастический градиентный спуск. Вместо вычисления градиента на всем обучающем наборе данных, он обновляет модель на основе случайно выбранных мини-пакетов (batch) данных. Это позволяет обучать модель эффективно и работать с большими данными.
    3. Регуляризация: SGDRegressor поддерживает L1 и L2 регуляризацию (Lasso и Ridge). Это позволяет контролировать переобучение и улучшать устойчивость модели.
    4. Гиперпараметры: Как и в случае Ridge и Lasso, в SGDRegressor можно настраивать гиперпараметры, такие как параметр регуляризации (α, альфа) и тип регуляризации.

    Преимущества SGDRegressor:

    Ограничения SGDRegressor:


    SGDRegressor - это метод регрессии, который использует стохастический градиентный спуск для обучения регрессионной модели. Он эффективен и способен работать с большими данными, а также поддерживает регуляризацию для управления переобучением.


    2.1.30.1. Код создания модели SGDRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.SGDRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # SGDRegressor2.py
    # The code demonstrates the process of training SGDRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.linear_model import SGDRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,10,0.1).reshape(-1,1)
    y = 4*X + np.sin(X*10)

    model_name = "SGDRegressor"
    onnx_model_filename = data_path + "sgd_regressor"

    # create an SGDRegressor model
    regression_model = SGDRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  SGDRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9961197872743282
    Python  Mean Absolute Error: 0.6405924406136998
    Python  Mean Squared Error: 0.5169867345998348
    Python  
    Python  SGDRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\sgd_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9961197876338647
    Python  Mean Absolute Error: 0.6405924014799271
    Python  Mean Squared Error: 0.5169866866963753
    Python  R^2 matching decimal places:  9
    Python  MAE matching decimal places:  7
    Python  MSE matching decimal places:  6
    Python  float ONNX model precision:  7
    Python  
    Python  SGDRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\sgd_regressor_double.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: double_input, Data Type: tensor(double), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(double), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9961197872743282
    Python  Mean Absolute Error: 0.6405924406136998
    Python  Mean Squared Error: 0.5169867345998348
    Python  R^2 matching decimal places:  16
    Python  MAE matching decimal places:  16
    Python  MSE matching decimal places:  16
    Python  double ONNX model precision:  16
    


    Рис.97. Результат работы скрипта SGDRegressor.py (float ONNX)

    Рис.97. Результат работы скрипта SGDRegressor.py (float ONNX)


    2.1.30.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели sgd_regressor_float.onnx и sgd_rgressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                 SGDRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "SGDRegressor"
    #define   ONNXFilenameFloat  "sgd_regressor_float.onnx"
    #define   ONNXFilenameDouble "sgd_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i*0.1;
          y[i]=(double)(4*x[i] + sin(x[i]*10));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    SGDRegressor (EURUSD,H1)        Testing ONNX float: SGDRegressor (sgd_regressor_float.onnx)
    SGDRegressor (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9961197876338647
    SGDRegressor (EURUSD,H1)        MQL5:   Mean Absolute Error: 0.6405924014799272
    SGDRegressor (EURUSD,H1)        MQL5:   Mean Squared Error: 0.5169866866963754
    SGDRegressor (EURUSD,H1)        
    SGDRegressor (EURUSD,H1)        Testing ONNX double: SGDRegressor (sgd_regressor_double.onnx)
    SGDRegressor (EURUSD,H1)        MQL5:   R-Squared (Coefficient of determination): 0.9961197872743282
    SGDRegressor (EURUSD,H1)        MQL5:   Mean Absolute Error: 0.6405924406136998
    SGDRegressor (EURUSD,H1)        MQL5:   Mean Squared Error: 0.5169867345998348
    

    Сравнение с оригинальной моделью:

    Testing ONNX float: SGDRegressor (sgd_regressor_float.onnx)
    Python  Mean Absolute Error: 0.6405924406136998
    MQL5:   Mean Absolute Error: 0.6405924014799272
            
    Testing ONNX double: SGDRegressor (sgd_regressor_double.onnx)
    Python  Mean Absolute Error: 0.6405924406136998
    MQL5:   Mean Absolute Error: 0.6405924406136998

    Точность MAE ONNX float: 7 знаков после запятой, точность MAE ONNX double 16 знаков после запятой.


    2.1.30.3. ONNX-представление моделей sgd_regressor_float.onnx и sgd_regressor_double.onnx




    Рис.98. ONNX-представление модели sgd_regressor_float.onnx в Netron

    Рис.98. ONNX-представление модели sgd_regressor_float.onnx в Netron


    Рис.99. ONNX-представление модели sgd_rgressor_double.onnx в Netron

    Рис.99. ONNX-представление модели sgd_rgressor_double.onnx в Netron


    2.2. Регрессионные модели библиотеки Scikit-learn, которые конвертируются только во float ONNX-модели

    В этом разделе рассматриваются модели, работа с которыми возможна только с точностью float. Конвертация в ONNX с точностью double приводит к ошибкам, связанным с ограничениями ONNX-операторов подмножества ai.onnx.ml.


    2.2.1. sklearn.linear_model.AdaBoostRegressor

    AdaBoostRegressor - это метод машинного обучения, который используется для регрессии, то есть для прогнозирования числовых значений (например, цены на недвижимость, объемы продаж и т. д.).

    Этот метод является вариацией алгоритма AdaBoost (Adaptive Boosting), который был изначально разработан для задач классификации.

    Принцип работы AdaBoostRegressor:

    1. Исходный набор данных: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие целевые переменные (зависимые переменные, которые мы пытаемся предсказать).
    2. Инициализация весов: В начале каждая точка данных (наблюдение) имеет равные веса, и модель строится на основе этого взвешенного набора данных.
    3. Обучение слабых учеников: AdaBoostRegressor строит несколько слабых регрессионных моделей (например, решающие деревья), которые пытаются предсказать целевую переменную. Эти модели называются "слабыми учителями". Каждый слабый учитель обучается на данных, учитывая веса каждого наблюдения.
    4. Выбор весов для слабых учителей: AdaBoostRegressor рассчитывает вес для каждого слабого учителя на основе того, насколько хорошо этот учитель справился с предсказаниями. Учителя, которые делают более точные предсказания, получают большие веса, и наоборот.
    5. Обновление весов наблюдений: Веса наблюдений обновляются так, чтобы наблюдения, которые были неправильно предсказаны ранее, получали больший вес, таким образом, увеличивая их важность для следующей модели.
    6. Финальное предсказание: AdaBoostRegressor комбинирует предсказания всех слабых учителей, присваивая им веса в зависимости от их производительности. Это финальное предсказание модели.

    Преимущества AdaBoostRegressor:

    Ограничения AdaBoostRegressor:

    AdaBoostRegressor - это полезный метод машинного обучения, который может быть применен в различных задачах регрессии, особенно в случаях, когда данные содержат сложные зависимости.


    2.2.1.1. Код создания модели AdaBoostRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.AdaBoostRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # AdaBoostRegressor.py
    # The code demonstrates the process of training AdaBoostRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import AdaBoostRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "AdaBoostRegressor"
    onnx_model_filename = data_path + "adaboost_regressor"

    # create an AdaBoostRegressor model
    regression_model = AdaBoostRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  AdaBoostRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9991257208809748
    Python  Mean Absolute Error: 2.3678022748065457
    Python  Mean Squared Error: 11.569124350863143
    Python  
    Python  AdaBoostRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\adaboost_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9991257199849699
    Python  Mean Absolute Error: 2.36780399225718
    Python  Mean Squared Error: 11.569136207480646
    Python  R^2 matching decimal places:  7
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  5
    Python  
    Python  AdaBoostRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\adaboost_regressor_double.onnx
    

    Здесь модель была экспортирована в ONNX-модели для float и double, ONNX-модель float исполнилась успешно, а с моделью double возникли проблемы (ошибки во вкладке Errors):

    AdaBoostRegressor.py started    AdaBoostRegressor.py    1       1
    Traceback (most recent call last):      AdaBoostRegressor.py    1       1
        onnx_session = ort.InferenceSession(onnx_filename)  AdaBoostRegressor.py    159     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\adaboost_regressor_double.onnx failed:Type Error:       onnxruntime_inference_collection.py     424     1
    AdaBoostRegressor.py finished in 3207 ms                5       1
    

    Рис.100. Результат работы скрипта AdaBoostRegressor.py (float ONNX)

    Рис.100. Результат работы скрипта AdaBoostRegressor.py (float ONNX)


    2.2.1.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели adaboost_regressor_float.onnx и adaboost_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                            AdaBoostRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "AdaBoostRegressor"
    #define   ONNXFilenameFloat  "adaboost_regressor_float.onnx"
    #define   ONNXFilenameDouble "adaboost_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    AdaBoostRegressor (EURUSD,H1)   
    AdaBoostRegressor (EURUSD,H1)   Testing ONNX float: AdaBoostRegressor (adaboost_regressor_float.onnx)
    AdaBoostRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9991257199849699
    AdaBoostRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 2.3678039922571803
    AdaBoostRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 11.5691362074806463
    AdaBoostRegressor (EURUSD,H1)   
    AdaBoostRegressor (EURUSD,H1)   Testing ONNX double: AdaBoostRegressor (adaboost_regressor_double.onnx)
    AdaBoostRegressor (EURUSD,H1)   ONNX: cannot create session (OrtStatus: 1 'Type Error: Type parameter (T) of Optype (Mul) bound to different types (tensor(float) and tensor(double) in node (Mul).'), inspect code 'Scripts\Regression\AdaBoostRegressor.mq5' (133:16)
    AdaBoostRegressor (EURUSD,H1)   model_name=AdaBoostRegressor OnnxCreate error 5800
    

    Здесь мы видим, что ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.1.3. ONNX-представление моделей adaboost_regressor_float.onnx и adaboost_regressor_double.onnx

    Рис.101. ONNX-представление модели adaboost_regressor_float.onnx в Netron

    Рис.101. ONNX-представление модели adaboost_regressor_float.onnx в Netron


    Рис.102. ONNX-представление модели adaboost_regressor_double.onnx в Netron

    Рис.102. ONNX-представление модели adaboost_regressor_double.onnx в Netron



    2.2.2. sklearn.linear_model.BaggingRegressor

    BaggingRegressor - это метод машинного обучения, который используется для задачи регрессии.

    Он представляет собой ансамбльный метод, основанный на идее "бэггинга" (Bootstrap Aggregating), который состоит в построении нескольких базовых регрессионных моделей и комбинировании их предсказаний для получения более стабильного и точного результата.

    Принцип работы BaggingRegressor:

    1. Исходный набор данных: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие целевые переменные (зависимые переменные, которые мы пытаемся предсказать).
    2. Генерация подвыборок: BaggingRegressor случайным образом создает несколько подвыборок (выборки с возвращением) из исходных данных. Каждая подвыборка содержит случайный набор наблюдений из исходных данных.
    3. Обучение базовых регрессионных моделей: Для каждой подвыборки BaggingRegressor строит отдельную базовую регрессионную модель (например, дерево решений, случайный лес, регрессионная линейная модель и др.).
    4. Прогнозы базовых моделей: Каждая базовая модель используется для предсказания целевой переменной на основе соответствующей подвыборки.
    5. Усреднение или комбинирование: BaggingRegressor усредняет или комбинирует предсказания всех базовых моделей, чтобы получить окончательное регрессионное предсказание.

    Преимущества BaggingRegressor:

    Ограничения BaggingRegressor:


    BaggingRegressor - это мощный метод машинного обучения, который может быть полезен в задачах регрессии, особенно при наличии шумовых данных и потребности в улучшении стабильности предсказаний.


    2.2.2.1. Код создания модели BaggingRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.BaggingRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # BaggingRegressor.py
    # The code demonstrates the process of training BaggingRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import BaggingRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from
    skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "BaggingRegressor"
    onnx_model_filename = data_path + "bagging_regressor"

    # create a Bagging Regressor model
    regression_model = BaggingRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType
    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX

    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  
    Python  BaggingRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9998128324923137
    Python  Mean Absolute Error: 1.0257279210387649
    Python  Mean Squared Error: 2.4767424083953005
    Python  
    Python  BaggingRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\bagging_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9998128317934672
    Python  Mean Absolute Error: 1.0257282792130034
    Python  Mean Squared Error: 2.4767516560614187
    Python  R^2 matching decimal laces:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  4
    Python  float ONNX model precision:  5
    Python  
    Python  BaggingRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\bagging_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    BaggingRegressor.py started     BaggingRegressor.py     1       1
    Traceback (most recent call last):      BaggingRegressor.py     1       1
        onnx_session = ort.InferenceSession(onnx_filename)  BaggingRegressor.py     161     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\bagging_regressor_double.onnx failed:Type Error: T      onnxruntime_inference_collection.py     424     1
    BaggingRegressor.py finished in 3173 ms         5       1
    

    Рис.103. Результат работы скрипта BaggingRegressor.py (float ONNX)

    Рис.103. Результат работы скрипта BaggingRegressor.py (float ONNX)


    2.2.2.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели bagging_regressor_float.onnx и bagging_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                             BaggingRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "BaggingRegressor"
    #define   ONNXFilenameFloat  "bagging_regressor_float.onnx"
    #define   ONNXFilenameDouble "bagging_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    BaggingRegressor (EURUSD,H1)    Testing ONNX float: BaggingRegressor (bagging_regressor_float.onnx)
    BaggingRegressor (EURUSD,H1)    MQL5:   R-Squared (Coefficient of determination): 0.9998128317934672
    BaggingRegressor (EURUSD,H1)    MQL5:   Mean Absolute Error: 1.0257282792130034
    BaggingRegressor (EURUSD,H1)    MQL5:   Mean Squared Error: 2.4767516560614196
    BaggingRegressor (EURUSD,H1)    
    BaggingRegressor (EURUSD,H1)    Testing ONNX double: BaggingRegressor (bagging_regressor_double.onnx)
    BaggingRegressor (EURUSD,H1)    ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (ReduceMean) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\BaggingRegressor.mq5' (133:16)
    BaggingRegressor (EURUSD,H1)    model_name=BaggingRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.2.3. ONNX-представление моделей bagging_regressor_float.onnx и bagging_regressor_double.onnx



    Рис.104. ONNX-представление модели bagging_regressor_float.onnx в Netron

    Рис.104. ONNX-представление модели bagging_regressor_float.onnx в Netron


    Рис.105. ONNX-представление модели bagging_regressor_double.onnx в Netron

    Рис.105. ONNX-представление модели bagging_regressor_double.onnx в Netron




    2.2.3. sklearn.linear_model.DecisionTreeRegressor

    DecisionTreeRegressor - это метод машинного обучения, используемый для задачи регрессии, то есть для прогнозирования числовых значений целевой переменной на основе набора признаков (независимых переменных).

    Этот метод основан на построении деревьев решений, которые разбивают пространство признаков на интервалы и предсказывают значение целевой переменной для каждого интервала.

    Принцип работы DecisionTreeRegressor:

    1. Начало построения: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Выбор признака и разделение: Дерево решений выбирает признак и пороговое значение, которое разделяет данные на две или более подгруппы. Разделение выполняется таким образом, чтобы минимизировать среднеквадратичную ошибку (среднеквадратическое отклонение между предсказанными и фактическими значениями целевой переменной) в каждой подгруппе.
    3. Рекурсивное построение: Процесс выбора признака и разделения повторяется для каждой подгруппы, создавая поддеревья. Этот процесс выполняется рекурсивно до тех пор, пока не выполнены определенные критерии остановки, такие как максимальная глубина дерева или минимальное количество образцов в узле.
    4. Листовые узлы: Когда критерии остановки выполнены, создаются листовые узлы, которые предсказывают числовые значения целевой переменной для образцов, попавших в данный листовой узел.
    5. Предсказание: Для новых данных дерево решений применяется, и новые наблюдения проходят через дерево, пока не достигнут листовой узел, который предсказывает числовое значение целевой переменной.

    Преимущества DecisionTreeRegressor:

    Ограничения DecisionTreeRegressor:

    DecisionTreeRegressor - это полезный метод для задач регрессии, особенно когда важно понимать логику принятия решений моделью и визуализировать процесс.


    2.2.3.1. Код создания модели DecisionTreeRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.linear_model.DecisionTreeRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # DecisionTreeRegressor.py
    # The code demonstrates the process of training DecisionTreeRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.tree import DecisionTreeRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "DecisionTreeRegressor"
    onnx_model_filename = data_path + "decision_tree_regressor"

    # create a Decision Tree Regressor model
    regression_model = DecisionTreeRegressor()

    # fit the model to the data
    regression_model.fit(X, y)

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType
    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions

    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  DecisionTreeRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 1.0
    Python  Mean Absolute Error: 0.0
    Python  Mean Squared Error: 0.0
    Python  
    Python  DecisionTreeRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\decision_tree_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9999999999999971
    Python  Mean Absolute Error: 4.393654615473253e-06
    Python  Mean Squared Error: 3.829042036424747e-11
    Python  R^2 matching decimal places:  0
    Python  MAE matching decimal places:  0
    Python  MSE matching decimal places:  0
    Python  float ONNX model precision:  0
    Python  
    Python  DecisionTreeRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\decision_tree_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    DecisionTreeRegressor.py started        DecisionTreeRegressor.py        1       1
    Traceback (most recent call last):      DecisionTreeRegressor.py        1       1
        onnx_session = ort.InferenceSession(onnx_filename)  DecisionTreeRegressor.py        160     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\decision_tree_regressor_double.onnx failed:Type Er      onnxruntime_inference_collection.py     424     1
    DecisionTreeRegressor.py finished in 2957 ms            5       1
    


    Рис.106. Результат работы скрипта DecisionTreeRegressor.py (float ONNX)

    Рис.106. Результат работы скрипта DecisionTreeRegressor.py (float ONNX)


    2.2.3.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели decision_tree_regressor_float.onnx и decision_tree_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                        DecisionTreeRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "DecisionTreeRegressor"
    #define   ONNXFilenameFloat  "decision_tree_regressor_float.onnx"
    #define   ONNXFilenameDouble "decision_tree_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    DecisionTreeRegressor (EURUSD,H1)       Testing ONNX float: DecisionTreeRegressor (decision_tree_regressor_float.onnx)
    DecisionTreeRegressor (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9999999999999971
    DecisionTreeRegressor (EURUSD,H1)       MQL5:   Mean Absolute Error: 0.0000043936546155
    DecisionTreeRegressor (EURUSD,H1)       MQL5:   Mean Squared Error: 0.0000000000382904
    DecisionTreeRegressor (EURUSD,H1)       
    DecisionTreeRegressor (EURUSD,H1)       Testing ONNX double: DecisionTreeRegressor (decision_tree_regressor_double.onnx)
    DecisionTreeRegressor (EURUSD,H1)       ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\DecisionTreeRegressor.mq5' (133:16)
    DecisionTreeRegressor (EURUSD,H1)       model_name=DecisionTreeRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.3.3. ONNX-представление моделей decision_tree_regressor_float.onnx и decision_tree_regressor_double.onnx


    Рис.107. ONNX-представление модели decision_tree_regressor_float.onnx в Netron

    Рис.107. ONNX-представление модели decision_tree_regressor_float.onnx в Netron


    Рис.108. ONNX-представление модели decision_tree_regressor_double.onnx в Netron

    Рис.108. ONNX-представление модели decision_tree_regressor_double.onnx в Netron



    2.2.4. sklearn.tree.ExtraTreeRegressor

    ExtraTreeRegressor, или Extremely Randomized Trees Regressor, представляет собой регрессионный ансамбльный метод, который основан на решающих деревьях.

    Этот метод является вариацией случайных лесов (Random Forest) и отличается тем, что вместо выбора наилучшего разделения для каждого узла дерева, он использует случайные разделения для каждого узла. Это делает его более случайным и быстрым, что может быть полезным в некоторых ситуациях.

    Принцип работы ExtraTreeRegressor:

    1. Начало построения: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Случайность при разделениях: В отличие от обычных решающих деревьев, где выбирается наилучшее разделение, ExtraTreeRegressor использует случайные пороговые значения для разделения узлов дерева. Это делает процесс разделения более случайным и менее склонным к переобучению.
    3. Строительство дерева: Дерево строится путем разделения узлов на основе случайных признаков и пороговых значений. Этот процесс продолжается до достижения критериев остановки, таких как максимальная глубина дерева или минимальное количество образцов в узле.
    4. Ансамбль деревьев: ExtraTreeRegressor строит несколько таких случайных деревьев, их количество контролируется гиперпараметром "n_estimators".
    5. Предсказание: Для предсказания целевой переменной для новых данных, ExtraTreeRegressor просто усредняет предсказания всех деревьев в ансамбле.

    Преимущества ExtraTreeRegressor:

    Ограничения ExtraTreeRegressor:

    ExtraTreeRegressor может быть полезным методом для регрессии в ситуациях, где требуется снижение переобучения и быстрое обучение.


    2.2.4.1. Код создания модели ExtraTreeRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.tree.ExtraTreeRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # ExtraTreeRegressor.py
    # The code demonstrates the process of training ExtraTreeRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.tree import ExtraTreeRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "ExtraTreeRegressor"
    onnx_model_filename = data_path + "extra_tree_regressor"

    # create an ExtraTreeRegressor model
    regression_model = ExtraTreeRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression data
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    2023.10.30 14:40:57.665 Python  ExtraTreeRegressor Original model (double)
    2023.10.30 14:40:57.665 Python  R-squared (Coefficient of determination): 1.0
    2023.10.30 14:40:57.665 Python  Mean Absolute Error: 0.0
    2023.10.30 14:40:57.665 Python  Mean Squared Error: 0.0
    2023.10.30 14:40:57.681 Python  
    2023.10.30 14:40:57.681 Python  ExtraTreeRegressor ONNX model (float)
    2023.10.30 14:40:57.681 Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_tree_regressor_float.onnx
    2023.10.30 14:40:57.681 Python  Information about input tensors in ONNX:
    2023.10.30 14:40:57.681 Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    2023.10.30 14:40:57.681 Python  Information about output tensors in ONNX:
    2023.10.30 14:40:57.681 Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    2023.10.30 14:40:57.681 Python  R-squared (Coefficient of determination) 0.9999999999999971
    2023.10.30 14:40:57.681 Python  Mean Absolute Error: 4.393654615473253e-06
    2023.10.30 14:40:57.681 Python  Mean Squared Error: 3.829042036424747e-11
    2023.10.30 14:40:57.681 Python  R^2 matching decimal places:  0
    2023.10.30 14:40:57.681 Python  MAE matching decimal places:  0
    2023.10.30 14:40:57.681 Python  MSE matching decimal places:  0
    2023.10.30 14:40:57.681 Python  float ONNX model precision:  0
    2023.10.30 14:40:58.011 Python  
    2023.10.30 14:40:58.011 Python  ExtraTreeRegressor ONNX model (double)
    2023.10.30 14:40:58.011 Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_tree_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    ExtraTreeRegressor.py started   ExtraTreeRegressor.py   1       1
    Traceback (most recent call last):      ExtraTreeRegressor.py   1       1
        onnx_session = ort.InferenceSession(onnx_filename)  ExtraTreeRegressor.py   159     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_tree_regressor_double.onnx failed:Type Error      onnxruntime_inference_collection.py     424     1
    ExtraTreeRegressor.py finished in 2980 ms               5       1
    

    Рис.109. Результат работы скрипта ExtraTreeRegressor.py (float ONNX)

    Рис.109. Результат работы скрипта ExtraTreeRegressor.py (float ONNX)


    2.2.4.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели extra_tree_regressor_float.onnx и extra_tree_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                           ExtraTreeRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "ExtraTreeRegressor"
    #define   ONNXFilenameFloat  "extra_tree_regressor_float.onnx"
    #define   ONNXFilenameDouble "extra_tree_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    ExtraTreeRegressor (EURUSD,H1)  Testing ONNX float: ExtraTreeRegressor (extra_tree_regressor_float.onnx)
    ExtraTreeRegressor (EURUSD,H1)  MQL5:   R-Squared (Coefficient of determination): 0.9999999999999971
    ExtraTreeRegressor (EURUSD,H1)  MQL5:   Mean Absolute Error: 0.0000043936546155
    ExtraTreeRegressor (EURUSD,H1)  MQL5:   Mean Squared Error: 0.0000000000382904
    ExtraTreeRegressor (EURUSD,H1)  
    ExtraTreeRegressor (EURUSD,H1)  Testing ONNX double: ExtraTreeRegressor (extra_tree_regressor_double.onnx)
    ExtraTreeRegressor (EURUSD,H1)  ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\ExtraTreeRegressor.mq5' (133:16)
    ExtraTreeRegressor (EURUSD,H1)  model_name=ExtraTreeRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.4.3. ONNX-представление моделей extra_tree_regressor_float.onnx и extra_tree_regressor_double.onnx


    Рис.110. ONNX-представление модели extra_tree_regressor_float.onnx в Netron

    Рис.110. ONNX-представление модели extra_tree_regressor_float.onnx в Netron


    Рис.111. ONNX-представление модели extra_tree_regressor_double.onnx в Netron

    Рис.111. ONNX-представление модели extra_tree_regressor_double.onnx в Netron


    2.2.5. sklearn.ensemble.ExtraTreesRegressor   

    ExtraTreesRegressor (Extremely Randomized Trees Regressor) - это метод машинного обучения, который представляет собой вариацию случайных лесов (Random Forests) для задачи регрессии.

    Этот метод использует ансамбль деревьев решений для предсказания числовых значений целевой переменной на основе набора признаков.

    Принцип работы ExtraTreesRegressor:

    1. Начало построения: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Случайность при разделениях: В отличие от обычных решающих деревьев, где для разделения узлов выбирается наилучший разделитель, ExtraTreesRegressor использует случайные пороговые значения для разделения узлов дерева. Это делает процесс разделения более случайным и менее склонным к переобучению.
    3. Строительство деревьев: ExtraTreesRegressor строит несколько деревьев решений в ансамбле. Количество деревьев управляется гиперпараметром "n_estimators". Каждое дерево обучается на случайной подвыборке данных (с повторениями) и случайных подмножествах признаков.
    4. Прогноз: Для предсказания целевой переменной для новых данных ExtraTreesRegressor агрегирует предсказания всех деревьев в ансамбле (обычно путем усреднения).

    Преимущества ExtraTreesRegressor:

    Ограничения ExtraTreesRegressor:

    ExtraTreesRegressor может быть полезным методом для регрессии в различных задачах, особенно когда необходимо снизить переобучение и улучшить обобщающую способность модели.


    2.2.5.1. Код создания модели ExtraTreesRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.ensemble.ExtraTreesRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # ExtraTreesRegressor.py
    # The code demonstrates the process of training ExtraTreesRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import ExtraTreesRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "ExtraTreesRegressor"
    onnx_model_filename = data_path + "extra_trees_regressor"

    # create an Extra Trees Regressor model
    regression_model = ExtraTreesRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  ExtraTreesRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 1.0
    Python  Mean Absolute Error: 2.2302160118670144e-13
    Python  Mean Squared Error: 8.41048471722451e-26
    Python  
    Python  ExtraTreesRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_trees_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9999999999998015
    Python  Mean Absolute Error: 3.795239380975701e-05
    Python  Mean Squared Error: 2.627067474763585e-09
    Python  R^2 matching decimal places:  0
    Python  MAE matching decimal places:  0
    Python  MSE matching decimal places:  0
    Python  float ONNX model precision:  0
    Python  
    Python  ExtraTreesRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_trees_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    ExtraTreesRegressor.py started  ExtraTreesRegressor.py  1       1
    Traceback (most recent call last):      ExtraTreesRegressor.py  1       1
        onnx_session = ort.InferenceSession(onnx_filename)  ExtraTreesRegressor.py  160     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\extra_trees_regressor_double.onnx failed:Type Erro      onnxruntime_inference_collection.py     424     1
    ExtraTreesRegressor.py finished in 4654 ms              5       1
    


    Рис.112. Результат работы скрипта ExtraTreesRegressor.py (float ONNX)

    Рис.112. Результат работы скрипта ExtraTreesRegressor.py (float ONNX)


    2.2.5.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели extra_trees_regressor_float.onnx и extra_trees_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                          ExtraTreesRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "ExtraTreesRegressor"
    #define   ONNXFilenameFloat  "extra_trees_regressor_float.onnx"
    #define   ONNXFilenameDouble "extra_trees_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    ExtraTreesRegressor (EURUSD,H1) Testing ONNX float: ExtraTreesRegressor (extra_trees_regressor_float.onnx)
    ExtraTreesRegressor (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.9999999999998015
    ExtraTreesRegressor (EURUSD,H1) MQL5:   Mean Absolute Error: 0.0000379523938098
    ExtraTreesRegressor (EURUSD,H1) MQL5:   Mean Squared Error: 0.0000000026270675
    ExtraTreesRegressor (EURUSD,H1) 
    ExtraTreesRegressor (EURUSD,H1) Testing ONNX double: ExtraTreesRegressor (extra_trees_regressor_double.onnx)
    ExtraTreesRegressor (EURUSD,H1) ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\ExtraTreesRegressor.mq5' (133:16)
    ExtraTreesRegressor (EURUSD,H1) model_name=ExtraTreesRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.5.3. ONNX-представление моделей extra_trees_regressor_float.onnx и extra_trees_regressor_double.onnx


    Рис.113. ONNX-представление модели extra_trees_regressor_float.onnx в Netron

    Рис.113. ONNX-представление модели extra_trees_regressor_float.onnx в Netron


    Рис.114. ONNX-представление модели extra_trees_regressor_double.onnx в Netron

    Рис.114. ONNX-представление модели extra_trees_regressor_double.onnx в Netron


    2.2.6. sklearn.svm.NuSVR

    NuSVR - это метод машинного обучения, который используется для задачи регрессии. Этот метод основан на Support Vector Machine (SVM), но применяется к задачам регрессии вместо задач классификации.

    NuSVR представляет собой вариант SVM, который позволяет решать задачи регрессии, предсказывая непрерывные значения целевой переменной.

    Принцип работы NuSVR:

    1. Входные данные: Начнем с набора данных, который включает признаки (независимые переменные) и значения целевой переменной (непрерывные).
    2. Выбор ядра (kernel): NuSVR использует ядра, такие как линейное, полиномиальное или радиально-базисная функция (RBF), чтобы преобразовать данные в более высокоразмерное пространство, где можно найти линейную разделяющую гиперплоскость.
    3. Определение параметра Nu: Параметр Nu (nu) в NuSVR контролирует сложность модели и определяет, как много обучающих примеров будет рассмотрено как выбросы. Значение Nu должно быть в диапазоне от 0 до 1, и оно влияет на количество опорных векторов.
    4. Построение опорных векторов: NuSVR стремится найти оптимальную разделяющую гиперплоскость таким образом, чтобы максимизировать зазор между этой гиперплоскостью и ближайшими точками выборки.
    5. Обучение модели: Модель обучается таким образом, чтобы минимизировать ошибку регрессии и удовлетворить ограничения, связанные с параметром Nu.
    6. Получение прогнозов: После обучения модель может быть использована для предсказания значений целевой переменной на новых данных.

    Преимущества NuSVR:

    Ограничения NuSVR:

    NuSVR - это метод машинного обучения для задачи регрессии, основанный на методе Support Vector Machine (SVM). Он позволяет предсказывать непрерывные значения целевой переменной и может управлять выбросами с использованием параметра Nu.


    2.2.6.1. Код создания модели NuSVR и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.svm.NuSVR, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # NuSVR.py
    # The code demonstrates the process of training NuSVR model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.svm import NuSVR
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "NuSVR"
    onnx_model_filename = data_path + "nu_svr"

    # create a NuSVR model
    nusvr_model = NuSVR()

    # fit the model to the data
    nusvr_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = nusvr_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(nusvr_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(nusvr_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  NuSVR Original model (double)
    Python  R-squared (Coefficient of determination): 0.2771437770527445
    Python  Mean Absolute Error: 83.76666411704255
    Python  Mean Squared Error: 9565.381751764757
    Python  
    Python  NuSVR ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\nu_svr_float.onnx
    Python  Information about input tensors in ONNX:
    1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.27714379657935495
    Python  Mean Absolute Error: 83.766663385322
    Python  Mean Squared Error: 9565.381493373838
    Python  R^2 matching decimal places:  7
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  3
    Python  float ONNX model precision:  5
    Python  
    Python  NuSVR ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\nu_svr_double.onnx
    

    Ошибки во вкладке Errors:

    NuSVR.py started        NuSVR.py        1       1
    Traceback (most recent call last):      NuSVR.py        1       1
        onnx_session = ort.InferenceSession(onnx_filename)  NuSVR.py        159     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess.initialize_session(providers, provider_options, disabled_optimizers)   onnxruntime_inference_collection.py     435     1
    onnxruntime.capi.onnxruntime_pybind11_state.NotImplemented: [ONNXRuntimeError] : 9 : NOT_IMPLEMENTED : Could not find an implementation for SVMRegressor(1) node with name 'SVM'        onnxruntime_inference_collection.py     435     1
    NuSVR.py finished in 2925 ms            5       1
    

    Рис.115. Результат работы скрипта NuSVR.py (float ONNX)

    Рис.115. Результат работы скрипта NuSVR.py (float ONNX)


    2.2.6.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели nu_svr_float.onnx и nu_svr_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                        NuSVR.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "NuSVR"
    #define   ONNXFilenameFloat  "nu_svr_float.onnx"
    #define   ONNXFilenameDouble "nu_svr_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    NuSVR (EURUSD,H1)       Testing ONNX float: NuSVR (nu_svr_float.onnx)
    NuSVR (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.2771437965793548
    NuSVR (EURUSD,H1)       MQL5:   Mean Absolute Error: 83.7666633853219906
    NuSVR (EURUSD,H1)       MQL5:   Mean Squared Error: 9565.3814933738358377
    NuSVR (EURUSD,H1)       
    NuSVR (EURUSD,H1)       Testing ONNX double: NuSVR (nu_svr_double.onnx)
    NuSVR (EURUSD,H1)       ONNX: cannot create session (OrtStatus: 9 'Could not find an implementation for SVMRegressor(1) node with name 'SVM''), inspect code 'Scripts\Regression\NuSVR.mq5' (133:16)
    NuSVR (EURUSD,H1)       model_name=NuSVR OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.

    Сравнение с оригинальной моделью:

    Testing ONNX float: NuSVR (nu_svr_float.onnx)
    Python  Mean Absolute Error: 83.76666411704255
    MQL5:   Mean Absolute Error: 83.7666633853219906


    2.2.6.3. ONNX-представление моделей nu_svr_float.onnx и nu_svr_double.onnx


    Рис.116. ONNX-представление модели nu_svr_float.onnx в Netron

    Рис.116. ONNX-представление модели nu_svr_float.onnx в Netron


    Рис.117. ONNX-представление модели nu_svr_double.onnx в Netron

    Рис.117. ONNX-представление модели nu_svr_double.onnx в Netron



    2.2.7. sklearn.ensemble.RandomForestRegressor

    RandomForestRegressor - это метод машинного обучения, который применяется для решения задачи регрессии.

    Он является одним из наиболее популярных методов, основанных на ансамблевом обучении, и использует алгоритм "Случайного леса" для создания мощных и устойчивых регрессионных моделей.

    Принцип работы RandomForestRegressor:

    1. Входные данные: Начнем с набора данных, который включает в себя признаки (независимые переменные) и целевую переменную (непрерывную).
    2. Случайный лес: RandomForestRegressor использует ансамбль деревьев решений для решения задачи регрессии. В данном случае, каждое дерево в лесу работает над предсказанием значений целевой переменной.
    3. Бутстрэп выборка: Для обучения каждого дерева используется бутстрэп выборка данных, то есть случайная выборка с возвратом из обучающего набора данных. Это позволяет разнообразить данные, на которых обучается каждое дерево.
    4. Случайный выбор признаков: При построении каждого дерева также производится случайный выбор подмножества признаков, что делает модель более устойчивой и способствует снижению коррелированности между деревьями.
    5. Усреднение прогнозов: Когда все деревья построены, RandomForestRegressor усредняет или комбинирует их прогнозы для получения окончательного регрессионного предсказания.

    Преимущества RandomForestRegressor:

    Ограничения RandomForestRegressor:

    RandomForestRegressor - это мощный метод машинного обучения для задачи регрессии, который использует ансамбль случайных деревьев решений для создания устойчивой и высокопроизводительной регрессионной модели. Этот метод особенно полезен в задачах с большими объемами данных и для оценки важности признаков.


    2.2.7.1. Код создания модели RandomForestRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.ensemble.RandomForestRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # RandomForestRegressor.py
    # The code demonstrates the process of training RandomForestRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import RandomForestRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "RandomForestRegressor"
    onnx_model_filename = data_path + "random_forest_regressor"

    # create a RandomForestRegressor model
    regression_model = RandomForestRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  RandomForestRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9998854509605539
    Python  Mean Absolute Error: 0.9186485980852603
    Python  Mean Squared Error: 1.5157997632401086
    Python  
    Python  RandomForestRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\random_forest_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9998854516013125
    Python  Mean Absolute Error: 0.9186420704511761
    Python  Mean Squared Error: 1.515791284236419
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  5
    Python  
    Python  RandomForestRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\random_forest_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    RandomForestRegressor.py started        RandomForestRegressor.py        1       1
    Traceback (most recent call last):      RandomForestRegressor.py        1       1
        onnx_session = ort.InferenceSession(onnx_filename)  RandomForestRegressor.py        159     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     383     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     424     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\random_forest_regressor_double.onnx failed:Type Er      onnxruntime_inference_collection.py     424     1
    RandomForestRegressor.py finished in 4392 ms            5       1
    

    Рис.118. Результат работы скрипта RandomForestRegressor.py (float ONNX)

    Рис.118. Результат работы скрипта RandomForestRegressor.py (float ONNX)


    2.2.7.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели random_forest_regressor_float.onnx и random_forest_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                        RandomForestRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "RandomForestRegressor"
    #define   ONNXFilenameFloat  "random_forest_regressor_float.onnx"
    #define   ONNXFilenameDouble "random_forest_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    RandomForestRegressor (EURUSD,H1)       
    RandomForestRegressor (EURUSD,H1)       Testing ONNX float: RandomForestRegressor (random_forest_regressor_float.onnx)
    RandomForestRegressor (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9998854516013125
    RandomForestRegressor (EURUSD,H1)       MQL5:   Mean Absolute Error: 0.9186420704511761
    RandomForestRegressor (EURUSD,H1)       MQL5:   Mean Squared Error: 1.5157912842364190
    RandomForestRegressor (EURUSD,H1)       
    RandomForestRegressor (EURUSD,H1)       Testing ONNX double: RandomForestRegressor (random_forest_regressor_double.onnx)
    RandomForestRegressor (EURUSD,H1)       ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\RandomForestRegressor.mq5' (133:16)
    RandomForestRegressor (EURUSD,H1)       model_name=RandomForestRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.


    2.2.7.3. ONNX-представление моделей random_forest_regressor_float.onnx и random_forest_regressor_double.onnx


    Рис.119. ONNX-представление модели random_forest_regressor_float.onnx в Netron

    Рис.119. ONNX-представление модели random_forest_regressor_float.onnx в Netron


    Рис.120. ONNX-представление модели random_forest_regressor_double.onnx в Netron

    Рис.120. ONNX-представление модели random_forest_regressor_double.onnx в Netron



    2.2.8. sklearn.ensemble.GradientBoostingRegressor

    GradientBoostingRegressor - это метод машинного обучения, который используется для задачи регрессии. Он является частью семейства ансамблевых методов и базируется на идее построения слабых моделей и комбинирования их в сильную модель с помощью градиентного бустинга.

    Градиентный бустинг - это техника улучшения моделей путем итеративного добавления слабых моделей и коррекции ошибок предыдущих моделей.

    Вот как работает GradientBoostingRegressor:

    1. Начало построения: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Первая модель: Начинаем с обучения первой модели, которая часто выбирается как простая регрессионная модель (например, решающее дерево) на исходных данных.
    3. Остатки и антиградиент: Вычисляем остатки, то есть разницу между предсказанными значениями первой модели и фактическими значениями целевой переменной. Затем вычисляем антиградиент этой функции потерь, который указывает направление, в котором мы хотим улучшить нашу модель.
    4. Построение следующей модели: Строим следующую модель, фокусируясь на предсказании антиградиента (ошибок первой модели). Эта модель обучается на остатках и добавляется к первой модели.
    5. Итерации: Процесс построения новых моделей и коррекции остатков повторяется многократно. Каждая новая модель учитывает остатки предыдущих моделей и пытается улучшить предсказания.
    6. Комбинирование моделей: Предсказания всех моделей комбинируются в итоговое предсказание путем усреднения или взвешивания их в соответствии с их важностью.

    Преимущества GradientBoostingRegressor:

    Ограничения GradientBoostingRegressor:

    GradientBoostingRegressor является мощным методом регрессии и часто используется в практических задачах для достижения высокой производительности при настройке правильных гиперпараметров.


    2.2.8.1. Код создания модели GradientBoostingRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.ensemble.GradientBoostingRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # GradientBoostingRegressor.py
    # The code demonstrates the process of training GradientBoostingRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import GradientBoostingRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "GradientBoostingRegressor"
    onnx_model_filename = data_path + "gradient_boosting_regressor"

    # create a Gradient Boosting Regressor model
    regression_model = GradientBoostingRegressor()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  GradientBoostingRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9999959514652565
    Python  Mean Absolute Error: 0.15069342754017417
    Python  Mean Squared Error: 0.053573282108575676
    Python  
    Python  GradientBoostingRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gradient_boosting_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9999959514739537
    Python  Mean Absolute Error: 0.15069457426101718
    Python  Mean Squared Error: 0.05357316702127665
    Python  R^2 matching decimal places:  10
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  6
    Python  float ONNX model precision:  5
    Python  
    Python  GradientBoostingRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gradient_boosting_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    GradientBoostingRegressor.py started    GradientBoostingRegressor.py    1       1
    Traceback (most recent call last):      GradientBoostingRegressor.py    1       1
        onnx_session = ort.InferenceSession(onnx_filename)  GradientBoostingRegressor.py    161     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     419     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     452     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\gradient_boosting_regressor_double.onnx failed:Typ      onnxruntime_inference_collection.py     452     1
    GradientBoostingRegressor.py finished in 3073 ms                5       1
    

    Рис.121. Результат работы скрипта GradientBoostingRegressor.py (float ONNX)

    Рис.121. Результат работы скрипта GradientBoostingRegressor.py (float ONNX)


    2.2.8.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели gradient_boosting_regressor_float.onnx и gradient_boosting_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                    GradientBoostingRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "GradientBoostingRegressor"
    #define   ONNXFilenameFloat  "gradient_boosting_regressor_float.onnx"
    #define   ONNXFilenameDouble "gradient_boosting_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    GradientBoostingRegressor (EURUSD,H1)   Testing ONNX float: GradientBoostingRegressor (gradient_boosting_regressor_float.onnx)
    GradientBoostingRegressor (EURUSD,H1)   MQL5:   R-Squared (Coefficient of determination): 0.9999959514739537
    GradientBoostingRegressor (EURUSD,H1)   MQL5:   Mean Absolute Error: 0.1506945742610172
    GradientBoostingRegressor (EURUSD,H1)   MQL5:   Mean Squared Error: 0.0535731670212767
    GradientBoostingRegressor (EURUSD,H1)   
    GradientBoostingRegressor (EURUSD,H1)   Testing ONNX double: GradientBoostingRegressor (gradient_boosting_regressor_double.onnx)
    GradientBoostingRegressor (EURUSD,H1)   ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\GradientBoostingRegressor.mq5' (133:16)
    GradientBoostingRegressor (EURUSD,H1)   model_name=GradientBoostingRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.

    Сравнение с оригинальной моделью:

    Testing ONNX float: GradientBoostingRegressor (gradient_boosting_regressor_float.onnx)
    Python  Mean Absolute Error: 0.15069342754017417
    MQL5:   Mean Absolute Error: 0.1506945742610172
    

    Точность MAE ONNX float: 5 знаков после запятой.


    2.2.8.3. ONNX-представление моделей gradient_boosting_regressor_float.onnx и gradient_boosting_regressor_double.onnx


    Рис.122. ONNX-представление модели gradient_boosting_regressor_float.onnx в Netron

    Рис.122. ONNX-представление модели gradient_boosting_regressor_float.onnx в Netron


    Рис.123. ONNX-представление модели gradient_boosting_regressor_double.onnx в Netron

    Рис.123. ONNX-представление модели gradient_boosting_regressor_double.onnx в Netron



    2.2.9. sklearn.ensemble.HistGradientBoostingRegressor

    HistGradientBoostingRegressor - это метод машинного обучения, который представляет собой вариацию градиентного бустинга, оптимизированную для работы с большими наборами данных.

    Этот метод используется для задачи регрессии, а его название "Hist" указывает на то, что он использует гистограммные методы для ускорения обучения.

    Принцип работы HistGradientBoostingRegressor:

    1. Начало построения: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Гистограммные методы: Вместо того, чтобы использовать точное разделение данных на узлах деревьев, HistGradientBoostingRegressor использует гистограммные методы для эффективного представления данных в форме гистограмм. Это сильно ускоряет обучение, особенно на больших объемах данных.
    3. Построение базовых деревьев: Метод строит набор базовых решающих деревьев, называемых "деревьями решений с гистограммами", используя гистограммные представления данных. Эти деревья строятся на основе градиентного бустинга и настраиваются на остатках предыдущей модели.
    4. Постепенное обучение: HistGradientBoostingRegressor добавляет новые деревья в ансамбль постепенно, каждое дерево исправляет остатки предыдущих деревьев.
    5. Комбинирование моделей: После построения базовых деревьев, предсказания всех деревьев комбинируются для получения итогового предсказания.

    Преимущества HistGradientBoostingRegressor:

    Ограничения HistGradientBoostingRegressor:

    HistGradientBoostingRegressor может быть полезным методом регрессии в задачах с большими наборами данных, где высокая производительность и эффективность высокой размерности данных важны.


    2.2.9.1. Код создания модели HistGradientBoostingRegressor и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.ensemble.HistGradientBoostingRegressor, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # HistGradientBoostingRegressor.py
    # The code demonstrates the process of training HistGradientBoostingRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.ensemble import HistGradientBoostingRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "HistGradientBoostingRegressor"
    onnx_model_filename = data_path + "hist_gradient_boosting_regressor"

    # create a Histogram-Based Gradient Boosting Regressor model
    hist_gradient_boosting_model = HistGradientBoostingRegressor()

    # fit the model to the data
    hist_gradient_boosting_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = hist_gradient_boosting_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(hist_gradient_boosting_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType
    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(hist_gradient_boosting_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  HistGradientBoostingRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.9833421349506157
    Python  Mean Absolute Error: 9.070567104488434
    Python  Mean Squared Error: 220.4295035561544
    Python  
    Python  HistGradientBoostingRegressor ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\hist_gradient_boosting_regressor_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.9833421351962779
    Python  Mean Absolute Error: 9.07056497799043
    Python  Mean Squared Error: 220.42950030536645
    Python  R^2 matching decimal places:  8
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  5
    Python  float ONNX model precision:  5
    Python  
    Python  HistGradientBoostingRegressor ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\hist_gradient_boosting_regressor_double.onnx
    

    Ошибки во вкладке Errors:

    HistGradientBoostingRegressor.py started        HistGradientBoostingRegressor.py        1       1
    Traceback (most recent call last):      HistGradientBoostingRegressor.py        1       1
        onnx_session = ort.InferenceSession(onnx_filename)  HistGradientBoostingRegressor.py        161     1
        self._create_inference_session(providers, provider_options, disabled_optimizers)    onnxruntime_inference_collection.py     419     1
        sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)    onnxruntime_inference_collection.py     452     1
    onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Load model from C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\hist_gradient_boosting_regressor_double.onnx faile      onnxruntime_inference_collection.py     452     1
    HistGradientBoostingRegressor.py finished in 3100 ms            5       1
    

    Рис.124. Результат работы скрипта HistGradientBoostingRegressor.py (float ONNX)


    Рис.124. Результат работы скрипта HistGradientBoostingRegressor.py (float ONNX)


    2.2.9.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели hist_gradient_boosting_regressor_float.onnx и hist_gradient_boosting_regressor_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                HistGradientBoostingRegressor.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "HistGradientBoostingRegressor"
    #define   ONNXFilenameFloat  "hist_gradient_boosting_regressor_float.onnx"
    #define   ONNXFilenameDouble "hist_gradient_boosting_regressor_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    HistGradientBoostingRegressor (EURUSD,H1)       Testing ONNX float: HistGradientBoostingRegressor (hist_gradient_boosting_regressor_float.onnx)
    HistGradientBoostingRegressor (EURUSD,H1)       MQL5:   R-Squared (Coefficient of determination): 0.9833421351962779
    HistGradientBoostingRegressor (EURUSD,H1)       MQL5:   Mean Absolute Error: 9.0705649779904292
    HistGradientBoostingRegressor (EURUSD,H1)       MQL5:   Mean Squared Error: 220.4295003053665312
    HistGradientBoostingRegressor (EURUSD,H1)       
    HistGradientBoostingRegressor (EURUSD,H1)       Testing ONNX double: HistGradientBoostingRegressor (hist_gradient_boosting_regressor_double.onnx)
    HistGradientBoostingRegressor (EURUSD,H1)       ONNX: cannot create session (OrtStatus: 1 'Type Error: Type (tensor(double)) of output arg (variable) of node (TreeEnsembleRegressor) does not match expected type (tensor(float)).'), inspect code 'Scripts\Regression\HistGradientBoostingRegressor.mq5' (133:16)
    HistGradientBoostingRegressor (EURUSD,H1)       model_name=HistGradientBoostingRegressor OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.

    Сравнение ONNX-float модели с оригинальной моделью:

    Testing ONNX float: HistGradientBoostingRegressor (hist_gradient_boosting_regressor_float.onnx)
    Python  Mean Absolute Error: 9.070567104488434
    MQL5:   Mean Absolute Error: 9.0705649779904292

    Точность MAE ONNX float: 5 знаков после запятой.


    2.2.9.3. ONNX-представление моделей hist_gradient_boosting_regressor_float.onnx и hist_gradient_boosting_regressor_double.onnx  


    Рис.125. ONNX-представление модели hist_gradient_boosting_regressor_float.onnx в Netron

    Рис.125. ONNX-представление модели hist_gradient_boosting_regressor_float.onnx в Netron


    Рис.126. ONNX-представление модели hist_gradient_boosting_regressor_double.onnx в Netron

    Рис.126. ONNX-представление модели hist_gradient_boosting_regressor_double.onnx в Netron




    2.2.10. sklearn.svm.SVR

    SVR (Support Vector Regression) - это метод машинного обучения, используемый для задачи регрессии. Он основан на той же идее, что и Support Vector Machine (SVM) для задачи классификации, но адаптирован для регрессии. Главная цель SVR - предсказать непрерывные значения целевой переменной, опираясь на максимальное среднее расстояние между точками данных и линией регрессии.

    Принцип работы SVR:

    1. Определение границ: Как и в случае SVM, SVR также строит границы, которые отделяют разные классы точек данных. Вместо того, чтобы разделять классы, SVR стремится построить "трубку" (tube) вокруг точек данных, где ширина трубки контролируется гиперпараметром.
    2. Целевая переменная и функция потерь: Вместо использования классов, как в задаче классификации, SVR работает с непрерывными значениями целевой переменной. Он минимизирует ошибку предсказания, которая измеряется с использованием функции потерь, такой как квадрат разности между предсказанным и фактическим значением.
    3. Регуляризация: SVR также поддерживает регуляризацию, которая помогает управлять сложностью модели и предотвращать переобучение.
    4. Функции ядра: SVR обычно использует функции ядра (kernel functions), которые позволяют работать с нелинейными зависимостями между признаками и целевой переменной. Популярными ядровыми функциями являются радиальная базисная функция (RBF), полиномиальная и линейная функции.

    Преимущества SVR:

    Ограничения SVR:

    SVR - это метод машинного обучения для задачи регрессии, основанный на идее построения "трубки" вокруг точек данных, чтобы минимизировать ошибку предсказания. Он обладает устойчивостью к выбросам и способностью работать с нелинейными зависимостями, что делает его полезным в различных задачах регрессии.

    2.2.10.1. Код создания модели SVR и ее экспорта в ONNX для float и double

    Этот код создает модель sklearn.svm.SVR, обучает ее на синтетических данных, сохраняет модель в формате ONNX и выполняет предсказания с использованием как float, так и double входных данных. Также он оценивает точность как исходной модели, так и моделей, экспортированных в ONNX.

    # SVR.py
    # The code demonstrates the process of training SVR model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.svm import SVR
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "SVR"
    onnx_model_filename = data_path + "svr"

    # create an SVR model
    regression_model = SVR()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  SVR Original model (double)
    Python  R-squared (Coefficient of determination): 0.398243655775797
    Python  Mean Absolute Error: 73.63683696034649
    Python  Mean Squared Error: 7962.89631509593
    Python  
    Python  SVR ONNX model (float)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\svr_float.onnx
    Python  Information about input tensors in ONNX:
    Python  1. Name: float_input, Data Type: tensor(float), Shape: [None, 1]
    Python  Information about output tensors in ONNX:
    Python  1. Name: variable, Data Type: tensor(float), Shape: [None, 1]
    Python  R-squared (Coefficient of determination) 0.3982436352100983
    Python  Mean Absolute Error: 73.63683840363255
    Python  Mean Squared Error: 7962.896587236852
    Python  R^2 matching decimal places:  7
    Python  MAE matching decimal places:  5
    Python  MSE matching decimal places:  3
    Python  float ONNX model precision:  5
    Python  
    Python  SVR ONNX model (double)
    Python  ONNX model saved to C:\Users\user\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075\MQL5\Scripts\Regression\svr_double.onnx
    

    Рис.127. Результат работы скрипта SVR.py (float ONNX)

    Рис.127. Результат работы скрипта SVR.py (float ONNX)


    2.2.10.2. Код на MQL5 для исполнения ONNX-моделей

    Этот код исполняет сохраненные ONNX-модели svr_float.onnx и svr_double.onnx и демонстрирует использование регрессионных метрик в MQL5.

    //+------------------------------------------------------------------+
    //|                                                          SVR.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #define   ModelName          "SVR"
    #define   ONNXFilenameFloat  "svr_float.onnx"
    #define   ONNXFilenameDouble "svr_double.onnx"
    
    #resource ONNXFilenameFloat  as const uchar ExtModelFloat[];
    #resource ONNXFilenameDouble as const uchar ExtModelDouble[];
    
    #define   TestFloatModel  1
    #define   TestDoubleModel 2
    
    //+------------------------------------------------------------------+
    //| Calculate regression using float values                          |
    //+------------------------------------------------------------------+
    bool RunModelFloat(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       float input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=(float)input_vector[k];
    //--- prepare output tensor
       float output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    //+------------------------------------------------------------------+
    //| Calculate regression using double values                         |
    //+------------------------------------------------------------------+
    bool RunModelDouble(long model,vector &input_vector, vector &output_vector)
      {
    //--- check number of input samples
       ulong batch_size=input_vector.Size();
       if(batch_size==0)
          return(false);
    //--- prepare output array
       output_vector.Resize((int)batch_size);
    //--- prepare input tensor
       double input_data[];
       ArrayResize(input_data,(int)batch_size);
    //--- set input shape
       ulong input_shape[]= {batch_size, 1};
       OnnxSetInputShape(model,0,input_shape);
    //--- copy data to the input tensor
       for(int k=0; k<(int)batch_size; k++)
          input_data[k]=input_vector[k];
    //--- prepare output tensor
       double output_data[];
       ArrayResize(output_data,(int)batch_size);
    //--- set output shape
       ulong output_shape[]= {batch_size,1};
       OnnxSetOutputShape(model,0,output_shape);
    //--- run the model
       bool res=OnnxRun(model,ONNX_DEBUG_LOGS,input_data,output_data);
    //--- copy output to vector
       if(res)
         {
          for(int k=0; k<(int)batch_size; k++)
             output_vector[k]=output_data[k];
         }
    //---
       return(res);
      }
    
    //+------------------------------------------------------------------+
    //| Generate synthetic data                                          |
    //+------------------------------------------------------------------+
    bool GenerateData(const int n,vector &x,vector &y)
      {
       if(n<=0)
          return(false);
    //--- prepare arrays
       x.Resize(n);
       y.Resize(n);
    //---
       for(int i=0; i<n; i++)
         {
          x[i]=(double)1.0*i;
          y[i]=(double)(4*x[i] + 10*sin(x[i]*0.5));
         }
    //---
       return(true);
      }
    
    //+------------------------------------------------------------------+
    //| TestRegressionModel                                              |
    //+------------------------------------------------------------------+
    bool TestRegressionModel(const string model_name,const int model_type)
      {
    //---
       long  model=INVALID_HANDLE;
       ulong flags=ONNX_DEFAULT;
    
       if(model_type==TestFloatModel)
         {
          PrintFormat("\nTesting ONNX float: %s (%s)",model_name,ONNXFilenameFloat);
          model=OnnxCreateFromBuffer(ExtModelFloat,flags);
         }
       else
          if(model_type==TestDoubleModel)
            {
             PrintFormat("\nTesting ONNX double: %s (%s)",model_name,ONNXFilenameDouble);
             model=OnnxCreateFromBuffer(ExtModelDouble,flags);
            }
          else
            {
             PrintFormat("Model type is not incorrect.");
             return(false);
            }
    //--- check
       if(model==INVALID_HANDLE)
         {
          PrintFormat("model_name=%s OnnxCreate error %d",model_name,GetLastError());
          return(false);
         }
    //---
       vector x_values= {};
       vector y_true= {};
       vector y_predicted= {};
    //---
       int n=100;
       GenerateData(n,x_values,y_true);
    //---
       bool run_result=false;
       if(model_type==TestFloatModel)
         {
          run_result=RunModelFloat(model,x_values,y_predicted);
         }
       else
          if(model_type==TestDoubleModel)
            {
             run_result=RunModelDouble(model,x_values,y_predicted);
            }
    //---
       if(run_result)
         {
          PrintFormat("MQL5:   R-Squared (Coefficient of determination): %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_R2));
          PrintFormat("MQL5:   Mean Absolute Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MAE));
          PrintFormat("MQL5:   Mean Squared Error: %.16f",y_predicted.RegressionMetric(y_true,REGRESSION_MSE));
         }
       else
          PrintFormat("Error %d",GetLastError());
    //--- release model
       OnnxRelease(model);
    //---
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    int OnStart(void)
      {
    //--- test ONNX regression model for float
       TestRegressionModel(ModelName,TestFloatModel);
    //--- test ONNX regression model for double
       TestRegressionModel(ModelName,TestDoubleModel);
    //---
       return(0);
      }
    //+------------------------------------------------------------------+

    Результат:

    SVR (EURUSD,H1) Testing ONNX float: SVR (svr_float.onnx)
    SVR (EURUSD,H1) MQL5:   R-Squared (Coefficient of determination): 0.3982436352100981
    SVR (EURUSD,H1) MQL5:   Mean Absolute Error: 73.6368384036325523
    SVR (EURUSD,H1) MQL5:   Mean Squared Error: 7962.8965872368517012
    SVR (EURUSD,H1) 
    SVR (EURUSD,H1) Testing ONNX double: SVR (svr_double.onnx)
    SVR (EURUSD,H1) ONNX: cannot create session (OrtStatus: 9 'Could not find an implementation for SVMRegressor(1) node with name 'SVM''), inspect code 'Scripts\R\SVR.mq5' (133:16)
    SVR (EURUSD,H1) model_name=SVR OnnxCreate error 5800
    

    ONNX-модель с расчетом во float исполнилась нормально, а при исполнении модели с double возникает ошибка.

    Сравнение с оригинальной моделью:

    Testing ONNX float: SVR (svr_float.onnx)
    Python  Mean Absolute Error: 73.63683696034649
    MQL5:   Mean Absolute Error: 73.6368384036325523

    Точность MAE ONNX float: 5 знаков после запятой.


    2.2.10.3. ONNX-представление моделей svr_float.onnx и svr_double.onnx

    Рис.128. ONNX-представление модели svr_float.onnx в Netron

    Рис.128. ONNX-представление модели svr_float.onnx в Netron


    Рис.129. ONNX-представление модели svr_double.onnx в Netron

    Рис.129. ONNX-представление модели svr_double.onnx в Netron


    2.3. Регрессионные модели, которые столкнулись с проблемами при конвертации в ONNX

    Некоторые регрессионные модели конвертер sklearn-onnx не смог конвертировать в ONNX-формат.


    2.3.1. sklearn.dummy.DummyRegressor

    DummyRegressor - это метод машинного обучения, который используется в задачах регрессии для создания базовой модели, которая предсказывает целевую переменную с использованием простых правил. DummyRegressor полезен для сравнения с другими более сложными моделями и оценки их производительности. Этот метод часто используется в контексте оценки качества других регрессионных моделей.

    DummyRegressor предоставляет несколько стратегий для предсказания:

    1. "mean" (по умолчанию): DummyRegressor предсказывает среднее значение целевой переменной из обучающего набора данных. Эта стратегия полезна, когда нужно определить, насколько другая модель лучше, чем просто предсказание среднего.
    2. "median": DummyRegressor предсказывает медианное значение целевой переменной из обучающего набора данных.
    3. "quantile": DummyRegressor предсказывает значение квантиля целевой переменной (задается параметром quantile) из обучающего набора данных.
    4. "constant": DummyRegressor предсказывает постоянное значение, которое задается пользователем (с помощью параметра strategy).

    Преимущества DummyRegressor:

    Ограничения DummyRegressor:

    DummyRegressor полезен как инструмент для быстрой оценки и сравнения производительности других моделей регрессии, но сам по себе не представляет серьезной регрессионной модели.


    2.3.1.1. Код создания модели DummyRegressor

    # DummyRegressor.py
    # The code demonstrates the process of training DummyRegressor model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.dummy import DummyRegressor
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "DummyRegressor"
    onnx_model_filename = data_path + "dummy_regressor"

    # create an Dummy Regressor model
    regression_model = DummyRegressor(strategy="mean")

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_flo
    at = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  DummyRegressor Original model (double)
    Python  R-squared (Coefficient of determination): 0.0
    Python  Mean Absolute Error: 100.00329851715793
    Python  Mean Squared Error: 13232.758393867645
    

    Ошибки во вкладке "Errors":

    DummyRegressor.py started       DummyRegressor.py       1       1
    Traceback (most recent call last):      DummyRegressor.py       1       1
        onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)     DummyRegressor.py       87      1
        onnx_model = convert_topology(      convert.py      208     1
        topology.convert_operators(container=container, verbose=verbose)    _topology.py    1532    1
        self.call_shape_calculator(operator)        _topology.py    1348    1
        operator.infer_types()      _topology.py    1163    1
        raise MissingShapeCalculator(       _topology.py    629     1
    skl2onnx.common.exceptions.MissingShapeCalculator: Unable to find a shape calculator for type '<class 'sklearn.dummy.DummyRegressor'>'. _topology.py    629     1
    It usually means the pipeline being converted contains a        _topology.py    629     1
    transformer or a predictor with no corresponding converter      _topology.py    629     1
    implemented in sklearn-onnx. If the converted is implemented    _topology.py    629     1
    in another library, you need to register        _topology.py    629     1
    the converted so that it can be used by sklearn-onnx (function  _topology.py    629     1
    update_registered_converter). If the model is not yet covered   _topology.py    629     1
    by sklearn-onnx, you may raise an issue to      _topology.py    629     1
    https://github.com/onnx/sklearn-onnx/issues     _topology.py    629     1
    to get the converter implemented or even contribute to the      _topology.py    629     1
    project. If the model is a custom model, a new converter must   _topology.py    629     1
    be implemented. Examples can be found in the gallery.   _topology.py    629     1
    DummyRegressor.py finished in 2565 ms           19      1
    


    2.3.2. sklearn.kernel_ridge.KernelRidge

    KernelRidge (ядерная регрессия) - это метод машинного обучения, используемый для задачи регрессии. Он является комбинацией ядерного метода опорных векторов (Kernel SVM) и регрессии. KernelRidge позволяет моделировать сложные, нелинейные отношения между признаками и целевой переменной, используя ядерные функции.

    Принцип работы KernelRidge:

    Преимущества KernelRidge:

    Ограничения KernelRidge:

    KernelRidge полезен в задачах регрессии, где данные имеют сложные, нелинейные зависимости, и требуется модель, способная их учесть. Он также полезен в задачах, где ядерные функции могут быть использованы для преобразования данных в более информативное представление.


    2.3.2.1. Код создания модели KernelRidge

    # KernelRidge.py
    # The code demonstrates the process of training KernelRidge model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.kernel_ridge import KernelRidge
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "KernelRidge"
    onnx_model_filename = data_path + "kernel_ridge"

    # create an KernelRidge model
    regression_model = KernelRidge(alpha=1.0, kernel='linear')

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  KernelRidge Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962137909675411
    Python  Mean Absolute Error: 6.36977985227399
    Python  Mean Squared Error: 50.10198935520715
    

    Ошибки во вкладке Errors:

    KernelRidge.py started  KernelRidge.py  1       1
    Traceback (most recent call last):      KernelRidge.py  1       1
        onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)     KernelRidge.py  87      1
        onnx_model = convert_topology(      convert.py      208     1
        topology.convert_operators(container=container, verbose=verbose)    _topology.py    1532    1
        self.call_shape_calculator(operator)        _topology.py    1348    1
        operator.infer_types()      _topology.py    1163    1
        raise MissingShapeCalculator(       _topology.py    629     1
    skl2onnx.common.exceptions.MissingShapeCalculator: Unable to find a shape calculator for type '<class 'sklearn.kernel_ridge.KernelRidge'>'.     _topology.py    629     1
    It usually means the pipeline being converted contains a        _topology.py    629     1
    transformer or a predictor with no corresponding converter      _topology.py    629     1
    implemented in sklearn-onnx. If the converted is implemented    _topology.py    629     1
    in another library, you need to register        _topology.py    629     1
    the converted so that it can be used by sklearn-onnx (function  _topology.py    629     1
    update_registered_converter). If the model is not yet covered   _topology.py    629     1
    by sklearn-onnx, you may raise an issue to      _topology.py    629     1
    https://github.com/onnx/sklearn-onnx/issues     _topology.py    629     1
    to get the converter implemented or even contribute to the      _topology.py    629     1
    project. If the model is a custom model, a new converter must   _topology.py    629     1
    be implemented. Examples can be found in the gallery.   _topology.py    629     1
    KernelRidge.py finished in 2516 ms              19      1
    


    2.3.3. sklearn.isotonic.IsotonicRegression

    IsotonicRegression - это метод машинного обучения, используемый для задачи регрессии, который моделирует монотонную зависимость между признаками и целевой переменной. В данном контексте "монотонность" означает, что увеличение значения одного из признаков приводит к увеличению или уменьшению значения целевой переменной, сохраняя при этом направление изменения.

    Принцип работы IsotonicRegression:

    1. Входные данные: Начинаем с исходного набора данных, где у нас есть признаки (независимые переменные) и соответствующие значения целевой переменной.
    2. Монотонная регрессия: IsotonicRegression стремится найти наилучшую монотонную функцию, которая описывает зависимость между признаками и целевой переменной. Эта функция может быть линейной или нелинейной, но она должна сохранять монотонность.
    3. Обучение модели: Модель обучается на данных, чтобы определить параметры монотонной функции. В процессе обучения модель пытается минимизировать сумму квадратов ошибок между предсказаниями и фактическими значениями целевой переменной.
    4. Предсказание: После обучения модель может использоваться для предсказания значений целевой переменной для новых данных, сохраняя монотонность взаимосвязи.

    Преимущества IsotonicRegression:

    Ограничения IsotonicRegression:

    IsotonicRegression полезен в задачах, где монотонность взаимосвязи между признаками и целевой переменной считается важным фактором, и требуется построение модели, которая сохраняет эту характеристику.


    2.3.3.1. Код создания модели IsotonicRegression

    # IsotonicRegression.py
    # The code demonstrates the process of training IsotonicRegression model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.isotonic import IsotonicRegression
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "IsotonicRegression"
    onnx_model_filename = data_path + "isotonic_regression"

    # create an IsotonicRegression model
    regression_model = IsotonicRegression()

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  IsotonicRegression Original model (double)
    Python  R-squared (Coefficient of determination): 0.9999898125037958
    Python  Mean Absolute Error: 0.20093409873424467
    Python  Mean Squared Error: 0.13480867590911208
    

    Ошибки во вкладке Errors:

    IsotonicRegression.py started   IsotonicRegression.py   1       1
    Traceback (most recent call last):      IsotonicRegression.py   1       1
        onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)     IsotonicRegression.py   87      1
        onnx_model = convert_topology(      convert.py      208     1
        topology.convert_operators(container=container, verbose=verbose)    _topology.py    1532    1
        self.call_shape_calculator(operator)        _topology.py    1348    1
        operator.infer_types()      _topology.py    1163    1
        raise MissingShapeCalculator(       _topology.py    629     1
    skl2onnx.common.exceptions.MissingShapeCalculator: Unable to find a shape calculator for type '<class 'sklearn.isotonic.IsotonicRegression'>'.  _topology.py    629     1
    It usually means the pipeline being converted contains a        _topology.py    629     1
    transformer or a predictor with no corresponding converter      _topology.py    629     1
    implemented in sklearn-onnx. If the converted is implemented    _topology.py    629     1
    in another library, you need to register        _topology.py    629     1
    the converted so that it can be used by sklearn-onnx (function  _topology.py    629     1
    update_registered_converter). If the model is not yet covered   _topology.py    629     1
    by sklearn-onnx, you may raise an issue to      _topology.py    629     1
    https://github.com/onnx/sklearn-onnx/issues     _topology.py    629     1
    to get the converter implemented or even contribute to the      _topology.py    629     1
    project. If the model is a custom model, a new converter must   _topology.py    629     1
    be implemented. Examples can be found in the gallery.   _topology.py    629     1
    IsotonicRegression.py finished in 2499 ms               19      1
    


    2.3.4. sklearn.cross_decomposition.PLSCanonical

    PLSCanonical (Partial Least Squares Canonical) - это метод машинного обучения, который используется для решения задачи канонической корреляции. Он является расширением метода частных наименьших квадратов (PLS) и применяется для анализа и моделирования связей между двумя наборами переменных.

    Принцип работы PLSCanonical:

    1. Входные данные: Начнем с двух наборов данных (X и Y), где каждый набор представляет собой множество переменных (признаков). Обычно X и Y содержат коррелированные данные, и задача - найти линейные комбинации признаков, которые максимизируют корреляцию между ними.
    2. Подбор линейных комбинаций: PLSCanonical находит линейные комбинации (компоненты), как в X, так и в Y, таким образом, чтобы максимизировать корреляцию между компонентами двух наборов данных. Эти компоненты называются каноническими переменными.
    3. Поиск максимальной корреляции: Основная цель PLSCanonical - найти такие канонические переменные, которые максимизируют корреляцию между X и Y. Это позволяет выделить наиболее информативные связи между двумя наборами данных.
    4. Обучение модели: Как только канонические переменные найдены, вы можете использовать их для создания модели, которая может прогнозировать значения Y на основе X.
    5. Получение прогнозов: После обучения модель может быть использована для предсказания значений Y на новых данных, используя соответствующие значения X.

    Преимущества PLSCanonical:

    Ограничения PLSCanonical:

    PLSCanonical - это метод машинного обучения, используемый для анализа и моделирования корреляций между двумя наборами переменных. Этот метод позволяет изучать взаимосвязи между данными и может быть полезным для снижения размерности данных и предсказания значений на основе коррелированных компонент.

    2.3.4.1. Код создания модели PLSCanonical

    # PLSCanonical.py
    # The code demonstrates the process of training PLSCanonical model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com


    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cross_decomposition import PLSCanonical
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name = "PLSCanonical"
    onnx_model_filename = data_path + "pls_canonical"

    # create an PLSCanonical model
    regression_model = PLSCanonical(n_components=1)

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8, 5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  
    Python  PLSCanonical Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962347199278333
    Python  Mean Absolute Error: 6.3561407034365995
    Python  Mean Squared Error: 49.82504148022689
    

    Ошибки во вкладке Errors:

    PLSCanonical.py started PLSCanonical.py 1       1
    Traceback (most recent call last):      PLSCanonical.py 1       1
        onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)     PLSCanonical.py 87      1
        onnx_model = convert_topology(      convert.py      208     1
        topology.convert_operators(container=container, verbose=verbose)    _topology.py    1532    1
        self.call_shape_calculator(operator)        _topology.py    1348    1
        operator.infer_types()      _topology.py    1163    1
        raise MissingShapeCalculator(       _topology.py    629     1
    skl2onnx.common.exceptions.MissingShapeCalculator: Unable to find a shape calculator for type '<class 'sklearn.cross_decomposition._pls.PLSCanonical'>'.        _topology.py    629     1
    It usually means the pipeline being converted contains a        _topology.py    629     1
    transformer or a predictor with no corresponding converter      _topology.py    629     1
    implemented in sklearn-onnx. If the converted is implemented    _topology.py    629     1
    in another library, you need to register        _topology.py    629     1
    the converted so that it can be used by sklearn-onnx (function  _topology.py    629     1
    update_registered_converter). If the model is not yet covered   _topology.py    629     1
    by sklearn-onnx, you may raise an issue to      _topology.py    629     1
    https://github.com/onnx/sklearn-onnx/issues     _topology.py    629     1
    to get the converter implemented or even contribute to the      _topology.py    629     1
    project. If the model is a custom model, a new converter must   _topology.py    629     1
    be implemented. Examples can be found in the gallery.   _topology.py    629     1
    PLSCanonical.py finished in 2513 ms             19      1
    


    2.3.5. sklearn.cross_decomposition.CCA

    Canonical Correlation Analysis (CCA), или канонический анализ корреляции, является мультивариативным методом статистического анализа, используемым для изучения связей между двумя наборами переменных (набором переменных X и набором переменных Y). Основная цель CCA - найти линейные комбинации переменных X и Y, которые максимизируют корреляцию между ними. Эти линейные комбинации называются каноническими переменными.

    Принцип работы CCA:

    1. Входные данные: Начинаем с двух наборов переменных X и Y. В этих наборах переменных может быть любое количество переменных, и CCA пытается найти линейные комбинации, которые максимизируют корреляцию между ними.
    2. Построение канонических переменных: CCA находит канонические переменные в X и Y, которые максимизируют корреляцию между ними. Эти канонические переменные представляют собой линейные комбинации исходных переменных, по одной для каждого канонического показателя.
    3. Оценка корреляции: CCA оценивает корреляцию между парами канонических переменных. Канонические переменные обычно упорядочены по убыванию корреляции, так что первая пара имеет наибольшую корреляцию, вторая - следующую по величине, и так далее.
    4. Интерпретация: Канонические переменные могут быть интерпретированы с учетом их корреляции и весов переменных. Это позволяет понять, какие переменные из наборов X и Y наиболее сильно связаны.

    Преимущества CCA:

    Ограничения CCA:

    CCA полезен в задачах, где требуется изучение взаимосвязи между двумя наборами переменных и выявление скрытых корреляций.


    2.3.5.1. Код создания модели CCA

    # CCA.py
    # The code demonstrates the process of training CCA model, exporting it to ONNX format (both float and double), and making predictions using the ONNX models.
    # Copyright 2023, MetaQuotes Ltd.
    # https://www.mql5.com

    # function to compare matching decimal places
    def compare_decimal_places(value1, value2):
        # convert both values to strings
        str_value1 = str(value1)
        str_value2 = str(value2)

        # find the positions of the decimal points in the strings
        dot_position1 = str_value1.find(".")
        dot_position2 = str_value2.find(".")

        # if one of the values doesn't have a decimal point, return 0
        if dot_position1 == -1 or dot_position2 == -1:
            return 0

        # calculate the number of decimal places
        decimal_places1 = len(str_value1) - dot_position1 - 1
        decimal_places2 = len(str_value2) - dot_position2 - 1

        # find the minimum of the two decimal places counts
        min_decimal_places = min(decimal_places1, decimal_places2)

        # initialize a count for matching decimal places
        matching_count = 0

        # compare characters after the decimal point
        for i in range(1, min_decimal_places + 1):
            if str_value1[dot_position1 + i] == str_value2[dot_position2 + i]:
                matching_count += 1
            else:
                break

        return matching_count

    # import necessary libraries
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.cross_decomposition import CCA
    from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
    import onnx
    import onnxruntime as ort
    from skl2onnx import convert_sklearn
    from skl2onnx.common.data_types import FloatTensorType
    from skl2onnx.common.data_types import DoubleTensorType
    from sys import argv

    # define the path for saving the model
    data_path = argv[0]
    last_index = data_path.rfind("\\") + 1
    data_path = data_path[0:last_index]

    # generate synthetic data for regression
    X = np.arange(0,100,1).reshape(-1,1)
    y = 4*X + 10*np.sin(X*0.5)

    model_name="CCA"
    onnx_model_filename = data_path + "cca"

    # create an CCA model
    regression_model = CCA(n_components=1)

    # fit the model to the data
    regression_model.fit(X, y.ravel())

    # predict values for the entire dataset
    y_pred = regression_model.predict(X)

    # evaluate the model's performance
    r2 = r2_score(y, y_pred)
    mse = mean_squared_error(y, y_pred)
    mae = mean_absolute_error(y, y_pred)

    print("\n"+model_name+" Original model (double)")
    print("R-squared (Coefficient of determination):", r2)
    print("Mean Absolute Error:", mae)
    print("Mean Squared Error:", mse)

    # convert to ONNX-model (float)
    # define the input data type as FloatTensorType

    initial_type_float = [('float_input', FloatTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_float.onnx"
    onnx.save_model(onnx_model_float, onnx_filename)

    print("\n"+model_name+" ONNX model (float)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as FloatTensorType
    initial_type_float = X.astype(np.float32)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_float = onnx_session.run([output_name], {input_name: initial_type_float})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_float = r2_score(y, y_pred_onnx_float)
    mse_onnx_float = mean_squared_error(y, y_pred_onnx_float)
    mae_onnx_float = mean_absolute_error(y, y_pred_onnx_float)
    print("R-squared (Coefficient of determination)", r2_onnx_float)
    print("Mean Absolute Error:", mae_onnx_float)
    print("Mean Squared Error:", mse_onnx_float)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_float))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_float))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_float))
    print("float ONNX model precision: ",compare_decimal_places(mae, mae_onnx_float))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with float ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_float.png')

    # convert to ONNX-model (double)
    # define the input data type as DoubleTensorType

    initial_type_double = [('double_input', DoubleTensorType([None, X.shape[1]]))]

    # export the model to ONNX format
    onnx_model_double = convert_sklearn(regression_model, initial_types=initial_type_double, target_opset=12)

    # save the model to a file
    onnx_filename=onnx_model_filename+"_double.onnx"
    onnx.save_model(onnx_model_double, onnx_filename)

    print("\n"+model_name+" ONNX model (double)")
    # print model path
    print(f"ONNX model saved to {onnx_filename}")

    # load the ONNX model and make predictions
    onnx_session = ort.InferenceSession(onnx_filename)
    input_name = onnx_session.get_inputs()[0].name
    output_name = onnx_session.get_outputs()[0].name

    # display information about input tensors in ONNX
    print("Information about input tensors in ONNX:")
    for i, input_tensor in enumerate(onnx_session.get_inputs()):
        print(f"{i + 1}. Name: {input_tensor.name}, Data Type: {input_tensor.type}, Shape: {input_tensor.shape}")

    # display information about output tensors in ONNX
    print("Information about output tensors in ONNX:")
    for i, output_tensor in enumerate(onnx_session.get_outputs()):
        print(f"{i + 1}. Name: {output_tensor.name}, Data Type: {output_tensor.type}, Shape: {output_tensor.shape}")

    # define the input data type as DoubleTensorType
    initial_type_double = X.astype(np.float64)

    # predict values for the entire dataset using ONNX
    y_pred_onnx_double = onnx_session.run([output_name], {input_name: initial_type_double})[0]

    # calculate and display the errors for the original and ONNX models
    r2_onnx_double = r2_score(y, y_pred_onnx_double)
    mse_onnx_double = mean_squared_error(y, y_pred_onnx_double)
    mae_onnx_double = mean_absolute_error(y, y_pred_onnx_double)
    print("R-squared (Coefficient of determination)", r2_onnx_double)
    print("Mean Absolute Error:", mae_onnx_double)
    print("Mean Squared Error:", mse_onnx_double)
    print("R^2 matching decimal places: ",compare_decimal_places(r2, r2_onnx_double))
    print("MAE matching decimal places: ",compare_decimal_places(mae, mae_onnx_double))
    print("MSE matching decimal places: ",compare_decimal_places(mse, mse_onnx_double))
    print("double ONNX model precision: ",compare_decimal_places(mae, mae_onnx_double))

    # set the figure size
    plt.figure(figsize=(8,5))
    # plot the original data and the regression line
    plt.scatter(X, y, label='Original Data', marker='o')
    plt.scatter(X, y_pred, color='blue', label='Scikit-Learn '+model_name+' Output', marker='o')
    plt.scatter(X, y_pred_onnx_float, color='red', label='ONNX '+model_name+' Output', marker='o', linestyle='--')
    plt.xlabel('X')
    plt.ylabel('y')
    plt.legend()
    plt.title(model_name+' Comparison (with double ONNX)')
    #plt.show()
    plt.savefig(data_path + model_name+'_plot_double.png')

    Результат:

    Python  CCA Original model (double)
    Python  R-squared (Coefficient of determination): 0.9962347199278333
    Python  Mean Absolute Error: 6.3561407034365995
    Python  Mean Squared Error: 49.82504148022689
    

    Ошибки во вкладке Errors:

    CCA.py started  CCA.py  1       1
    Traceback (most recent call last):      CCA.py  1       1
        onnx_model_float = convert_sklearn(regression_model, initial_types=initial_type_float, target_opset=12)     CCA.py  87      1
        onnx_model = convert_topology(      convert.py      208     1
        topology.convert_operators(container=container, verbose=verbose)    _topology.py    1532    1
        self.call_shape_calculator(operator)        _topology.py    1348    1
        operator.infer_types()      _topology.py    1163    1
        raise MissingShapeCalculator(       _topology.py    629     1
    skl2onnx.common.exceptions.MissingShapeCalculator: Unable to find a shape calculator for type '<class 'sklearn.cross_decomposition._pls.CCA'>'. _topology.py    629     1
    It usually means the pipeline being converted contains a        _topology.py    629     1
    transformer or a predictor with no corresponding converter      _topology.py    629     1
    implemented in sklearn-onnx. If the converted is implemented    _topology.py    629     1
    in another library, you need to register        _topology.py    629     1
    the converted so that it can be used by sklearn-onnx (function  _topology.py    629     1
    update_registered_converter). If the model is not yet covered   _topology.py    629     1
    by sklearn-onnx, you may raise an issue to      _topology.py    629     1
    https://github.com/onnx/sklearn-onnx/issues     _topology.py    629     1
    to get the converter implemented or even contribute to the      _topology.py    629     1
    project. If the model is a custom model, a new converter must   _topology.py    629     1
    be implemented. Examples can be found in the gallery.   _topology.py    629     1
    CCA.py finished in 2543 ms              19      1
    


    Выводы

    В статье рассмотрены 45 регрессионных моделей, доступных в библиотеке Scikit-learn версии 1.3.2.

    1. Из этого набора 5 моделей столкнулись с трудностями при конвертации в ONNX-формат:

    1. DummyRegressor (Фиктивный регрессор);
    2. KernelRidge (Регрессия Ridge с использованием методов ядра);
    3. IsotonicRegression (Изотоническая регрессия);
    4. PLSCanonical (Канонический анализ частных наименьших квадратов);
    5. CCA (Канонический анализ корреляции).

    Возможно, эти модели слишком сложны по своей структуре или логике, они могут использовать специфические структуры данных или алгоритмы, которые не полностью совместимы с форматом ONNX.

    2. Остальные 40 моделей были успешно конвертированы в ONNX с расчетом на float.

    1. ARDRegression: Регрессия с автоматическим определением релевантности (ARD);
    2. BayesianRidge: Байесовская линейная регрессия с регуляризацией;
    3. ElasticNet: Комбинация регуляризации L1 и L2 для уменьшения переобучения;
    4. ElasticNetCV: Эластичная сеть с автоматическим выбором параметра регуляризации;
    5. HuberRegressor: Регрессия с уменьшенной чувствительностью к выбросам;
    6. Lars: Линейная регрессия методом наименьших углов;
    7. LarsCV: Кросс-валидация для метода наименьших углов;
    8. Lasso: Регрессия с L1-регуляризацией для отбора признаков;
    9. LassoCV: Кросс-валидация для Lasso-регрессии;
    10. LassoLars: Комбинация Lasso и LARS для регрессии;
    11. LassoLarsCV: Кросс-валидация для LassoLars-регрессии;
    12. LassoLarsIC: Информационный критерий (IC) для подбора параметров LassoLars;
    13. LinearRegression: Простая линейная регрессия;
    14. Ridge: Линейная регрессия с L2-регуляризацией;
    15. RidgeCV: Кросс-валидация для Ridge-регрессии;
    16. OrthogonalMatchingPursuit: Регрессия с ортогональным отбором признаков;
    17. PassiveAggressiveRegressor: Регрессия с пассивно-агрессивным подходом к обучению;
    18. QuantileRegressor: Регрессия квантилей;
    19. RANSACRegressor: Регрессия методом RANdom SAmple Consensus;
    20. TheilSenRegressor: Нелинейная регрессия, основанная на методе Theil-Sen.
    21. LinearSVR: Линейная регрессия для задачи опорных векторов;
    22. MLPRegressor: Регрессия с использованием многослойного персептрона;
    23. PLSRegression: Регрессия методом Partial Least Squares;
    24. TweedieRegressor: Регрессия на основе распределения Твиди;
    25. PoissonRegressor: Регрессия для моделирования данных с распределением Пуассона;
    26. RadiusNeighborsRegressor: Регрессия на основе радиуса ближайших соседей;
    27. KNeighborsRegressor: Регрессия на основе ближайших соседей;
    28. GaussianProcessRegressor: Регрессия на основе гауссовских процессов;
    29. GammaRegressor: Регрессия для моделирования данных с гамма-распределением;
    30. SGDRegressor: Регрессия на основе стохастического градиентного спуска;
    31. AdaBoostRegressor: Регрессия с использованием алгоритма AdaBoost;
    32. BaggingRegressor: Регрессия с использованием метода Bagging;
    33. DecisionTreeRegressor: Регрессия с деревом решений;
    34. ExtraTreeRegressor: Регрессия с дополнительным деревом решений;
    35. ExtraTreesRegressor: Регрессия с дополнительными деревьями решений;
    36. NuSVR: Непрерывная линейная регрессия для опорных векторов (SVR);
    37. RandomForestRegressor: Регрессия с ансамблем деревьев решений (Random Forest);
    38. GradientBoostingRegressor: Регрессия с градиентным бустингом;
    39. HistGradientBoostingRegressor: Регрессия с гистограммным градиентным бустингом;
    40. SVR: Регрессия методом опорных векторов.

    3. Также исследовалась возможность конвертации регрессионных моделей в ONNX с расчетом в double.

    Серьезной проблемой при конвертации в ONNX модели с расчетом в double является ограничение ML-операторов ai.onnx.ml.LinearRegressor, ai.onnx.ml.SVMRegressor, ai.onnx.ml.TreeEnsembleRegressor: их параметры и выходное значение имеют тип float. Фактически это звенья понижения точности и их исполнение при расчетах с двойной точностью сомнительно. По этой причине в библиотеке ONNX Runtime не стали реализовывать некоторые операторы для ONNX-моделей с расчетом на double (в таких случаях возможны ошибки вида NOT_IMPLEMENTED :'Could not find an implementation for the node LinearRegressor:LinearRegressor(1), Could not find an implementation for SVMRegressor(1) node with name 'SVM' и т.п.). Таким образом, в рамках текущей спецификации ONNX полноценная работа в double для этих ML-операторов невозможна.

    Для моделей линейной регрессии конвертеру sklearn-onnx удалось обойти ограничение LinearRegressor: для ONNX-моделей double вместо него используются ONNX-операторы MatMul() и Add(). Благодаря такому подходу первые 30 моделей предыдущего списка успешно конвертируются в ONNX-модели с расчетом в double, и эти модели показывают сохранение точности оригинальных моделей в double.

    Однако для более сложных ML-операторов SVMRegressor и TreeEnsembleRegressor этого сделать пока не удалось, поэтому модели AdaBoostRegressor, BaggingRegressor, DecisionTreeRegressor, ExtraTreeRegressor, ExtraTreesRegressor, NuSVR, RandomForestRegressor, GradientBoostingRegressor, HistGradientBoostingRegressor и SVR сейчас доступны только в ONNX-моделях с расчетом во float.


    Заключение

    В статье рассмотрены 45 регрессионных моделей библиотеки Scikit-learn версии 1.3.2 и результаты их преобразования в формат ONNX для расчетах во float и double.

    Из всех рассмотренных моделей, 5 оказались сложными для конвертации в формат ONNX. Эти модели включают в себя DummyRegressor, KernelRidge, IsotonicRegression, PLSCanonical и CCA. Вероятно, их сложная структура или логика требуют дополнительной адаптации для успешной конвертации в формат ONNX.

    Остальные 40 регрессионных моделей были успешно преобразованы в формат ONNX для float. Из них 30 моделей также были успешно преобразованы в ONNX-формат для double и показали сохранение своей точности.

    По причине ограничения ML-операторов SVMRegressor и TreeEnsembleRegressor модели AdaBoostRegressor, BaggingRegressor, DecisionTreeRegressor, ExtraTreeRegressor, ExtraTreesRegressor, NuSVR, RandomForestRegressor, GradientBoostingRegressor, HistGradientBoostingRegressor и SVR в настоящее время доступны только в ONNX-моделях с расчетами во float.


    Все скрипты из статьи также доступны в публичном проекте MQL5\Shared Projects\Scikit.Regression.ONNX.