English Deutsch
preview
名義変数の順序符号化

名義変数の順序符号化

MetaTrader 5 | 17 12月 2024, 07:40
40 0
Francis Dube
Francis Dube

はじめに

機械学習でカテゴリデータを扱う場合、名義変数に遭遇することがよくあります。これらの変数はモデリングのための貴重な情報源となりえますが、多くの機械学習アルゴリズム、特に数値データのみで動作するアルゴリズムでは、これらを直接処理することはできません。これに対処するために、しばしば名義変数を順序変数に変換します。この記事では、名義変数を順序変数に変換する複雑さについて掘り下げます。このような変換の背後にある論理的根拠を探り、順序値を割り当てるための様々なテクニックについて議論し、それぞれのアプローチの潜在的な利点と欠点を強調します。さらに、主にPythonコードを使用してこれらのメソッドを実演する一方、純粋なMQL5で2つの汎用性の高い変換メソッドを実装します。


名義変数と順序変数について

名義変数は、カテゴリ間に固有の順序や順位が存在しないカテゴリデータを表します。金融時系列データセットに特化した例としては、以下のようなものがあります。

  • 価格バーの種類(ピンバー、スピニングトップ、ハンマーなど)
  • 曜日(例:月曜日、火曜日、水曜日)

これらの変数は純粋に定性的なもので、カテゴリ間の階層や順序は暗示されていません。例えば、ピンバーフォーメーションがスピニングトップより本質的に優れているわけではなく、強気バーが弱気バーより優れているわけでもありません。

数値計算では、任意の整数を異なるカテゴリに割り当てるのが一般的です。しかし、これらの整数を機械学習アルゴリズムの入力として使用する場合、割り当てられた値が元のデータによって伝えられた情報を歪めてしまう危険性があります。アルゴリズムは、たとえ意図していなくても、より大きな値が特定の関係や順位を意味すると誤って推論するかもしれません。

一方、順序変数は、カテゴリ間の固有の順序またはランキングを持つカテゴリデータです。例を挙げます。

  • トレンドの強さ(例:強いトレンド、穏やかなトレンド、弱いトレンド)
  • ボラティリティ(例:高ボラティリティ、低ボラティリティ)

この区別を理解することで、単に整数を名義カテゴリに割り当てることが必ずしも適切でない理由が明らかになります。よりよく理解するために、カテゴリ変数を含むデータセットを作成し、次のセクションでさまざまな方法を用いて順序形式に変換します。ビットコインの日次バーデータ(始値、高値、安値、終値)を収集し、翌日のリターンを予測するための名義変数を生成します。最初の名義変数は、バーを強気と弱気のいずれかに分類します。第二の名義変数は4つの異なるカテゴリを含み、ローソク足のボディサイズとフルサイズの比率に基づいてバーをグループ化します。最後の名義変数については、3つのカテゴリが作られます。

  • 現在のバーと直前のバーの両方が強気で、現在のバーの安値と高値が直前のバーの安値と高値を上回っている場合、現在のバーを「高値引け」とします。

高値引け2バーパターン

  • その逆のシナリオは「安値引け」と呼ばれます。

安値更新2バーパターン

  • その他の2バーパターンは3番目のカテゴリに入ります。

このデータセットを生成するPythonコードを以下に示します。

# Copyright 2024, MetaQuotes Ltd.
# https://www.mql5.com
# imports 
from datetime import datetime
import MetaTrader5 as mt5
import pandas as pd
import numpy  as np
import pytz
import os
from category_encoders import OrdinalEncoder, OneHotEncoder, BinaryEncoder,TargetEncoder, CountEncoder, HashingEncoder, LeaveOneOutEncoder,JamesSteinEncoder

if not mt5.initialize():
    print("initialize() failed ")
    mt5.shutdown()
    exit()
 
#set up timezone infomation   
tz=pytz.timezone("Etc/UTC")

#use time zone to set correct date for history data extraction
startdate = datetime(2023,12,31,hour=23,minute=59,second=59,tzinfo=tz)
stopdate = datetime(2017,12,31,hour=23,minute=59,second=59,tzinfo=tz)

#list the symbol 
symbol = "BTCUSD"

#get price history
prices = pd.DataFrame(mt5.copy_rates_range(symbol,mt5.TIMEFRAME_D1,stopdate,startdate))

if len(prices) < 1:
    print(" Error downloading rates history ")
    mt5.shutdown()
    exit()

#shutdown mt5 tether
mt5.shutdown()

#drop unnecessary columns
prices.drop(labels=["time","tick_volume","spread","real_volume"],axis=1,inplace=True)

#initialize categorical features
prices["bar_type"] = np.where(prices["close"]>=prices["open"],"bullish","bearish")
prices["body_type"] = np.empty((len(prices),),dtype='str')
prices["bar_pattern"] = np.empty((len(prices),),dtype='str')

#set feature values
for i in np.arange(len(prices)):
    bodyratio = np.abs(prices.iloc[i,3]-prices.iloc[i,0])/np.abs(prices.iloc[i,1]-prices.iloc[i,2])
    if bodyratio >= 0.75:
        prices.iloc[i,5] = ">=0.75"
    elif bodyratio < 0.75 and bodyratio >= 0.5:
        prices.iloc[i,5]=">=0.5<0.75"
    elif bodyratio < 0.5 and bodyratio >= 0.25:
        prices.iloc[i,5]=">=0.25<0.5"
    else:
        prices.iloc[i,5]="<0.25"
    if i < 1:
      prices.iloc[i,6] = None
      continue
    if(prices.iloc[i,4]=="bullish" and prices.iloc[i-1,4]=="bullish") and (prices.iloc[i,1]>prices.iloc[i-1,1]) and (prices.iloc[i,2]>prices.iloc[i-1,2]):
        prices.iloc[i,6] = "higherHigh"
    elif(prices.iloc[i,4]=="bearish" and prices.iloc[i-1,4]=="bearish") and (prices.iloc[i,2]<prices.iloc[i-1,2]) and (prices.iloc[i,1]<prices.iloc[i-1,1]):
        prices.iloc[i,6] = "lowerLow"
    else :
        prices.iloc[i,6] = "flat"
 
#calculate target
look_ahead = 1
prices["target"] = np.log(prices["close"])
prices["target"] = prices["target"].diff(look_ahead)
prices["target"] = prices["target"].shift(-look_ahead)              

#drop rows with NA values
prices.dropna(axis=0,inplace=True,ignore_index=True)

print("Full feature matrix \n",prices.head())

Pythonでは、カテゴリにはリテラル名が割り当てられているが、この後に続くMQL5のコードリストでは、カテゴリを区別するために整数が使われていることに注意してください。

