【MQL5】ArrayToFP16関数について

MQL5リファレンス
スポンサーリンク
スポンサーリンク

ArrayToFP16関数の働き・役割

ArrayToFP16関数は、float型またはdouble型配列を、指定された形式で16ビットの浮動小数点数(ushort型)の配列に変換するために使用されます。この関数は、ENUM_FLOAT16_FORMATという列挙型を通じて、FLOAT16またはBFLOAT16のいずれかの形式でデータを変換します。特に、FLOAT16はディープラーニングのトレーニング時に使用される半精度の浮動小数点形式で、BFLOAT16はGoogleのTPUでの使用に特化して設計されています。

この関数は、MQL5ONNXモデル(深層学習モデル)の操作を行う際に使用されることが多く、効率的なデータ処理やモデル推論を実現するために重要な役割を果たします。

ArrayToFP16関数の引数について

ArrayToFP16関数は、2つの異なる書式(float型の配列double型配列を対象とするオーバーロード関数)を持ちます。

書式1: float型の配列を使用する場合

bool ArrayToFP16(
   const ushort&       dst_array[],       // 変換後の配列
   const float&        src_array[],       // 変換元の配列
   ENUM_FLOAT16_FORMAT fmt                // 変換形式
);

書式2: double型の配列を使用する場合

bool ArrayToFP16(
   const ushort&       dst_array[],       // 変換後の配列
   const double&       src_array[],       // 変換元の配列
   ENUM_FLOAT16_FORMAT fmt                // 変換形式
);

各引数の説明

dst_array[]

dst_arrayは変換後の配列です。ushort型配列として宣言され、src_arrayで指定されたfloat型またはdouble型のデータを指定されたフォーマット(FLOAT16またはBFLOAT16)に変換して格納します。

src_array[]

src_arrayは変換元の配列です。float型またはdouble型配列で、変換される元データを含みます。この配列の内容がFLOAT16またはBFLOAT16形式に変換され、dst_arrayにコピーされます。

fmt

fmtはENUM_FLOAT16_FORMAT型の列挙型で、どの形式で変換を行うかを指定します。次の2つの選択肢があります:

  • FLOAT16: 半精度浮動小数点形式。通常、深層学習やGPUでの高速計算に使用されます。
  • BFLOAT16: 16ビット形式の浮動小数点形式で、主にTPUやAI計算の効率化に使われます。

ENUM_FLOAT16_FORMATについて

ENUM_FLOAT16_FORMATは、ArrayToFP16関数で使用される浮動小数点形式を指定するための列挙型です。この列挙型には、16ビットの浮動小数点形式が2つ含まれています。それぞれの形式は、異なる計算目的や処理方法に適した特性を持ち、データの変換時に選択されます。

FLOAT_FP16とFLOAT_BFP16の違い

FLOAT_FP16(半精度浮動小数点数)は、標準的な16ビットの形式で、通常は高い精度が求められる場合に使用されます。しかし、計算リソースやストレージを多く消費します。

一方、FLOAT_BFP16は、bf16(Brain Floating Point 16)という特別な形式で、データ処理の効率を重視した設計です。この形式は計算のパフォーマンスが優れているものの、FLOAT_FP16に比べて精度が劣る可能性があります。

ENUM_FLOAT16_FORMATの各識別子について

FLOAT_FP16

FLOAT_FP16は、標準的な16ビットの浮動小数点形式で、しばしば「半精度」形式とも呼ばれます。この形式は、16ビットのデータで小数点数を表現するため、精度と計算リソースのバランスを取った表現です。ディープラーニングのトレーニング時に大量のデータを扱う場合に、計算精度を保持しながらもデータサイズを小さくする利点があります。

FLOAT_BFP16

FLOAT_BFP16は、Brain Floating Point 16の略で、特にGoogleのTensor Processing Unit(TPU)で使用される形式です。この形式では、8ビットが指数部分に割り当てられ、残りの7ビットで仮数を表します。この設計は、高速で効率的な計算を可能にし、大規模なデータセットの処理に適していますが、FLOAT_FP16に比べて精度がやや低い点が特徴です。

