ArrayFromFP16関数の働き・役割
ArrayFromFP16関数は、ushort型の配列に格納された半精度浮動小数点数(FLOAT16やBFLOAT16)を、指定された形式のfloat型またはdouble型の配列に変換してコピーするために使用されます。この関数は、特にONNXモデル(Open Neural Network Exchange形式の機械学習モデル)の出力結果を取り扱う際に利用されます。
FLOAT16形式は16ビットを使用して数値を表すため、浮動小数点数の計算精度を犠牲にしながらも、データのサイズを小さくし、計算効率を向上させます。BFLOAT16も同様に16ビットを使用しますが、FLOAT16とは異なり、指数部(数値のスケールを示す部分)に多くのビットを割り当てることで異なる数値表現を行います。これらの形式は、特にディープラーニング(深層学習アルゴリズム)やニューラルネットワーク(脳神経回路を模したアルゴリズム)のトレーニングで高効率を発揮します。
ArrayFromFP16関数の引数について
ArrayFromFP16関数には、float型またはdouble型の配列にushort型の配列を変換してコピーする役割があります。この関数はオーバーロード関数で、float型とdouble型の2つの書式があります。
float型の書式
bool ArrayFromFP16(
const float& dst_array[], // コピー先の配列
const ushort& src_array[], // コピー元の配列
ENUM_FLOAT16_FORMAT fmt // 変換形式
);
double型の書式
bool ArrayFromFP16(
const double& dst_array[], // コピー先の配列
const ushort& src_array[], // コピー元の配列
ENUM_FLOAT16_FORMAT fmt // 変換形式
);
引数の説明
dst_array[]
float型またはdouble型の受信側配列を指定します。これは、ushort型のコピー元配列から変換された値がコピーされる配列です。
src_array[]
ushort型のコピー元配列を指定します。この配列には、半精度浮動小数点数(FLOAT16またはBFLOAT16)形式のデータが格納されています。
fmt
ENUM_FLOAT16_FORMAT型の変換形式を指定します。これは、FLOAT16またはBFLOAT16のいずれかを選択するために使用されます。
ArrayFromFP16関数を使う際の注意点
ArrayFromFP16関数は、主にONNXモデルのデータ処理に使われます。FLOAT16やBFLOAT16形式のデータを扱う場面で使用されるため、これらの形式が必要な状況か確認することが重要です。
FLOAT16は精度が低いため、精密な計算には不向きです。また、BFLOAT16はFLOAT16とは異なる形式で数値を表すため、使用する際にはその特性に注意が必要です。
ArrayFromFP16関数を使った公式リファレンスのサンプルコード
//+------------------------------------------------------------------+
//| 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関数を使用して、各インデックスでのデータをエキスパートログに出力しています。具体的には、以下の情報を出力しています。
test_data_float[i]
: 変換されたfloat型の値。ArrayToString(input_float16_values[i].uc)
: 16進数で表現されたFLOAT16データ(ただし、この部分はMQL5には存在しないため、このままコンパイルするとエラーが発生します)。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つの引数が渡されます。
model_handle
: 実行するONNXモデルのハンドル(識別子)。ONNX_NO_CONVERSION
: モデル実行時のオプション。ここではデータ変換を行わない設定になっています。input_float16_values
: モデルに渡される入力データ(FLOAT16型のデータが格納された配列)。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を返して関数を終了します。この戻り値によって、呼び出し元で処理が成功したかどうかを判断することができます。