//get relative shift of is and oos sets
   int trainstart,trainstop;
   trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate);
   trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate);
//check for errors from ibarshift calls
   if(trainstart<0 || trainstop<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//---set the size of the sample sets
   size_insample=(trainstop - trainstart) + 1;
//---check for input errors
   if(size_insample<=0)
     {
      Print("Invalid inputs ");
      return;
     }
//---
   if(!predictors.Resize(size_insample,3))
     {
      Print("ArrayResize error ",ErrorDescription(GetLastError()));
      return;
     }
//---
   if(!prices.CopyRates(SetSymbol,tf,COPY_RATES_VERTICAL|COPY_RATES_OHLC,TrainingSampleStartDate,TrainingSampleStopDate))
     {
      Print("Copyrates error ",ErrorDescription(GetLastError()));
      return;
     }
//---
   targets = log(prices.Col(3));
   targets = np::diff(targets);
//---
   double bodyratio = 0.0;
   for(ulong i = 0; i<prices.Rows(); i++)
     {
      if(prices[i][3]<prices[i][0])
         predictors[i][0] = 0.0;
      else
         predictors[i][0] = 1.0;

      bodyratio = MathAbs(prices[i][3]-prices[i][0])/MathAbs(prices[i][1]-prices[i][2]);

      if(bodyratio >=0.75)
         predictors[i][1] = 0.0;
      else
         if(bodyratio<0.75 && bodyratio>=0.5)
            predictors[i][1] = 1.0;
         else
            if(bodyratio<0.5 && bodyratio>=0.25)
               predictors[i][1] = 2.0;
            else
               predictors[i][1] = 3.0;

      if(i<1)
        {
         predictors[i][2] = 0.0;
         continue;
        }

      if(predictors[i][0]==1.0 && predictors[i-1][0]==1.0 && prices[i][1]>prices[i-1][1] && prices[i][2]>prices[i-1][2])
         predictors[i][2] = 2.0;
      else
         if(predictors[i][0]==0.0 && predictors[i-1][0]==0.0 && prices[i][2]<prices[i-1][2] && prices[i][1]>prices[i-1][1])
            predictors[i][2] = 1.0;
         else
            predictors[i][2] = 0.0;
     }

   targets = np::sliceVector(targets,1);

   prices = np::sliceMatrixRows(prices,1,predictors.Rows()-1);

   predictors = np::sliceMatrixRows(predictors,1,predictors.Rows()-1);

   matrix fullFeatureMatrix(predictors.Rows(),predictors.Cols()+prices.Cols());

   if(!np::matrixCopyCols(fullFeatureMatrix,prices,0,prices.Cols()) ||
      !np::matrixCopyCols(fullFeatureMatrix,predictors,prices.Cols()))
     {
      Print("Failed to merge matrices");
      return;
     }

データセットの一部を以下に示します。

特徴行列



なぜ名義変数を順序変数に変換するのか

決定木のような一部の機械学習アルゴリズムは、名義データを直接扱うことができます。しかし、他のモデル、特にロジスティック回帰やニューラルネットワークのような線形モデルは、数値入力を必要とします。名義変数を順序変数に変換することで、これらのモデルで解釈できるようになり、アルゴリズムがより効果的にデータから学習できるようになります。順序変数はカテゴリを表す一方で、明確な進行やランキングを提供し、アルゴリズムに関係を理解するためのより多くのコンテキストを与えます。名義変数がターゲット変数と実質的な情報を共有している場合、その測定レベルを上げることが有利になることが多いです。もし名義変数が意味のある数値を持っていれば、それをモデルの入力として直接使うことができます。しかし、値が本質的な数値的意味を持たない場合でも、ターゲット変数との関係に基づいて順序値を割り当てることができる場合が多いです。

名義変数を順序尺度に引き上げることで、カテゴリ間に順序や順位付けの感覚を導入します。これにより、変数とターゲットの間の基本的なパターンや関係を捉えるモデルの能力を高めることができます。理論的には、名義変数をターゲット変数と同じ測定レベルまで上げることは可能ですが、実際には順序尺度に変換するだけで十分なことがよくあります。このアプローチは、変数の情報量の保持とノイズの最小化のバランスをとるものです。以下のセクションでは、データの完全性を維持するための重要な考慮事項とともに、名義変数を順序形式に変換するための一般的なテクニックを探ります。



名義変数変換技術

まず、最も単純な方法である序列符号化から始めます。この方法では、単純に各カテゴリに整数値を割り当てます。前述したように、これはカテゴリにランキングを課すものです。もし実務者がデータに精通していて、カテゴリがターゲット変数にどのように関係するかを事前に知っていれば、この方法で十分でしょう。しかし、順序符号化は教師なし学習では使うべきではありません。なぜなら、順序が存在しないにもかかわらず、順序を暗示することによってバイアスが生じやすいからです。

名義変数のデータセットをPythonで変換するために、category_encodersパッケージを使います。本パッケージは幅広いカテゴリ変換の実装を提供し、ほとんどのタスクに適しています。読者はプロジェクトのGitHubリポジトリでより多くの情報を得ることができます。

変数を序数形式に変換するには、OrdinalEncoderオブジェクトが必要です。

#Ordinal encoding
ord_encoder = OrdinalEncoder(cols = ["bar_type","body_type","bar_pattern"])
ordinal_data = ord_encoder.fit_transform(prices)

print(" ordinal encoding\n ", ordinal_data.head())

以下は変換されたデータです。

順序符号化データ

教師なし学習アルゴリズムに適した変換技術には、バイナリ符号化、ワンホット符号化、周波数符号化などがあります。ワンホット符号化では、各カテゴリを2進数の列に変換し、カテゴリが存在する場合は「1」、存在しない場合は「0」で示します。この方法の主な欠点は、入力変数の数が大幅に増えることです。カテゴリ変数の各カテゴリごとに新しい変数が作成されます。例えば、1年の月を符号化するとしたら、11個の入力変数が追加されることになります。

OneHotEncoderオブジェクトは、category_encodersパッケージのワンホット符号化を扱います。

#One-Hot encoding
onehot_encoder = OneHotEncoder(cols = ["bar_type","body_type","bar_pattern"])
onehot_data = onehot_encoder.fit_transform(prices)

print(" ordinal encoding\n ", onehot_data.head())

以下は変換されたデータです。

OneHotエンコードデータ

バイナリ符号化は、特に多数のカテゴリを扱う場合、より効率的な選択肢です。この方法では、まず各カテゴリが固有の整数に変換され、次にその整数が2進数として表現されます。このバイナリ表現は複数の列にまたがっているため、ワンホット符号化に比べて列数が少なくなるのが一般的です。例えば、12ヶ月をエンコードするには、4つのバイナリ列が必要なだけです。バイナリ符号化は、カテゴリ変数が多くのユニークなカテゴリを持ち、入力変数の数を制限したいシナリオでうまく機能します。

BTCUSDデータセットをバイナリ符号化します。

#Binary encoding
binary_encoder = BinaryEncoder(cols = ["bar_type","body_type","bar_pattern"])
binary_data = binary_encoder.fit_transform(prices)

print(" binary encoding\n ", binary_data.head())

以下は変換されたデータです。

バイナリ符号化データ

周波数符号化は、各カテゴリをデータセット中の出現周波数に置き換えることで、カテゴリ変数を変換します。複数の列を作る代わりに、各カテゴリは、それが出現する周波数の割合またはカウントで置き換えられます。このアプローチは、カテゴリの周波数とターゲット変数の間に意味のある関係がある場合に有効で、貴重な情報をよりコンパクトな形で保存できるからです。しかし、データセットにおいて特定のカテゴリが支配的な場合、バイアスが生じる可能性があります。教師なし学習のシナリオでは、より複雑な特徴量エンジニアリングパイプラインの最初のステップとして使用されることが多いです。

ここでは、CountEncoderオブジェクトを使用します。

#Frequency encoding
freq_encoder = CountEncoder(cols = ["bar_type","body_type","bar_pattern"])
freq_data = freq_encoder.fit_transform(prices)

print(" frequency encoding\n ", freq_data.head())

以下は変換されたデータです。


周波数エンコードデータ

二値符号化、ワンホット符号化、周波数符号化は、学習結果に影響を与えるような不当な副作用をもたらすリスクなしに、ほとんどのカテゴリデータに適用できる汎用性の高いテクニックです。これらの変換は、ターゲット変数から独立しているという重要な特徴を共有しています。

しかし場合によっては、機械学習アルゴリズムは、変数とターゲットとの関連性を反映した変換から恩恵を受けるかもしれません。これらの方法は、ターゲット変数を活用してカテゴリデータを数値に変換し、ある程度の関連性を付与することで、モデルの予測力を高める可能性があります。

そのような方法の1つが、ターゲット符号化(平均符号化とも呼ばれる)です。このアプローチは、各カテゴリをそのカテゴリのターゲット変数の平均に置き換えます。例えば、ある銘柄の終値が高くなる可能性(バイナリターゲット)を予測する場合、取引高レンジのような名義変数の各カテゴリを、各レンジの平均終値確率に置き換えることができます。ターゲット符号化は、データセットの次元を増やすことなく有用な情報を統合するため、カーディナリティの高いカテゴリ変数に対して特に強力です。このテクニックは、カテゴリ変数とターゲット間の関係を捉えるのに役立ち、教師あり学習タスクに効果的です。カテゴリとターゲットに強い相関関係がある場合、ほとんどの場合効果的です。しかし、より良い汎化を確実にするために、過剰適合の緩和処置と組み合わせる必要があります。

#Target encoding
target_encoder = TargetEncoder(cols = ["bar_type","body_type","bar_pattern"])
target_data = target_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"])

print(" target encoding\n ", target_data.head())

以下は変換されたデータです。

ターゲット符号化データ

ターゲットに依存するもう1つの方法は、leave-one-out符号化です。これはターゲット符号化と同様に機能しますが、そのカテゴリの平均を計算する際に、現在の行のターゲット値を除外することで符号化を調整します。これは、特にデータセットが小さい場合や、特定のカテゴリが過剰に表現されている場合に、過剰適合を減らすのに役立ちます。Leave-one-out符号化は、変換が処理されている行から独立したままであることを保証し、それによって学習プロセスの完全性を維持します。

#LeaveOneOut encoding
oneout_encoder = LeaveOneOutEncoder(cols = ["bar_type","body_type","bar_pattern"])
oneout_data = oneout_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"])

print(" LeaveOneOut encoding\n ", oneout_data.head())

以下は変換されたデータです。

LeaveOneOutエンコードデータ

James-Stein符号化は、各カテゴリの利用可能なデータ量に応じて、カテゴリの目標平均の推定値を全体の平均に向かって縮小する符号化のベイズアプローチです。このテクニックは、カーディナリティの低いデータセットに特に有効です。ターゲット符号化やleave-one-out符号化のような従来の方法では、特に小規模なデータセットや、分布が著しく不均一なカテゴリを扱う場合に、過剰適合につながる可能性があります。全体平均に基づいてカテゴリ平均を調整することで、James-Stein符号化は、極端な値がモデルに過度に影響するリスクを軽減します。この結果、より安定したロバストな推定値が得られるので、疎なデータやオブザベーションの少ないカテゴリを扱う場合に効果的な代替手段となります。

#James Stein encoding
james_encoder = JamesSteinEncoder(cols = ["bar_type","body_type","bar_pattern"])
james_data = james_encoder.fit_transform(prices[["open","high","low","close","bar_type","body_type","bar_pattern"]], prices["target"])

print(" James Stein encoding\n ", james_data.head())

以下は変換されたデータです。

James Steinエンコードデータ

category_encodersライブラリは、さまざまなタイプのカテゴリデータや機械学習タスクに合わせた、多種多様な符号化技術を提供します。適切な符号化方法は、データの性質、使用される機械学習アルゴリズム、および手元のタスクの特定の要件によって異なります。まとめると、ワンホット符号化は、特に名義変数を扱う場合など、多くのユースケースに適した汎用性の高い手法です。ターゲット符号化、leave-one-out符号化、James-Stein符号化は、変数とターゲットの関係を強調する必要がある場合に採用されるべきです。最後に、バイナリ符号化とハッシュ符号化は、意味のある情報を保持したまま次元を減らすことを目的とする場合に有用なテクニックです。



MQL5における名義から序数への変換

このセクションでは、MQL5で2つの名義変数符号化メソッドを実装します。これらのメソッドは両方とも、ヘッダーファイルnom2ord.mqhで宣言されたクラスにカプセル化されています。COneHotEncoderクラスは、データセットのワンホット符号化を実装しており、ユーザーがよく知るべき2つの主要なメソッドを備えています。

//+------------------------------------------------------------------+
//| one hot encoder class                                            |
//+------------------------------------------------------------------+
class COneHotEncoder
  {
private:
   vector            m_mapping[];
   ulong             m_cat_cols[];
   ulong             m_vars,m_cols;
public:
   //+------------------------------------------------------------------+
   //|  Constructor                                                     |
   //+------------------------------------------------------------------+
                     COneHotEncoder(void)
     {
     }
   //+------------------------------------------------------------------+
   //|  Destructor                                                      |
   //+------------------------------------------------------------------+
                    ~COneHotEncoder(void)
     {
      ArrayFree(m_mapping);
     }
   //+------------------------------------------------------------------+
   //| map categorical features of a training dataset                   |
   //+------------------------------------------------------------------+
   bool              fit(matrix &in_data,ulong &cols[])
     {
      m_cols = in_data.Cols();
      matrix data = np::selectMatrixCols(in_data,cols);

      if(data.Cols()!=ulong(cols.Size()))
        {
         Print(__FUNCTION__, " invalid input data ");
         return false;
        }

      m_vars = ulong(cols.Size());

      if(ArrayCopy(m_cat_cols,cols)<=0 || !ArraySort(m_cat_cols))
        {
         Print(__FUNCTION__, " ArrayCopy or ArraySort failure ", GetLastError());
         return false;
        }

      if(ArrayResize(m_mapping,int(m_vars))<0)
        {
         Print(__FUNCTION__, " Vector array resize failure ", GetLastError());
         return false;
        }

      for(ulong i = 0; i<m_vars; i++)
        {
         vector unique = data.Col(i);
         m_mapping[i] = np::unique(unique);
        }

      return true;
     }
   //+------------------------------------------------------------------+
   //| Transform abitrary feature matrix to learned category m_mapping  |
   //+------------------------------------------------------------------+

   matrix            transform(matrix &in_data)
     {
      if(in_data.Cols()!=m_cols)
        {
         Print(__FUNCTION__," Column dimension of input not equal to ", m_cols);
         return matrix::Zeros(1,1);
        }

      matrix out,input_copy;
      matrix data = np::selectMatrixCols(in_data,m_cat_cols);

      if(data.Cols()!=ulong(m_cat_cols.Size()))
        {
         Print(__FUNCTION__, " invalid input data ");
         return matrix::Zeros(1,1);
        }

      ulong unchanged_feature_cols[];

      for(ulong i = 0; i<in_data.Cols(); i++)
        {
         int found = ArrayBsearch(m_cat_cols,i);
         if(m_cat_cols[found]!=i)
           {
            if(!unchanged_feature_cols.Push(i))
              {
               Print(__FUNCTION__, " Failed array insertion ", GetLastError());
               return matrix::Zeros(1,1);
              }
           }
        }

      input_copy = unchanged_feature_cols.Size()?np::selectMatrixCols(in_data,unchanged_feature_cols):input_copy;
      ulong numcols = 0;
      vector cumsum = vector::Zeros(ulong(MathMin(m_vars,data.Cols())));

      for(ulong i = 0; i<cumsum.Size(); i++)
        {
         cumsum[i] = double(numcols);
         numcols+=m_mapping[i].Size();
        }

      out = matrix::Zeros(data.Rows(),numcols);

      for(ulong i = 0;i<data.Rows(); i++)
        {
         vector row = data.Row(i);
         for(ulong col = 0; col<row.Size(); col++)
           {
            for(ulong k = 0; k<m_mapping[col].Size(); k++)
              {
               if(MathAbs(row[col]-m_mapping[col][k])<=1.e-15)
                 {
                  out[i][ulong(cumsum[col])+k]=1.0;
                  break;
                 }
              }
           }
        }

      matrix newfeaturematrix(out.Rows(),input_copy.Cols()+out.Cols());

      if((input_copy.Cols()>0 && !np::matrixCopyCols(newfeaturematrix,input_copy,0,input_copy.Cols())) || !np::matrixCopyCols(newfeaturematrix,out,input_copy.Cols()))
        {
         Print(__FUNCTION__, " Failed matrix copy ");
         return matrix::Zeros(1,1);
        }

      return newfeaturematrix;

     }

  };
//+------------------------------------------------------------------+

最初のメソッドはfit()で、クラスのインスタンスを生成した後に呼び出します。この方法は、特徴行列(学習データ)と配列の2つの入力を必要とします。特徴行列は、カテゴリ変数と非カテゴリ変数の両方を含む完全なデータセットとすることができます。その場合、配列は行列内の名義変数の列インデックスを含むべきです。空の配列が与えられると、すべての変数が名義上のものであるとみなされます。fit()メソッドが正常に実行されて真を返したら、 transform()メソッドを呼び出して変換後のデータセットを得ます。このメソッドは、fit()メソッドに与える行列と同じ列数の行列を必要とします。寸法が一致しない場合、エラーフラグが立ちます。

このテキストで先に用意したBTCUSDデータセットを使ったデモンストレーションを通して、ConeHotEncoderクラスがどのように機能するかを検証してみましょう。以下のスニペットは変換プロセスを示しています。このコードはMetaTrader 5スクリプトOneHotEncoding_demo.mq5から出典されています。

//+------------------------------------------------------------------+
//|                                          OneHotEncoding_demo.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
#include<np.mqh>
#include<nom2ord.mqh>
#include<ErrorDescription.mqh>
//--- input parameters
input datetime TrainingSampleStartDate=D'2023.12.31';
input datetime TrainingSampleStopDate=D'2017.12.31';
input ENUM_TIMEFRAMES tf = PERIOD_D1;
input string   SetSymbol="BTCUSD";
//+------------------------------------------------------------------+
//|global integer variables                                          |
//+------------------------------------------------------------------+
int size_insample,                 //training set size
    size_observations,             //size of of both training and testing sets combined
    price_handle=INVALID_HANDLE;   //log prices indicator handle
//+------------------------------------------------------------------+
//|double global variables                                           |
//+------------------------------------------------------------------+

matrix       prices;                   //array for log transformed prices
vector       targets;                  //differenced prices kept here
matrix       predictors;               //flat array arranged as matrix of all predictors ie size_observations by size_predictors
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//get relative shift of is and oos sets
   int trainstart,trainstop;
   trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate);
   trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate);