ArrayToFP16関数の戻り値について

ArrayToFP16関数は、配列の変換が成功したかどうかを示すブール型の値を返します。戻り値の意味は次の通りです。

  • true: 変換が成功したことを示します。指定されたfloat型またはdouble型配列が、正しくushort型配列に変換されました。
  • false: 変換が失敗したことを示します。この場合、エラーの原因としては、配列のサイズの不一致や無効なフォーマットが指定されたことが考えられます。エラーが発生した場合は、GetLastError関数を使用して詳細なエラーコードを確認することが推奨されます。

ArrayToFP16関数を使う際の注意点

変換先と変換元の配列のサイズに注意する必要があります。配列サイズが一致していない場合、変換に失敗し、falseが返されます。変換元(float型またはdouble型)と変換先(ushort型)の配列サイズが正しいことを確認してください。

ENUM_FLOAT16_FORMATの選択にも注意が必要です。使用するフォーマット(FLOAT16またはBFLOAT16)によって、データの精度や処理速度が異なります。FLOAT16は精度が高い一方で、BFLOAT16は処理効率が高いですが、精度は低くなります。どちらの形式が適しているかは、使用するアルゴリズムや処理内容に依存します。

MQL5におけるFLOAT16およびBFLOAT16形式は主にONNXモデルの操作に使用されることが前提とされています。一般的なトレードアルゴリズムでは必要とされない場合もあるため、深層学習モデルやAI関連の処理に使用する場合に適しています。

ArrayToFP16関数を使った公式リファレンスのサンプルコード

//+------------------------------------------------------------------+
//| RunCastFloat16ToDouble関数                                       |
//| ONNXモデルを使用して、double型配列をFLOAT16型に変換し、          |
//| 再度double型に変換してエラーの差を確認するサンプル              |
//+------------------------------------------------------------------+
bool RunCastFloat16ToDouble(long model_handle)
 {
  // 関数名をエキスパートログに出力
  PrintFormat("test=%s",__FUNCTION__);

  // double型の配列test_dataを作成し、初期化(1〜12の数値を格納)
  double test_data[12] = {1,2,3,4,5,6,7,8,9,10,11,12};

  // ushort型の配列data_uint16を作成(FLOAT16型変換結果を格納するための配列)
  ushort data_uint16[12];

  // ArrayToFP16関数を使用して、double型配列をFLOAT16型に変換
  if(!ArrayToFP16(data_uint16, test_data, FLOAT_FP16))
    {
    // エラーが発生した場合、エラーコードをエキスパートログに出力し、falseを返す
    Print("error in ArrayToFP16. error code=", GetLastError());
    return(false);
    }

  // 変換前のdouble型配列をエキスパートログに出力
  Print("test array:");
  ArrayPrint(test_data);

  // 変換後のFLOAT16型のushort配列をエキスパートログに出力
  Print("ArrayToFP16:");
  ArrayPrint(data_uint16);

  // FLOAT16型データを保持するための配列input_float16_valuesを作成(サイズは3x4=12)
  U<ushort> input_float16_values[3*4];

  // double型データを保持するための配列output_double_valuesを作成(同じく3x4=12)
  U<double> output_double_values[3*4];

  // 浮動小数点数のデータを格納する配列test_data_floatを作成(後で使用)
  float test_data_float[];

  // ArrayFromFP16関数を使用して、FLOAT16型からfloat型に再変換
  if(!ArrayFromFP16(test_data_float, data_uint16, FLOAT_FP16))
    {
    // エラーが発生した場合、エラーコードをエキスパートログに出力し、falseを返す
    Print("error in ArrayFromFP16. error code=", GetLastError());
    return(false);
    }

  // 変換後のushort型配列をfloat型配列に変換し、それぞれの値をログ出力
  for(int i = 0; i < 12; i++)
    {
    input_float16_values[i].value = data_uint16[i]; // ushort型の値を格納
    // それぞれのインデックスでの値、16進数でのFLOAT16表現、ushort値をエキスパートログに出力
    PrintFormat("%d input value =%f  Hex float16 = %s  ushort value=%d",
                i, test_data_float[i], ArrayToString(input_float16_values[i].uc), input_float16_values[i].value);
    }

  // ONNXの入力配列をログ出力
  Print("ONNX input array:");
  ArrayPrint(input_float16_values);

  // OnnxRun関数を使用してONNXモデルの実行を行い、結果を出力配列に格納
  bool res = OnnxRun(model_handle, ONNX_NO_CONVERSION, input_float16_values, output_double_values);

  // 実行に失敗した場合、エラーコードをログ出力し、falseを返す
  if(!res)
    {
    PrintFormat("error in OnnxRun. error code=%d", GetLastError());
    return(false);
    }

  // ONNXモデルの出力配列をエキスパートログに出力
  Print("ONNX output array:");
  ArrayPrint(output_double_values);

  // 変換結果と元のデータの差を計算し、総誤差を表示
  double sum_error = 0.0;
  for(int i = 0; i < 12; i++)
    {
    double delta = test_data[i] - output_double_values[i].value; // 元の値と出力値の差
    sum_error += MathAbs(delta); // 絶対誤差を積算
    // 各インデックスごとの結果、差分をログ出力
    PrintFormat("%d output double %f = %s  difference=%f",
                i, output_double_values[i].value, ArrayToString(output_double_values[i].uc), delta);
    }

  // 総誤差をエキスパートログに出力
  PrintFormat("test=%s   sum_error=%f", __FUNCTION__, sum_error);

  // 関数の実行が成功したためtrueを返す
  return(true);
 }

