モデルの実行

MQL5でONNXモデルを実行するには、次の3つの手順を実行します。

  1. OnnxCreate関数を使用して*.onnxファイルからモデルを読み込むか、OnnxCreateFromBufferを使用して配列からモデルを読み込みます。
  2. OnnxSetInputShape関数とOnnxSetOutputShape関数を使用して入出力データの形状を指定します。
  3. OnnxRun関数を使用してモデルを実行し、関連する入出力パラメータを渡します。
  4. 必要に応じて、OnnxRelease関数を使用してモデル操作を終了できます。

 

ONNXモデルを作成するときは、https://github.com/microsoft/onnxruntime/blob/rel-1.14.0/docs/OperatorKernels.mdで説明されている既存の制限や規制を考慮する必要があります。

このような制限の例のいくつかを以下に示します。

操作

対応データ型

ReduceSum

tensor(double)、tensor(float)、tensor(int32)、tensor(int64)

Mul

tensor(bfloat16)、tensor(double)、tensor(float)、tensor(float16)、tensor(int32)、tensor(int64)、tensor(uint32)、tensor(uint64)

 

以下は、公開プロジェクトONNX.Price.PredictionのMQL5 コード例です。

const long   ExtOutputShape[] = {1,1};   // モデルの出力形状
const long   ExtInputShape [] = {1,10,4}; // モデルの入力形状
#resource "Python/model.onnx" as uchar ExtModel[]// リソースとしてのモデル
//+------------------------------------------------------------------+
//| スクリプトプログラム開始関数                                              |
//+------------------------------------------------------------------+
int OnStart(void)
 {
  matrix rates;
//--- 10バーを取得する
  if(!rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,2,10))
    return(-1);
//--- 一連のOHLCベクトルを入力する
  matrix x_norm=rates.Transpose();
  vector m=x_norm.Mean(0);              
  vector s=x_norm.Std(0);
  matrix mm(10,4);
  matrix ms(10,4);
//--- 正規化行列に入力する
  for(int i=0; i<10; i++)
    {
    mm.Row(m,i);
    ms.Row(s,i);
    }
//--- 入力データを正規化する
  x_norm-=mm;
  x_norm/=ms;
//--- モデルを作成する
  long handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
//--- 入力データの形状を指定する
  if(!OnnxSetInputShape(handle,0,ExtInputShape))
    {
    Print("OnnxSetInputShape failed, error ",GetLastError());
    OnnxRelease(handle);
    return(-1);
    }
//--- 出力データの形状を指定する
  if(!OnnxSetOutputShape(handle,0,ExtOutputShape))
    {
    Print("OnnxSetOutputShape failed, error ",GetLastError());
    OnnxRelease(handle);
    return(-1);
    }
//--- 正規化された入力データをfloat型に変換する
  matrixf x_normf;
  x_normf.Assign(x_norm);
//--- ここでモデルの出力データ、つまり価格予測を取得する
  vectorf y_norm(1);
//--- モデルを実行する
  if(!OnnxRun(handle,ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION,x_normf,y_norm))
    {
    Print("OnnxRun failed, error ",GetLastError());
    OnnxRelease(handle);
    return(-1);
    }
//--- モデルの出力値をログに出力する
  Print(y_norm);
//--- 逆変換を行って予測価格を取得する
  double y_pred=y_norm[0]*s[3]+m[3];
  Print("price predicted:",y_pred);
//--- 操作を完成する
  OnnxRelease(handle);
  return(0);
 }

スクリプト実行例:

ONNX: Creating and using per session threadpools since use_per_session_threads_ is true
ONNX: Dynamic block base set to 0
ONNX: Initializing session.
ONNXAdding default CPU execution provider.
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Use DeviceBasedPartition as default
ONNX: Saving initialized tensors.
ONNX: Done saving initialized tensors
ONNX: Session successfully initialized.
[0.28188983]
predicted 1.0559258806393044

MetaTrader 5ターミナルは、計算に最適なエグゼキュータ(ONNXランタイム実行プロバイダ)を選択しました。この例では、モデルはCPU上で実行されました。

スクリプトを変更して、直前の10バーの値に基づいて行われた終値予測の成功率を計算してみましょう。

#resource "Python/model.onnx" as uchar ExtModel[]// リソースとしてのモデル
 