//check for errors from ibarshift calls
   if(trainstart<0 || trainstop<0)
     {
      Print(ErrorDescription(GetLastError()));
      return;
     }
//---set the size of the sample sets
   size_insample=(trainstop - trainstart) + 1;
//---check for input errors
   if(size_insample<=0)
     {
      Print("Invalid inputs ");
      return;
     }
//---
   if(!predictors.Resize(size_insample,3))
     {
      Print("ArrayResize error ",ErrorDescription(GetLastError()));
      return;
     }
//---
   if(!prices.CopyRates(SetSymbol,tf,COPY_RATES_VERTICAL|COPY_RATES_OHLC,TrainingSampleStartDate,TrainingSampleStopDate))
     {
      Print("Copyrates error ",ErrorDescription(GetLastError()));
      return;
     }
//---
   targets = log(prices.Col(3));
   targets = np::diff(targets);
//---
   double bodyratio = 0.0;
   for(ulong i = 0; i<prices.Rows(); i++)
     {
      if(prices[i][3]<prices[i][0])
         predictors[i][0] = 0.0;
      else
         predictors[i][0] = 1.0;

      bodyratio = MathAbs(prices[i][3]-prices[i][0])/MathAbs(prices[i][1]-prices[i][2]);

      if(bodyratio >=0.75)
         predictors[i][1] = 0.0;
      else
         if(bodyratio<0.75 && bodyratio>=0.5)
            predictors[i][1] = 1.0;
         else
            if(bodyratio<0.5 && bodyratio>=0.25)
               predictors[i][1] = 2.0;
            else
               predictors[i][1] = 3.0;

      if(i<1)
        {
         predictors[i][2] = 0.0;
         continue;
        }

      if(predictors[i][0]==1.0 && predictors[i-1][0]==1.0 && prices[i][1]>prices[i-1][1] && prices[i][2]>prices[i-1][2])
         predictors[i][2] = 2.0;
      else
         if(predictors[i][0]==0.0 && predictors[i-1][0]==0.0 && prices[i][2]<prices[i-1][2] && prices[i][1]>prices[i-1][1])
            predictors[i][2] = 1.0;
         else
            predictors[i][2] = 0.0;
     }

   targets = np::sliceVector(targets,1);

   prices = np::sliceMatrixRows(prices,1,predictors.Rows()-1);

   predictors = np::sliceMatrixRows(predictors,1,predictors.Rows()-1);

   matrix fullFeatureMatrix(predictors.Rows(),predictors.Cols()+prices.Cols());

   if(!np::matrixCopyCols(fullFeatureMatrix,prices,0,prices.Cols()) ||
      !np::matrixCopyCols(fullFeatureMatrix,predictors,prices.Cols()))
     {
      Print("Failed to merge matrices");
      return;
     }

   if(predictors.Rows()!=targets.Size())
     {
      Print(" Error in aligning data structures ");
      return;
     }

   COneHotEncoder enc;

   ulong selectedcols[] = {4,5,6};

   if(!enc.fit(fullFeatureMatrix,selectedcols))
      return;

   matrix transformed = enc.transform(fullFeatureMatrix);

   Print(" Original predictors \n", fullFeatureMatrix);
   Print(" transformed predictors \n", transformed);
  }