コンパイルエラーに関する注意

上記のサンプルコードは、ONNXモデルを使用してdouble型配列をFLOAT16型に変換し、再度double型に戻して誤差を確認する内容となっています。

しかし、このコードには、MQL5標準のライブラリに存在しないデータ型関数が使用されています。具体的には、U<ushort>やU<double>という未定義の型、およびArrayToString関数という関数が含まれており、これらはそのままではコンパイルエラーを引き起こします。

U の文法的役割は、何らかのユーザー定義型またはテンプレートとして使用されることが意図されていた可能性があります。たとえば、浮動小数点のデータをushort型などの整数型に変換したり、それらを管理するためのカスタムクラスであることが考えられますが、このコード内では明示的な定義がないため、エラーが発生してしまいます。

このサンプルコードは、これらの型や関数が未定義であるため、適切に修正する必要があることを念頭に置いてください。

※テンプレート関数についての詳細は↓の記事をご覧ください。

サンプルコード解説1

//+------------------------------------------------------------------+
//| RunCastFloat16ToDouble関数                                       |
//| ONNXモデルを使用して、double型配列をFLOAT16型に変換し、          |
//| 再度double型に変換してエラーの差を確認するサンプル              |
//+------------------------------------------------------------------+
bool RunCastFloat16ToDouble(long model_handle)
 {
  // 関数名をエキスパートログに出力
  PrintFormat("test=%s",__FUNCTION__);

  // double型の配列test_dataを作成し、初期化(1〜12の数値を格納)
  double test_data[12] = {1,2,3,4,5,6,7,8,9,10,11,12};

  // ushort型の配列data_uint16を作成(FLOAT16型変換結果を格納するための配列)
  ushort data_uint16[12];

このセクションでは、サンプルコードの冒頭部分を解説します。このコードは、RunCastFloat16ToDoubleという関数を使用して、double型のデータをFLOAT16型に変換し、再度double型に戻して誤差を確認するためのものです。。

関数の定義と目的

RunCastFloat16ToDouble関数は、ONNXモデルを操作する際に使用されるサンプルです。model_handleという引数を受け取り、ONNXモデルの実行に必要な操作を行います。この関数の目的は、double型配列をFLOAT16型に変換し、最終的にdouble型に戻して変換による誤差を確認することです。

関数名のログ出力

最初に、PrintFormat関数を使用して、関数名をエキスパートログに出力しています。__FUNCTION__は、現在実行中の関数名を取得する特別な識別子です。これにより、関数が呼び出された際に、どの関数が実行されているかをログに記録することができます。

double型の配列の初期化

次に、double型配列test_dataを作成し、1から12までの数値を初期化しています。この配列は、後にFLOAT16型に変換されるデータの元となります。ここでは12個の値を用意しており、これが変換の対象となります。

ushort型の配列の作成

ushort型配列data_uint16は、FLOAT16型に変換されたデータを格納するために用意されています。FLOAT16形式は16ビットの浮動小数点形式であり、ushort(符号なし16ビット整数)に変換結果を格納します。この配列には、元のdouble型データをFLOAT16形式に変換した後のデータが格納されます。

この部分では、関数の初期設定を行い、double型データをFLOAT16型に変換するための準備をしています。

サンプルコード解説2

  // ArrayToFP16関数を使用して、double型配列をFLOAT16型に変換
  if(!ArrayToFP16(data_uint16, test_data, FLOAT_FP16))
    {
    // エラーが発生した場合、エラーコードをエキスパートログに出力し、falseを返す
    Print("error in ArrayToFP16. error code=", GetLastError());
    return(false);
    }

  // 変換前のdouble型配列をエキスパートログに出力
  Print("test array:");
  ArrayPrint(test_data);

  // 変換後のFLOAT16型のushort配列をエキスパートログに出力
  Print("ArrayToFP16:");
  ArrayPrint(data_uint16);

このセクションでは、ArrayToFP16関数を使用して、double型配列をFLOAT16型に変換し、その結果をエキスパートログに出力する部分について解説します。

ArrayToFP16 関数を使用した変換

最初に、ArrayToFP16関数を使用して、double型配列であるtest_dataをFLOAT16型に変換し、その結果をushort型配列data_uint16に格納しています。この関数は、double型またはfloat型の配列を受け取り、指定したフォーマット(ここではFLOAT_FP16)に変換する役割を持っています。変換が成功すれば次に進みますが、失敗した場合は、エラーコードをエキスパートログに出力し、関数はfalseを返して終了します。

エラー処理

もし変換に失敗した場合、Print関数を使用してエラーメッセージとGetLastError関数によるエラーコードをエキスパートログに出力します。このようにして、何らかの問題が発生した場合でも、原因をエラーログから確認できるようになっています。

配列の出力

変換が成功した場合、まず変換前のtest_data(double型配列)をエキスパートログに出力します。これにより、元のデータがどのような状態であったかを確認できます。その後、変換後のFLOAT16型データが格納されたdata_uint16(ushort型配列)を同様にログに出力します。ArrayPrint関数を使用して配列の内容全体を出力しているため、変換の前後のデータを比較することができます。

サンプルコード解説3

  // FLOAT16型データを保持するための配列input_float16_valuesを作成(サイズは3x4=12)
  U<ushort> input_float16_values[3*4];

  // double型データを保持するための配列output_double_valuesを作成(同じく3x4=12)
  U<double> output_double_values[3*4];

  // 浮動小数点数のデータを格納する配列test_data_floatを作成(後で使用)
  float test_data_float[];

  // ArrayFromFP16関数を使用して、FLOAT16型からfloat型に再変換
  if(!ArrayFromFP16(test_data_float, data_uint16, FLOAT_FP16))
    {
    // エラーが発生した場合、エラーコードをエキスパートログに出力し、falseを返す
    Print("error in ArrayFromFP16. error code=", GetLastError());
    return(false);
    }

このセクションでは、FLOAT16型データを再びfloat型およびdouble型に変換するための準備と処理について解説します。

FLOAT16型データを保持する配列の作成

まず、FLOAT16型データを保持するために、ushort型を保持する配列input_float16_valuesを作成しています。この配列は、変換されたFLOAT16データを後で処理するために必要です。この例では、3×4=12というサイズで作成されており、3行4列のデータが格納されることを示しています。

double型データを保持する配列の作成

次に、double型のデータを保持するための配列output_double_valuesを同様に作成しています。この配列も同じく12個の要素を持ち、後でFLOAT16から再変換されたdouble型データが格納されます。

float型配列の作成

さらに、浮動小数点数(float型)のデータを格納するための配列test_data_floatも作成しています。この配列は、後でFLOAT16からfloat型に再変換されたデータを格納するために使用されますが、この時点では配列のサイズや内容はまだ指定されていません。

ArrayFromFP16関数による再変換

次に、ArrayFromFP16関数を使用して、FLOAT16型データを再びfloat型データに変換しています。この関数は、前に変換されたFLOAT16型のデータを使用して、元のfloat型データに戻す役割を果たします。もし再変換が成功すれば次に進みますが、失敗した場合には、Print関数を使用してエラーメッセージとGetLastError関数によるエラーコードをエキスパートログに出力し、falseを返して関数を終了します。

この処理により、再び使用可能なfloat型データを取り出すことが可能になり、次の処理に進む準備が整います。

サンプルコード解説4

// 変換後のushort型配列をfloat型配列に変換し、それぞれの値をログ出力
  for(int i = 0; i < 12; i++)
    {
    input_float16_values[i].value = data_uint16[i]; // ushort型の値を格納
    // それぞれのインデックスでの値、16進数でのFLOAT16表現、ushort値をエキスパートログに出力
    PrintFormat("%d input value =%f  Hex float16 = %s  ushort value=%d",
                i, test_data_float[i], ArrayToString(input_float16_values[i].uc), input_float16_values[i].value);
    }

  // ONNXの入力配列をログ出力
  Print("ONNX input array:");
  ArrayPrint(input_float16_values);

このセクションでは、FLOAT16型データをfloat型に変換した後、各値をログに出力する処理について解説します。

ushort型配列をfloat型配列に変換

まず、forループを使用して、ushort型配列data_uint16をfloat型の配列に変換しています。ループは配列の全要素(ここでは12個)を対象にしており、各インデックスに対して、input_float16_values配列要素ushort型の値を格納しています。この処理により、ushort型のFLOAT16データが順番に変換され、input_float16_values配列に保持されます。

各値をエキスパートログに出力

続いて、PrintFormat関数を使用して、各インデックスでのデータをエキスパートログに出力しています。具体的には、以下の情報を出力しています。

  1. test_data_float[i]: 変換されたfloat型の値。
  2. ArrayToString(input_float16_values[i].uc): 16進数で表現されたFLOAT16データ(ただし、この部分はMQL5には存在しないため、このままコンパイルするとエラーが発生します)。
  3. input_float16_values[i].value: 元のushort型の値。

これにより、変換されたデータを詳細に確認することができ、変換が正しく行われたかどうかをエキスパートログで確認できるようになっています。

ONNXの入力配列をログ出力

最後に、Print関数を使用して、ONNXモデルの入力として使用されるFLOAT16データ(input_float16_values配列)をログに出力しています。ArrayPrint関数を使用することで、配列全体を一度にログに出力し、データ内容を確認できます。

これにより、データの変換後、ONNXモデルに渡される前の状態をログで確認でき、後続の処理で問題がないかどうかを調査するのに役立ちます。

サンプルコード解説5

  // OnnxRun関数を使用してONNXモデルの実行を行い、結果を出力配列に格納
  bool res = OnnxRun(model_handle, ONNX_NO_CONVERSION, input_float16_values, output_double_values);

  // 実行に失敗した場合、エラーコードをログ出力し、falseを返す
  if(!res)
    {
    PrintFormat("error in OnnxRun. error code=%d", GetLastError());
    return(false);
    }

  // ONNXモデルの出力配列をエキスパートログに出力
  Print("ONNX output array:");
  ArrayPrint(output_double_values);

このセクションでは、ONNXモデルを実行し、その結果を処理する部分について解説します。

OnnxRun関数の呼び出し

OnnxRun関数を呼び出して、ONNXモデルの実行を行います。この関数には4つの引数が渡されます。

  1. model_handle: 実行するONNXモデルのハンドル識別子)。
  2. ONNX_NO_CONVERSION: モデル実行時のオプション。ここではデータ変換を行わない設定になっています。
  3. input_float16_values: モデルに渡される入力データ(FLOAT16型のデータが格納された配列)。
  4. output_double_values: 実行結果が格納される配列(ここではdouble型配列)。