#define TESTS 10000 // テストデータセットの数
//+------------------------------------------------------------------+
//| スクリプトプログラム開始関数                                              |
//+------------------------------------------------------------------+
int OnStart()
 {
//--- モデルを作成する
  long session_handle=OnnxCreateFromBuffer(ExtModel,ONNX_DEBUG_LOGS);
  if(session_handle==INVALID_HANDLE)
    {
    Print("Cannot create model. Error ",GetLastError());
    return(-1);
    }
 
//--- 入力テンソルサイズがモデルに定義されていないため、明示的に指定する
//--- 最初のインデックスはバッチサイズ、2番目のインデックスはシリーズサイズ、3番目のインデックスはシリーズ数(OHLC)
  const long input_shape[]={1,10,4};
  if(!OnnxSetInputShape(session_handle,0,input_shape))
    {
    Print("OnnxSetInputShape error ",GetLastError());
    return(-2);
    }
 
//--- 出力テンソルサイズがモデルに定義されていないため、明示的に指定する
//--- 最初のインデックスはバッチサイズであり、入力テンソルのバッチサイズと一致する必要がある
//--- 2番目のインデックスは予測価格の数(ここでは終値のみを予測)
  const long output_shape[]={1,1};
  if(!OnnxSetOutputShape(session_handle,0,output_shape))
    {
    Print("OnnxSetOutputShape error ",GetLastError());
    return(-3);
    }
//--- テストを実行する
  vector closes(TESTS);     // 検証価格を保存するためのベクトル
  vector predicts(TESTS);   // 得られた予測を保存するためのベクトル
  vector prev_closes(TESTS); // 前の価格を保存するベクトル
 
  matrix rates;             // OHLCシリーズを取得するための行列
  matrix splitted[2];       // シリーズをテストと検証に分割するための2つの部分行列
  ulong parts[]={10,1};     // 分割された部分行列のサイズ
 
//--- 前のバーから開始
  for(int i=1; i<=TESTS; i++)
    {
    //--- 11バーを取得する
    rates.CopyRates("EURUSD",PERIOD_H1,COPY_RATES_OHLC,i,11);
    //--- 行列をテストと検証に分割する
    rates.Vsplit(parts,splitted);
    //--- 検証行列から終値を取得する
    closes[i-1]=splitted[1][3][0];
    //--- last Close in the tested series
    prev_closes[i-1]=splitted[0][3][9];
 
    //--- 10バーのテスト行列をテストに提出する
    predicts[i-1]=PricePredictionTest(session_handle,splitted[0]);
    //--- ランタイムエラー
    if(predicts[i-1]<=0)
       {
        OnnxRelease(session_handle);
        return(-4);
       }
    }
//--- 操作を完成する
  OnnxRelease(session_handle);
//--- 価格変動が正しく予測されたかどうかを評価する
  int   right_directions=0;
  vector delta_predicts=prev_closes-predicts;
  vector delta_actuals=prev_closes-closes;
 
  for(int i=0; i<TESTS; i++)
    if((delta_predicts[i]>0 && delta_actuals[i]>0) || (delta_predicts[i]<0 && delta_actuals[i]<0))
        right_directions++;
  PrintFormat("right direction predictions = %.2f%%",(right_directions*100.0)/double(TESTS));
//---
  return(0);
 }
//+------------------------------------------------------------------+
//|  データを準備してモデルを実行する                                         |
//+------------------------------------------------------------------+
double PricePredictionTest(const long session_handle,matrix& rates)
 {
  static matrixf input_data(10,4); // 変換された入力の行列
  static vectorf output_data(1);   // 結果を受け取るベクトル
  static matrix mm(10,4);         // 水平ベクトルMeanの行列
  static matrix ms(10,4);         // 水平ベクトルStdの行列
 
//--- 一連のOHLC垂直ベクトルをモデルに入力する必要がある
  matrix x_norm=rates.Transpose();
//--- 価格を正規化する
  vector m=x_norm.Mean(0);
  vector s=x_norm.Std(0);
  for(int i=0; i<10; i++)
    {
    mm.Row(m,i);
    ms.Row(s,i);
    }
  x_norm-=mm;
  x_norm/=ms;
 
//--- モデルを実行する
  input_data.Assign(x_norm);
  if(!OnnxRun(session_handle,ONNX_DEBUG_LOGS,input_data,output_data))
    {
    Print("OnnxRun error ",GetLastError());
    return(0);
    }
//--- 出力値から価格を非正規化する
  double y_pred=output_data[0]*s[3]+m[3];
 
  return(y_pred);
 }

スクリプトを実行します。予測精度は約51%です

ONNX: Creating and using per session threadpools since use_per_session_threads_ is true
ONNX: Dynamic block base set to 0
ONNX: Initializing session.
ONNX: Adding default CPU execution provider.
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Total shared scalar initializer count: 0
ONNX: Total fused reshape node count: 0
ONNX: Use DeviceBasedPartition as default
ONNX: Saving initialized tensors.
ONNX: Done saving initialized tensors
ONNX: Session successfully initialized.
right direction predictions = 51.34 %