//+------------------------------------------------------------------+

以前の特徴行列:

RQ      0       16:40:41.760    OneHotEncoding_demo (BTCUSD,D1)  Original predictors 
ED      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,0,2,0]
RN      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [13348,15381,12535.67,14689,1,2,0]
DG      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [14232.48,15408,14110.57,15130,1,1,2]
HH      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [15114,15370,13786.18,15139,1,3,0]
QN      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [15055.8,16894,14349.84,16725,1,1,2]
RP      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [15699.53,16474,15672.99,16186,1,1,0]
OI      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [16187,16258,13639.83,14900,0,2,0]
ML      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [14884,15334,13777.33,14405,0,2,0]
GS      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [14405,14876,12969.58,14876,1,3,0]
KF      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [14876,14927,12417.22,13245,0,1,0]
ON      0       16:40:41.761    OneHotEncoding_demo (BTCUSD,D1)  [12776.79,14078.5,12355.38,13681,1,1,0…]]

後の特徴行列:

PH      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  transformed predictors 
KP      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1) [[13743,13855,12362.69,13347,1,0,1,0,0,0,1,0,0]
KP      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [13348,15381,12535.67,14689,0,1,1,0,0,0,1,0,0]
NF      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [14232.48,15408,14110.57,15130,0,1,0,1,0,0,0,1,0]
JI      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [15114,15370,13786.18,15139,0,1,0,0,1,0,1,0,0]
CL      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [15055.8,16894,14349.84,16725,0,1,0,1,0,0,0,1,0]
RL      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [15699.53,16474,15672.99,16186,0,1,0,1,0,0,1,0,0]
IS      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [16187,16258,13639.83,14900,1,0,1,0,0,0,1,0,0]
GG      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [14884,15334,13777.33,14405,1,0,1,0,0,0,1,0,0]
QK      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [14405,14876,12969.58,14876,0,1,0,0,1,0,1,0,0]
PL      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [14876,14927,12417.22,13245,1,0,0,1,0,0,1,0,0]
GS      0       16:40:41.762    OneHotEncoding_demo (BTCUSD,D1)  [12776.79,14078.5,12355.38,13681,0,1,0,1,0,0,1,0,0.]]