ONNXモデルが実行されると、結果はoutput_double_values配列に格納されます。この処理により、FLOAT16型の入力データに基づいてモデルが推論を行い、double型の結果を得ることができます。

実行結果のエラーハンドリング

OnnxRun関数の実行が失敗した場合、resがfalseを返すので、その場合にはPrintFormat関数を使用してエラーメッセージとエラーコードをログに出力します。エラーコードはGetLastError関数によって取得され、これを確認することで、何が原因で失敗したのかを特定できます。失敗した場合は、falseを返して関数を終了させます。

このようにして、ONNXモデルの実行が正常に行われたかどうかをチェックし、必要に応じてエラーログを残すことができます。

サンプルコード解説6

// ONNXモデルの出力配列をエキスパートログに出力
  Print("ONNX output array:");
  ArrayPrint(output_double_values);

  // 変換結果と元のデータの差を計算し、総誤差を表示
  double sum_error = 0.0;

このセクションでは、ONNXモデルからの出力結果をログに出力し、その結果と元のデータの誤差を計算する処理について解説します。

ONNXモデルの出力配列をログ出力

まず、ONNXモデルの実行後に得られた結果、つまりoutput_double_values配列をエキスパートログに出力しています。Print関数を使用して、”ONNXoutput array”というメッセージをログに表示した後、ArrayPrint関数を使用して配列の内容全体をログに出力します。この操作によって、ONNXモデルが返した推論結果がどのようなデータであるかを確認できるようになっています。