MQL5に実装されている2つ目の変換メソッドは2つのモードで動作し、どちらも過剰適合の影響を減らすために変更されたターゲット符号化のバリエーションです。このテクニックは、nom2ord.mqhで定義されているCNomOrdクラスにカプセル化されています。このクラスは、おなじみのメソッドであるfit()とtransform()を利用して、カテゴリ入力の次元を減らすことなく変数を変換します。

public:
   //+------------------------------------------------------------------+
   //|  constructor                                                     |
   //+------------------------------------------------------------------+

                     CNomOrd(void)
     {
     }
   //+------------------------------------------------------------------+
   //|  destructor                                                      |
   //+------------------------------------------------------------------+

                    ~CNomOrd(void)
     {
     }
   //+------------------------------------------------------------------+
   //| fit mapping to training data                                     |
   //+------------------------------------------------------------------+

   bool              fit(matrix &preds_in, ulong &cols[], vector &target)
     {
      m_dim_reduce = 0;
      mapped = false;
      //---
      if(cols.Size()==0 && preds_in.Cols())
        {
         m_pred = int(preds_in.Cols());
         if(!np::arange(m_colindices,m_pred,ulong(0),ulong(1)))
           {
            Print(__FUNCTION__, " arange error ");
            return mapped;
           }
        }
      else
        {
         m_pred = int(cols.Size());
        }
      //---
      m_rows = int(preds_in.Rows()) ;
      m_cols = int(preds_in.Cols());
      //---
      if(ArrayResize(m_mean_rankings,m_pred)<0 ||
         ArrayResize(m_rankings,m_rows)<0 ||
         ArrayResize(m_indices,m_rows)<0 ||
         ArrayResize(m_mapping,m_pred)<0  ||
         ArrayResize(m_class_counts,m_pred)<0  ||
         !m_median.Resize(m_pred) ||
         !m_class_ids.Resize(m_rows,m_pred) ||
         !shuffle_target.Resize(m_rows,2) ||
         (cols.Size()>0 && ArrayCopy(m_colindices,cols)<0) ||
         !ArraySort(m_colindices))
        {
         Print(__FUNCTION__, " Memory allocation failure ", GetLastError());
         return mapped;
        }
      //---
      for(uint col = 0; col<m_colindices.Size(); col++)
        {
         vector var = preds_in.Col(m_colindices[col]);
         m_mapping[col] = np::unique(var);
         m_class_counts[col] = vector::Zeros(m_mapping[col].Size());
         for(ulong i = 0; i<var.Size(); i++)
           {
            for(ulong j = 0; j<m_mapping[col].Size(); j++)
              {
               if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15)
                 {
                  m_class_ids[i][col]=double(j);
                  ++m_class_counts[col][j];
                  break;
                 }
              }
           }
        }

      m_target = target;

      for(uint i = 0; i<m_colindices.Size(); i++)
        {
         vector cid = m_class_ids.Col(i);
         vector ccounts = m_class_counts[i];
         m_mean_rankings[i] = train(cid,ccounts,m_target,m_median[i]);
        }

      mapped = true;
      return mapped;
     }
   //+------------------------------------------------------------------+
   //|  transform nominal to ordinal based on learned mapping           |
   //+------------------------------------------------------------------+

   matrix            transform(matrix &data_in)
     {
      if(m_dim_reduce)
        {
         Print(__FUNCTION__, " Invalid method call, Use fitTransform() or call fit() ");
         return matrix::Zeros(1,1);
        }

      if(!mapped)
        {
         Print(__FUNCTION__, " Invalid method call, training either failed or was not done. Call fit() first. ");
         return matrix::Zeros(1,1);
        }

      if(data_in.Cols()!=ulong(m_cols))
        {
         Print(__FUNCTION__, " Unexpected input data shape, doesnot match training data ");
         return matrix::Zeros(1,1);
        }
      //---
      matrix out = data_in;
      //---
      for(uint col = 0; col<m_colindices.Size(); col++)
        {
         vector var = data_in.Col(m_colindices[col]);
         for(ulong i = 0; i<var.Size(); i++)
           {
            for(ulong j = 0; j<m_mapping[col].Size(); j++)
              {
               if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15)
                 {
                  out[i][m_colindices[col]]=m_mean_rankings[col][j];
                  break;
                 }
              }
           }
        }
      //---
      return out;
     }

fit()メソッドは、対応するターゲット変数を表すベクトルという追加入力を必要とします。この符号化スキームは、標準的なターゲット符号化とは異なり、ターゲット変数の分布における異常値のためにしばしば生じる過剰適合を最小限に抑える。この問題に対処するため、このクラスではパーセンタイル変換を採用しています。目標値はパーセンタイルベースに変換され、最小値はパーセンタイルランク0、最大値はランク100、中間値は比例してスケーリングされます。このアプローチは、外れ値の影響を減衰させながら、値間の順序関係を効果的に保持します。

   //+------------------------------------------------------------------+
   //|   test for a genuine relationship between predictor and target   |
   //+------------------------------------------------------------------+

   double            score(int reps, vector &test_target,ulong selectedVar=0)
     {
      if(!mapped)
        {
         Print(__FUNCTION__, " Invalid method call, training either failed or was not done. Call fit() first. ");
         return -1.0;
        }

      if(m_dim_reduce==0 && selectedVar>=ulong(m_colindices.Size()))
        {
         Print(__FUNCTION__, " invalid predictor selection ");
         return -1.0;
        }

      if(test_target.Size()!=m_rows)
        {
         Print(__FUNCTION__, " invalid targets parameter, Does not match shape of training data. ");
         return -1.0;
        }

      int i, j, irep, unif_error ;
      double dtemp, min_neg, max_neg, min_pos, max_pos, medn ;
      dtemp = 0.0;
      min_neg = 0.0;
      max_neg = -DBL_MIN;
      min_pos = DBL_MAX;
      max_pos = DBL_MIN ;

      vector id = (m_dim_reduce)?m_class_ids.Col(0):m_class_ids.Col(selectedVar);
      vector cc = (m_dim_reduce)?m_class_counts[0]:m_class_counts[selectedVar];
      int nclasses = int(cc.Size());

      if(reps < 1)
         reps = 1 ;

      for(irep=0 ; irep<reps ; irep++)
        {

         if(!shuffle_target.Col(test_target,0))
           {
            Print(__FUNCTION__, " error filling shuffle_target column ", GetLastError());
            return -1.0;
           }

         if(irep)
           {
            i = m_rows ;
            while(i > 1)
              {
               j = (int)(MathRandomUniform(0.0,1.0,unif_error) * i) ;
               if(unif_error)
                 {
                  Print(__FUNCTION__, " mathrandomuniform() error ", unif_error);
                  return -1.0;
                 }
               if(j >= i)
                  j = i - 1 ;
               dtemp = shuffle_target[--i][0] ;
               shuffle_target[i][0] = shuffle_target[j][0] ;
               shuffle_target[j][0] = dtemp ;
              }
           }

         vector totrain = shuffle_target.Col(0);
         vector m_ranks = train(id,cc,totrain,medn) ;

         if(irep == 0)
           {
            for(i=0 ; i<nclasses ; i++)
              {
               if(i == 0)
                  min_pos = max_pos = m_ranks[i] ;
               else
                 {
                  if(m_ranks[i] > max_pos)
                     max_pos = m_ranks[i] ;
                  if(m_ranks[i] < min_pos)
                     min_pos = m_ranks[i] ;
                 }
              } // For i<nclasses
            orig_max_class = max_pos - min_pos ;
            count_max_class = 1 ;

           }
         else
           {
            for(i=0 ; i<nclasses ; i++)
              {
               if(i == 0)
                  min_pos = max_pos = m_ranks[i];
               else
                 {
                  if(m_ranks[i] > max_pos)
                     max_pos = m_ranks[i] ;
                  if(m_ranks[i] < min_pos)
                     min_pos = m_ranks[i] ;
                 }
              } // For i<nclasses
            if(max_pos - min_pos >= orig_max_class)
               ++count_max_class ;

           }
        }



      if(reps <= 1)
         return -1.0;

      return double(count_max_class)/ double(reps);

     }

score()メソッドは、順序変数とターゲットの間の暗黙の関係の統計的有意性を評価するために使用されます。モンテカルロ並べ替え検定は、対象変数のデータを繰り返しシャッフルし、観測された関係を再計算するために採用されます。観察された関係を無作為の順列によって得られた関係の分布と比較することによって、観察された関係が単に偶然によるものである確率を推定することができます。観察された関係を定量化するために、名義変数のすべてのカテゴリーにわたって、平均目標パーセンタイルの最大値と最小値の差を計算します。したがって、score()メソッドは、仮説検定の結果となる確率値を返します。ここで帰無仮説は、観察された差は、無関係な名義変数とターゲットから偶然に生じた可能性があるとします。