変換結果と元データの誤差計算

次に、ONNXモデルの出力結果と元のデータ(test_data)の間の差を計算する処理を行います。ここでは、sum_errorという変数を使用して、全てのデータの誤差を累積します。誤差を確認することにより、変換処理やモデルの推論がどれだけ正確に行われたかを測定できます。

この段階で、出力結果と元のデータの差異を視覚的に確認できるようになり、推論結果の精度を評価するための基礎が整います。

サンプルコード解説7

for(int i = 0; i < 12; i++)
    {
    double delta = test_data[i] - output_double_values[i].value; // 元の値と出力値の差
    sum_error += MathAbs(delta); // 絶対誤差を積算
    // 各インデックスごとの結果、差分をログ出力
    PrintFormat("%d output double %f = %s  difference=%f",
                i, output_double_values[i].value, ArrayToString(output_double_values[i].uc), delta);
    }

  // 総誤差をエキスパートログに出力
  PrintFormat("test=%s   sum_error=%f", __FUNCTION__, sum_error);

  // 関数の実行が成功したためtrueを返す
  return(true);
 }

このセクションでは、ONNXモデルの出力結果と元のデータの差を計算し、その誤差をログに出力する処理について解説します。

元のデータと出力結果の差の計算

forループを使用して、元のデータ(test_data)とONNXモデルの出力結果(output_double_values)の差を計算しています。各インデックスに対して以下の処理が行われます。

  • delta変数に、元のデータと出力結果の差を格納しています。この差が、変換や推論によってどれだけの誤差が生じたかを示します。
  • sum_error変数に、絶対誤差を累積しています。MathAbs関数を使用して絶対値を計算し、誤差が正負に関わらず正確に積算されるようにしています。これにより、全体の誤差の総和が得られます。

各インデックスの結果をログ出力

インデックスでの出力結果、16進数でのFLOAT16表現(ただし、MQL5にはArrayToString関数は存在しないため、コンパイルエラーが発生します)、そして元のデータとの差分をログに出力しています。PrintFormat関数を使用して、インデックスごとの詳細な情報をエキスパートログに記録します。

総誤差の出力

すべてのインデックスでの誤差が計算された後、sum_errorに累積された総誤差をエキスパートログに出力します。これにより、全体としてどれだけの誤差が生じたかを確認できます。

関数の終了と戻り値

最後に、関数の実行が成功したことを示すためにtrueを返して関数を終了します。この戻り値によって、呼び出し元で処理が成功したかどうかを判断することができます。

タイトルとURLをコピーしました