BTCUSDデータセットにこのクラスを適用して、どのように機能するか見てみましょう。このデモは、MQL5スクリプトTergebasedNominalVariableConversion_demo.mq5で提供されています。

CNomOrd enc;
   
   ulong selectedcols[] = {4,5,6};
    
   if(!enc.fit(fullFeatureMatrix,selectedcols,targets))
     return;
     
   matrix transformed = enc.transform(fullFeatureMatrix);
   
   Print(" Original predictors \n", fullFeatureMatrix);
   Print(" transformed predictors \n", transformed);
   
   for(uint i = 0; i<selectedcols.Size(); i++)
      Print(" Probability that predicator at ", selectedcols[i] , " is associated with target ", enc.score(10000,targets,ulong(i)));

以下は変換されたデータです。

IQ      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    transformed predictors 
LM      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)   [[13743,13855,12362.69,13347,52.28360492434251,50.66453470243147,50.45172139701621]
CN      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [13348,15381,12535.67,14689,47.85025875164135,50.66453470243147,50.45172139701621]
IH      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [14232.48,15408,14110.57,15130,47.85025875164135,49.77386885151457,48.16372967916465]
FF      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [15114,15370,13786.18,15139,47.85025875164135,49.23046392011166,50.45172139701621]
HR      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [15055.8,16894,14349.84,16725,47.85025875164135,49.77386885151457,48.16372967916465]
EM      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [15699.53,16474,15672.99,16186,47.85025875164135,49.77386885151457,50.45172139701621]
RK      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [16187,16258,13639.83,14900,52.28360492434251,50.66453470243147,50.45172139701621]
LG      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [14884,15334,13777.33,14405,52.28360492434251,50.66453470243147,50.45172139701621]
QD      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [14405,14876,12969.58,14876,47.85025875164135,49.23046392011166,50.45172139701621]
HP      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [14876,14927,12417.22,13245,52.28360492434251,49.77386885151457,50.45172139701621]
PO      0       16:44:25.680    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    [12776.79,14078.5,12355.38,13681,47.85025875164135,49.77386885151457,50.45172139701621…]]

以下は変換された変数とターゲットとの関係を推定するp値です。

NS	0	16:44:29.287	TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    Probability that predicator at 4 is associated with target 0.0005
IR      0       16:44:32.829    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    Probability that predicator at 5 is associated with target 0.7714
JS      0       16:44:36.406    TargetBasedNominalVariableConversion_demo (BTCUSD,D1)    Probability that predicator at 6 is associated with target 0.749

その結果、すべてのカテゴリ変数の中で、強気/弱気の分類のみがターゲットと有意に関連していることが示されました。対照的に、2バーパターンとローソク足の実体サイズは、ターゲットとの明らかな対応関係を示しません。このことは、変換された変数がランダム変数にほとんど影響を与えないことを示しています。

最後のデモンストレーションは、次元削減と組み合わせて名詞変数のセットを変換するためにクラスを使用することを示します。この機能は、fit()メソッドと同じ入力パラメータを受け取り、変換された名義変数から導かれる1つの代表変数を含む行列を返すfitTransform()を呼び出すことで実現されます。この操作は一貫して、任意の数の名義変数を1つの順序変数に削減します。

   //+------------------------------------------------------------------+
   //| categorical conversion with dimensionality reduction             |
   //+------------------------------------------------------------------+
   matrix            fitTransform(matrix &preds_in, ulong &cols[], vector &target)
     {
      //---
      if(preds_in.Cols()<2)
        {
         if(!fit(preds_in,cols,target))
           {
            Print(__FUNCTION__, " error at ", __LINE__);
            return matrix::Zeros(1,1);
           }
         //---
         return transform(preds_in);
        }
      //---
      m_dim_reduce = 1;
      mapped = false;
      //---
      if(cols.Size()==0 && preds_in.Cols())
        {
         m_pred = int(preds_in.Cols());
         if(!np::arange(m_colindices,m_pred,ulong(0),ulong(1)))
           {
            Print(__FUNCTION__, " arange error ");
            return matrix::Zeros(1,1);
           }
        }
      else
        {
         m_pred = int(cols.Size());
        }
      //---
      m_rows = int(preds_in.Rows()) ;
      m_cols = int(preds_in.Cols());
      //---
      if(ArrayResize(m_mean_rankings,1)<0 ||
         ArrayResize(m_rankings,m_rows)<0 ||
         ArrayResize(m_indices,m_rows)<0 ||
         ArrayResize(m_mapping,m_pred)<0  ||
         ArrayResize(m_class_counts,1)<0  ||
         !m_median.Resize(m_pred) ||
         !m_class_ids.Resize(m_rows,m_pred) ||
         !shuffle_target.Resize(m_rows,2) ||
         (cols.Size()>0 && ArrayCopy(m_colindices,cols)<0) ||
         !ArraySort(m_colindices))
        {
         Print(__FUNCTION__, " Memory allocation failure ", GetLastError());
         return matrix::Zeros(1,1);
        }
      //---
      for(uint col = 0; col<m_colindices.Size(); col++)
        {
         vector var = preds_in.Col(m_colindices[col]);
         m_mapping[col] = np::unique(var);
         for(ulong i = 0; i<var.Size(); i++)
           {
            for(ulong j = 0; j<m_mapping[col].Size(); j++)
              {
               if(MathAbs(var[i]-m_mapping[col][j])<=1.e-15)
                 {
                  m_class_ids[i][col]=double(j);
                  break;
                 }
              }
           }
        }

      m_class_counts[0] = vector::Zeros(ulong(m_colindices.Size()));

      if(!m_class_ids.Col(m_class_ids.ArgMax(1),0))
        {
         Print(__FUNCTION__, " failed to insert new class id values ", GetLastError());
         return matrix::Zeros(1,1);
        }

      for(ulong i = 0; i<m_class_ids.Rows(); i++)
         ++m_class_counts[0][ulong(m_class_ids[i][0])];

      m_target = target;

      vector cid = m_class_ids.Col(0);
      m_mean_rankings[0] = train(cid,m_class_counts[0],m_target,m_median[0]);

      mapped = true;

      ulong unchanged_feature_cols[];

      for(ulong i = 0; i<preds_in.Cols(); i++)
        {
         int found = ArrayBsearch(m_colindices,i);
         if(m_colindices[found]!=i)
           {
            if(!unchanged_feature_cols.Push(i))
              {
               Print(__FUNCTION__, " Failed array insertion ", GetLastError());
               return matrix::Zeros(1,1);
              }
           }
        }

      matrix out(preds_in.Rows(),unchanged_feature_cols.Size()+1);
      ulong nfeatureIndex = unchanged_feature_cols.Size();

      if(nfeatureIndex)
        {
         matrix input_copy = np::selectMatrixCols(preds_in,unchanged_feature_cols);
         if(!np::matrixCopyCols(out,input_copy,0,nfeatureIndex))
           {
            Print(__FUNCTION__, " failed to copy matrix columns ");
            return matrix::Zeros(1,1);
           }
        }

      for(ulong i = 0; i<out.Rows(); i++)
        {
         ulong r = ulong(m_class_ids[i][0]);
         if(r>=m_mean_rankings[0].Size())
           {
            Print(__FUNCTION__, " critical error , index out of bounds ");
            return matrix::Zeros(1,1);
           }
         out[i][nfeatureIndex] = m_mean_rankings[0][r];
        }

      return out;
     }

スクリプトTargetBasedNominalVariableConversionWithDimReduc_demoは、このプロセスがどのように実装されるかを示しています。

CNomOrd enc;
   
   ulong selectedcols[] = {4,5,6};
     
   matrix transformed = enc.fitTransform(fullFeatureMatrix,selectedcols,targets);
   
   Print(" Original predictors \n", fullFeatureMatrix);
   Print(" transformed predictors \n", transformed);
   
   Print(" Probability that predicator  is associated with target ", enc.score(10000,targets));

以下は、変換された変数です。

JR      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        transformed predictors 
JO      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)       [[13743,13855,12362.69,13347,49.36939702213909]
NS      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [13348,15381,12535.67,14689,49.36939702213909]
OP      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [14232.48,15408,14110.57,15130,49.36939702213909]
RM      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [15114,15370,13786.18,15139,50.64271980734179]
RL      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [15055.8,16894,14349.84,16725,49.36939702213909]
ON      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [15699.53,16474,15672.99,16186,49.36939702213909]
DO      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [16187,16258,13639.83,14900,49.36939702213909]
JN      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [14884,15334,13777.33,14405,49.36939702213909]
EN      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [14405,14876,12969.58,14876,50.64271980734179]
PI      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [14876,14927,12417.22,13245,50.64271980734179]
FK      0       16:51:06.137    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        [12776.79,14078.5,12355.38,13681,49.36939702213909…]]
NQ      0       16:51:09.741    TargetBasedNominalVariableConversionWithDimReduc_demo (BTCUSD,D1)        Probability that predicator  is associated with target 0.4981

新しい変数とターゲットとの関係をチェックすると、比較的高いp値が得られ、次元削減の限界の1つである情報の損失が浮き彫りになります。したがって、この方法は慎重に使用すべきです。 



結論

機械学習のための名義変数から順序変数への変換は、強力であるが微妙なプロセスです。これによってモデルはカテゴリデータを有意義に扱うことができるようになりますが、バイアスや誤った表現が生じないように注意深く考慮する必要があります。手動順序付け、周波数ベースの符号化、クラスタリング、ターゲット符号化など、適切な変換技術を採用することで、データの完全性を保ちながら、機械学習モデルが名義変数を効果的に扱えるようにすることができます。この記事は、一般的な名義変数の変換方法の不完全な概要を提供するものであり、より高度なテクニックが数多くあることに注意することが重要です。このテキストの主な目的は、実務家に序数符号化の概念と、特定のユースケースに最も適した方法を選択する際に考慮すべき要素を紹介することです。様々な符号化技術の意味を理解することで、実務者はより多くの情報に基づいた決定を下し、機械学習モデルのパフォーマンスと解釈可能性を向上させることができます。記事中で言及されているコードファイルはすべて以下に添付されています。

ファイル
詳細
MQL5/scripts/CategoricalVariableConversion.py
カテゴリ変換の例を含むpythonスクリプト
MQL5/scripts/CategoricalVariableConversion.ipynb
上記のpythonスクリプトのJupyterノートブック
MQL5/scripts/ OneHotEncoding_demo.mq5
MQL5でワンホット符号化を使って名義変数を変換するデモスクリプト
MQL5/scripts/TargetBasedNominalVariableConversion_demo.mq5
カスタムターゲットベースの符号化方式で名義変数を変換するデモスクリプト
MQL5/scripts/TargetBasedNominalVariableConversionWithDimReduc_demo.mq5
次元削減を実装したカスタムターゲットベースの符号化法を用いて名義変数を変換するためのデモスクリプト
MQL5/include/nom2ord.mqh
CNomOrdクラスとCOneHotEncoderクラスの定義を含むヘッダーファイル
MQL5/include/np.mqh
ベクトルおよび行列ユーティリティ関数のヘッダファイル

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16056

知っておくべきMQL5ウィザードのテクニック(第42回):ADXオシレーター 知っておくべきMQL5ウィザードのテクニック(第42回):ADXオシレーター
ADXは、一部のトレーダーが一般的なトレンドの強さを測定するために使用する、もう1つの比較的人気のあるテクニカルインジケーターです。これは他の2つのインジケーターの組み合わせとして機能し、オシレーターとして表示されます。この記事では、MQL5ウィザードアセンブリとそのサポートクラスを使用して、そのパターンについて説明します。
MQL5で取引管理者パネルを作成する(第3回):テーマ管理のための組み込みクラスの拡張(II) MQL5で取引管理者パネルを作成する(第3回):テーマ管理のための組み込みクラスの拡張(II)
このディスカッションでは、既存のダイアログライブラリを慎重に拡張して、テーマ管理ロジックを組み込みます。さらに、管理パネルプロジェクトで使用されるCDialog、CEdit、およびCButtonクラスにテーマ切り替えのメソッドを統合します。さらに洞察力のある視点については、引き続きお読みください。
リプレイシステムの開発(第54回):最初のモジュールの誕生 リプレイシステムの開発(第54回):最初のモジュールの誕生
この記事では、リプレイ/シミュレーターシステムで使用するための、他の目的にも汎用的に使用できる、実際に機能するモジュールの最初のものを組み立てる方法について説明します。マウスモジュールです。
Connexusにおけるヘッダ(第3部):リクエスト用HTTPヘッダの使い方をマスターする Connexusにおけるヘッダ(第3部):リクエスト用HTTPヘッダの使い方をマスターする
Connexusライブラリの開発を続けます。この章では、HTTPプロトコルにおけるヘッダの概念を探求し、ヘッダとは何か、何のためにあるのか、リクエストでどのように使うのかを説明します。APIとの通信で使用される主なヘッダを取り上げ、ライブラリでの設定方法の実践例を紹介します。