English Deutsch
preview
データサイエンスと機械学習(第23回):LightGBMとXGBoostが多くのAIモデルを凌駕する理由

データサイエンスと機械学習(第23回):LightGBMとXGBoostが多くのAIモデルを凌駕する理由

MetaTrader 5統計と分析 | 29 7月 2024, 10:32
18 0
Omega J Msigwa
Omega J Msigwa

勾配ブースティング決定木とは?

勾配ブースティング決定木(GBDT)は、主に回帰と分類タスクに使用される強力な機械学習手法です。複数の弱い学習機(通常は決定木)の予測を組み合わせて、強力な予測モデルを作成します。

核となる考え方は、モデルを順次構築していき、それぞれの新しいモデルで前のモデルの誤差を修正しようとすることです。

次はブースト決定木の例です。

  • Extreme Gradient Boosting (XGBoost): 勾配ブースティングの一般的で効率的な実装です。
  • Light Gradient Boosting Machnine (LightGBM): 特に大規模なデータセットで高いパフォーマンスと効率を発揮するように設計されています。
  • CatBoost: カテゴリ特徴量を自動的に処理し、過剰適合に対して頑健です。

機械学習コンテストで多くの優勝チームが選択するアルゴリズムとして、機械学習コミュニティで人気を博しています。この記事では、取引アプリケーションでこれらの正確なモデルをどのように使用することができるかを紹介します。



主な概念

ブースティング

  • ブースティングは、複数の弱い学習機(ランダムな推測よりもわずかに良い結果を出すモデル)を組み合わせて強い学習機を形成するアンサンブル学習手法です。
  • 各新モデルは、前モデルの失敗に焦点を当て、全体的なパフォーマンスを徐々に向上させています。

勾配降下法

  • 勾配ブースティングは、予測値と実際の値の差である損失関数を最小化するために勾配降下を使用します。
  • 損失関数の勾配の方向を指す新しいモデルを繰り返し追加することで、アンサンブルモデルは洗練されていきます。

わずか数行のコードで、これらのブーストされた木は、合理的な精度を与えるだけでなく、データサイエンスプロジェクトを次のレベルに引き上げることができます。

import lightgbm as lgb

train_data = lgb.Dataset(X_train, label=y_train) # preparing data the lightgbm way
val_data = lgb.Dataset(X_test, label=y_test, reference=train_data)

params = {
    'boosting_type': 'gbdt',  # Gradient Boosting Decision Tree
    'objective': 'binary',  # For binary classification (use 'regression' for regression tasks)
    'metric': ['auc','binary_logloss'],  # Evaluation metric
    'num_leaves': 10,  # Number of leaves in one tree
    'n_estimators' : 100, # number of trees
    'max_depth': 5,
    'learning_rate': 0.05,  # Learning rate
    'feature_fraction': 0.9  # Fraction of features to be used for each boosting round
}

# Train the model with evaluation results stored
num_round = 100

bst = lgb.train(params, train_data, num_round, valid_sets=[train_data, val_data])


y_pred = bst.predict(X_test, num_iteration=bst.best_iteration)

# For binary classification, you might want to threshold the predictions
y_pred_binary = np.round(y_pred)


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

結果

Classification Report
               precision    recall  f1-score   support

         0.0       0.70      0.75      0.73       104
         1.0       0.71      0.66      0.68        96

    accuracy                           0.70       200
   macro avg       0.71      0.70      0.70       200
weighted avg       0.71      0.70      0.70       200

同じデータを他の一般的な分類器にも適用してみました。

分類機 分類レポート
ロジスティック回帰
               precision    recall  f1-score   support

         0.0       0.69      0.76      0.72       104
         1.0       0.71      0.62      0.66        96

    accuracy                           0.69       200
   macro avg       0.70      0.69      0.69       200
weighted avg       0.70      0.69      0.69       200
決定木 
               precision    recall  f1-score   support

         0.0       0.62      0.61      0.61       104
         1.0       0.59      0.60      0.59        96

    accuracy                           0.60       200
   macro avg       0.60      0.60      0.60       200
weighted avg       0.61      0.60      0.61       200
ナイーブベイズ
               precision    recall  f1-score   support

         0.0       0.64      0.84      0.73       104
         1.0       0.73      0.49      0.59        96

    accuracy                           0.67       200
   macro avg       0.69      0.67      0.66       200
weighted avg       0.68      0.67      0.66       200
K-最近傍 
               precision    recall  f1-score   support

         0.0       0.68      0.73      0.71       104
         1.0       0.69      0.64      0.66        96

    accuracy                           0.69       200
   macro avg       0.69      0.68      0.68       200
weighted avg       0.69      0.69      0.68       200
サポートベクターマシン 
               precision    recall  f1-score   support

         0.0       0.69      0.69      0.69       104
         1.0       0.66      0.66      0.66        96

    accuracy                           0.68       200
   macro avg       0.67      0.67      0.67       200
weighted avg       0.67      0.68      0.67       200

LightGBMモデルは、同じ問題に対して他の分類器よりも総合的な精度が高くなりました。お気づきかもしれませんが、入力データを正規化しませんでした。それでも、モデルは他を凌駕することができました。正規化が機械学習モデルのパフォーマンスにとって極めて重要であることはご存知の通りですが、lightGBMはこの考えに反しているようです。これが、このモデルの面白さのひとつです。

GBDT(LightGBMとXGBoost)と他の機械学習分類器の違いを見てみましょう。


勾配ブースティング決定木(LighGBMとXGBoost)と他の分類器との比較

LightGBMとXGBoost その他の分類器
入力特徴のスケールに影響されない決定木に基づいているため、特徴のスケーリングを必要としません
K-最近傍(K-NN)やサポートベクターマシン(SVM)のようなデータ点間の距離に依存するアルゴリズムは、異なるスケールの特徴を持つデータセットではうまく機能しません。 スケーリングはほとんどの分類器にとって非常に重要です。


XGBoostもLightGBMにも、欠損値を処理できるメカニズムが組み込まれています。訓練中に欠損値を分割のどちらか一方に割り当てることで、欠損データへの対処法を学ぶこともできるし、単純に木内に欠損値用のブランチを作成し、そのパターンを個別に理解することもできます。

欠損値を扱えないことがほとんどです。これらの欠損値は精度を低下させる可能性があります。

パラメータチューニングは重要ですが、必須ではありません。これらのモデルは、デフォルトのパラメータでそれなりにうまく機能します。最適なパフォーマンスを達成するためには、ハイパーパラメータのチューニングが依然として重要です。

ニューラルネットワークのような分類モデルは、ハイパーパラメータに非常に敏感です。 デフォルトのパラメータでは役に立ちません。

さて、LightGBMとXGBoostが他の機械学習分類器に対してどのような結果を出すかを見てきたところで、XGBoostから順にモデルを解剖していきましょう。


Extreme Gradient Boosting (XGBoost)とは?

XGBoostは、機械学習モデルの効率的かつスケーラブルな訓練のために設計された、最適化された分散勾配ブースティングライブラリです。その中には、複数の弱いモデルの予測を組み合わせて、より強い予測を作り出すアンサンブル学習法があります。


XGBoostはどのように機能するのか?

XGBoostがどのように機能するかを理解するために、その理論を理解しましょう。

初期化

これは初期予測(通常は回帰の目標値の平均、または分類のオッズの対数)から始まります。

反復ブースティング

  • 残差計算: 各反復 について、現在のモデルのアンサンブルによる予測の残差(誤差)を計算します。

     

    ここで

    は、番目の観測値(反復)に対する残差です。

    y_iは実際の目標値です。

    は反復における予測値です。 

  • 新しい木のフィッティング: 残差に新しい決定木をフィッティングします。木は、現在のモデルの残差を予測するために構築されます。

    木の葉は、現在のモデルの予測に対する予測された調整を表しています。
  • モデルを更新: 新しい木の予測値を現在のモデルの予測値に追加します。通常、学習率 でスケーリングします。



    ここで

    は入力に対する反復における新しい木の予測値です。

目的関数

XGBoostの目的関数は2つの部分からなります。

  1. 損失関数:モデルがどれだけ訓練データに適合しているかを測定します。一般的な損失関数には、回帰の平均二乗誤差や分類の対数損失などがあります。
  2. 正則化項:過剰適合を防ぐために、モデルの複雑さにペナルティを与えます。XGBoostはL1 (Lasso)とL2 (Ridge)の両方の正則化を使用します。

最小化する目的関数は次の通りです。

目的関数xgboost

ここで 

は損失項

番目の木の正則化項。

 は観測数

は木の本数

勾配とヘッセ行列

XGBoostは2次のテイラー展開(勾配とヘッセ行列)を使用して損失関数を近似し、最適分割の計算を効率化します。勾配は予測に対する損失関数の一次導関数であり、ヘシアンは予測に対する損失関数の二次導関数です。

木の剪定

木は反復的に成長し、目的関数を最適化するために剪定されます。剪定は、目的関数の有意な改善をもたらさないノードを削除することで、過剰適合を防ぐのに役立ちます。

学習率ηは、ブースティングプロセスのステップサイズを制御するために、それぞれの木の寄与をスケーリングします。学習率が小さいほど、通常、より多くの木が必要になります。


PythonでXGBoostを実装する

PythonでXGBoostモデルを実装するには、数行のコードで済みます。これは別のライブラリなので、まだインストールしていない場合は先にインストールしておく必要があります。

pip install xgboost

Scikit-learnからPipeline関数をインポートする必要があります。 これは、前処理やその他の必要なステップで機械学習オブジェクトを作るのに役立ちます。 

from sklearn.pipeline import Pipeline

先に述べたように、ブースティングされた木は、データを正規化しなくても十分な性能を発揮しますが、正規化を加えても損はありません。もちろん、正規化を加えることで、モデルの行列乗算の結果生じるかもしれない、大きな数値による数値的不安定性の問題を軽減できるなど、いくつかの利点が得られます。モデルのパイプラインを作成し、訓練データにフィットさせる間に、グッドプラクティスとして正規化を追加しましょう。

Python

# Create a pipeline with a scaler and the XGBoost classifier
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("xgb", xgb.XGBClassifier(**params))
])

# Fit the pipeline to the training data
pipe.fit(X_train, y_train)

XGBoostモデルにはいくつかのパラメータがあり、それがどのように機能するかだけでなく、どのようにチューニングすればより良くなるかを理解することが重要です。

Python

params = {
    'objective': 'binary:logistic',  
    'learning_rate': 0.05,  
    'max_depth': 5,  
    'n_estimators': 100,  
    'colsample_bytree': 0.9,  
    'subsample': 0.9,  
    'eval_metric': ['auc', 'logloss']  
}
パラメータ 詳細 チューニング
objective 学習タスクと対応する学習目的を指定します。上記で使用したbinary:logisticは、ロジスティック回帰による2値分類に使用されます。確率を出力します。 一般的な目的には、回帰には'reg:squarederror'、バイナリ分類には'binary:logistic'、マルチクラス分類には'multi:softmax'があります。

常に問題の性質に基づいて目的を選択します。
learning_rate eta()としても知られ、損失関数の最小値に向かって移動する各反復のステップサイズを指定します。

学習率が低ければ低いほど、過剰適合を防いで頑健なモデルになりますが、ブースティングの回数は多くなります。
より低い学習率(例えば0.01~0.1)は、より多くの木(より高いn_estimators)を必要とする傾向がありますが、モデルの安定性とパフォーマンスを向上させることができます。

適度な値(たとえば0.1)から始めて、交差検証のパフォーマンスに基づいて調整するのがよいです。
max_depth この値を大きくすると、モデルがより複雑になり、過剰適合の可能性が高くなります。

モデルの複雑さを制御するもので、深い木は特徴間のパターンをより多く捉え、過剰適合のリスクを高めます。
一般的な値は3~10です。交差検証を使用して最適な深さを見つけてください。
n_estimators ブースティングラウンドや木を構築する数です。 

木の本数が多いと計算時間が長くなり、過剰適合になりやすいです。
早期停止は、適切な木の数を見つけるために交差検証で使用することができます。
colsample_bytree それぞれの木を構成する際の列のサブサンプル比率です。0.9(上記コードで使用)は、それぞれの木の構築に90%の特徴が使用されることを意味します。

列レベルでランダム性を導入することで、過剰適合を減らすのに役立ちます。また、訓練を高速化し、モデルをより頑健にすることもできます。
数値は通常0.3~1.0です。より高い値ではより多くの特徴を使用し、より低い値ではより多くのランダム性を導入します。
subsample 訓練インスタンスのサブサンプル比率。0.9という値は、訓練データの90%がそれぞれの木の構築に使用されることを意味します。

colsample_bytreeと同様に、このパラメータはデータレベルにランダム性を加えることで過剰適合を減らし、汎化を改善することができます。
値は通常0.5から1.0の範囲です。値が大きいほど多くのデータを使用し、値が小さいほどランダム性が高まります。
eval_metric  訓練中に使用する評価メトリクス。 

複数の評価指標により、モデルの性能をより包括的に見ることができます。
一般的なメトリクスには、回帰のrmse、分類のlogloss、バイナリ分類のerrorなどがあります。

常に、問題の性質と目標に沿った指標を選択します。

モデルはすでに訓練済みです。テストして、そのパフォーマンスを観察してみましょう。

Python

y_pred = pipe.predict(X_test) 

# For binary classification, you might want to threshold the predictions since these are probabilities
y_pred_binary = np.round(y_pred)

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred_binary)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.savefig("confusion-matrix xgboost")  # Display the heatmap


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

混同行列

混同行列xgboos.eurusd.h1

Classification Report
               precision    recall  f1-score   support

         0.0       0.71      0.73      0.72       104
         1.0       0.70      0.68      0.69        96

    accuracy                           0.70       200
   macro avg       0.70      0.70      0.70       200
weighted avg       0.70      0.70      0.70       200

弱気シグナル(クラス0)では71%、強気シグナル(クラス1)では70%の精度を示しました。

さて、XGBoostについて説明したところで、LightGBMを解剖し、その全貌を見てみましょう。


Light Gradient Boosting Machine (LightGBM)とは?

LightGBMはオープンソースの機械学習フレームワークで、効率性、拡張性、正確性を特に重視して設計されています。分散型機械学習ツールキットの一部としてマイクロソフトによって開発され、2017年4月24日にリリースされました。

LightGBMは、XGBoostのような既存の勾配ブースティング決定木をベースに構築されました。以下に概説するいくつかの重要な改善点が提供されます。 


LightGBMの改善

効率が良い:LightGBMは、XGBoostモデルに比べて格段に速い学習速度を誇ります。これは、誤差の多いデータ ポイントに焦点を当て、不要な計算を削減するGradient-based One-Side Sampling (GOSS)などの手法によって実現されます。

メモリ使用量:LightGBMは、効率的に木を構築するためにヒストグラムを利用します。これにより、他のGBDTアルゴリズムに比べてメモリ消費量が削減されます。これは、従来のアプローチではメモリに収まらないような巨大なデータセットを扱うのに特に適しています。

スケーラビリティ:LightGBMはスケーラビリティのために設計されています。分散学習が可能なため、大規模なデータセットも効率的に処理できます。これにより、複数のマシンで並列にモデルを学習させ、膨大なデータセットの処理速度を大幅に向上させることができます。

正確さ:スピードとメモリ効率を優先しながらも、LightGBMは精度に妥協しません。採用された技術は、従来のGBDTモデルと同等か、それ以上の性能を達成するように設計されています。


LightGBMの仕組み

LightGBMは、数学的理論に関してはXGBoostと同じ働きをしますが、木の成長方法が少し変わっています。LightGBMの背後にある中核概念を見てみましょう。

ブースティング:XGBoostと同じ勾配ブースティングでは、それぞれの新しいモデルは、残差(誤差)にフィッティングすることで、前のモデルによる誤差を修正することを目指します。

決定木:一方、XGBoostはレベルごとの木成長を使用します。つまり、木を深さ方向に成長させ、木のバランスをより均一にします。LightGBMは葉ごとに木を成長させるのでより深くより複雑な木を作ることができます。

葉単位とレベル単位の決定木の成長

この2つのモデルは、どのように機能するかという点では非常によく似ています。比較して理解を深めるために、それらの相違点の表を見てみましょう。


XGBoostとLightGBMの違い

側面 XGBoost LightGBM
木の成長戦略
レベル単位の成長(木の深さ方向に成長し、よりバランスの取れた木になる)

葉単位の成長(損失削減が最も高い葉を最初に成長させ、潜在的に深い木になる)
欠損値の処理
訓練中に欠損値を処理する最適な方向を学習する

欠損値を直接扱うことができ、データを欠損のないカテゴリと欠損のカテゴリに分割する
正則化
L1 (Lasso)とL2 (Ridge)正則化を用いてモデルの複雑性にペナルティを与える

L2正則化を使用し、過剰適合を防ぐことに重点を置く
サンプリング手法
すべてのデータで従来の勾配ブースティングを使用

Gradient-based One-Side Sampling (GOSS)により、大きな勾配を持つ重要なデータ点に焦点を当てる
特徴の処理
カテゴリ変数に対するone-hotエンコーディングまたはその他の前処理が必要

Exclusive Feature Bundling (EFB)を使用し、カテゴリ特徴量をネイティブでサポートする
スパースアウェアネス
スパースデータ用に最適化され、スパース性を考慮したアルゴリズムを使用してメモリと計算を効率化

スパースデータを効率的に処理し、XGBoostと同様の最適化技術を持つ
分割探索アルゴリズム
効率的な分割探索のために、デフォルトで近似アルゴリズムと分位スケッチを使用

ヒストグラムベースのアルゴリズムを使用し、分割探索の高速化とメモリ効率を実現
並列処理
大規模データ処理のための並列分散コンピューティングをサポート

大規模データセットに最適化された並列分散コンピューティングもサポート
訓練スピード
レベルごとの成長のため、LightGBMより一般的に遅い

葉ごとの生長と効率的なサンプリング技術により、一般に速い
柔軟性とカスタマイズ 

柔軟性が高く、カスタム損失関数と評価メトリクスをサポート
 
 また、柔軟性も高く、ブーストプロセスには幅広いカスタマイズオプションが用意されている


PythonによるLightGBMの実装

先に見たように、LightGBMモデルを実装するには数行のコードで済みます。今回はパイプラインの中に実装してみましょう。 XGBoostと同じようにです。

まずはそれをインストールしなければなりません。

pip install lightgbm

LightGBMには、理解することが重要なパラメータもあります。

パラメータ  詳細 チューニング
boosting_type
使用するブースティングアルゴリズムの種類。デフォルトはgbdt (Gradient Boosting Decision Tree)。

代表的な値はgbdtdartgossgbdtです。データセットと実験に基づいて選択します。
objective
学習タスクとそれに対応する学習目的を指定します。binaryは二値分類に使用されます。

一般的な目的には、回帰タスクのregression、バイナリ分類のbinaryなどがあります。常に問題の性質に基づいて選択します。
metric
aucとbinary_loglossはバイナリ分類によく使用されます。

標準的な値は20から50の範囲です。num_leavesを増やすと、より複雑なパターンを捉えることができますが、過剰適合につながる可能性があります。交差検証を用いて最適値を見つけます。
num_leaves
1本の木の葉の最大数。値が高いほどモデルの複雑さが増し、その逆も同様です。

標準的な値は20から50の範囲です。この値を大きくすると、より複雑なパターンを捉えることができますが、過剰適合につながる可能性があります。
n_estimators
ブースティングラウンド(木)の数。一般に、木の本数が増えると計算時間が長くなり、過剰適合のリスクが高まります。

適切な木の数を決定するために、交差検証で早期停止を使用します。適度な値(例えば100)から始め、パフォーマンスに応じて調整します。
max_depth
木の最大深度。この値を大きくするとモデルが複雑になり、過剰適合の可能性が高くなります。

一般的な値は3~10です。より深い木はより多くのパターンを捉えるが、過剰適合のリスクが高まります。

交差検証を使用して最適な深さを見つけます。
learning_rate
損失関数の最小値に向かって移動しながら、各反復におけるステップサイズ。値が低いほどモデルはより頑健になりますが、より多くのブーストラウンドを必要とします。

より低い学習率(例えば0.01~0.1)は、より多くの木(より高いn_estimators)を必要としますが、モデルの安定性とパフォーマンスを向上させることができます。適度な値(例えば0.1)から始め、交差検証のパフォーマンスに基づいて調整します。
feature_fraction  
ブースティングラウンドで使用される特徴量の数。0.9という値は、それぞれの木を構築するために90%の特徴量が使用されることを意味します。

数値は通常0.3~1.0です。高い値ではより多くの特徴を使用し、低い値ではより多くのランダム性を導入します。特徴量レベルにランダム性を導入することで、過剰適合を抑えることができます。


これらのハイパーパラメータに関する詳細は、記事の最後にリンクされているドキュメントを参照してください。では、LightGBMモデルを訓練してみましょう。

Python

params = {
    'boosting_type': 'gbdt',  # Gradient Boosting Decision Tree
    'objective': 'binary',  # For binary classification (use 'regression' for regression tasks)
    'metric': ['auc','binary_logloss'],  # Evaluation metric
    'num_leaves': 25,  # Number of leaves in one tree
    'n_estimators' : 100, # number of trees
    'max_depth': 5,
    'learning_rate': 0.05,  # Learning rate
    'feature_fraction': 0.9  # Fraction of features to be used for each boosting round
}

pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("lgbm", lgb.LGBMClassifier(**params))
])

# Fit the pipeline to the training data
pipe.fit(X_train, y_train)

コンソール出力は次のようになります。

[LightGBM] [Warning] feature_fraction is set=0.9, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=0.9
[LightGBM] [Warning] feature_fraction is set=0.9, colsample_bytree=1.0 will be ignored. Current value: feature_fraction=0.9
[LightGBM] [Info] Number of positive: 398, number of negative: 402
[LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000177 seconds.
You can set `force_col_wise=true` to remove the overhead.
[LightGBM] [Info] Total Bins 1594
[LightGBM] [Info] Number of data points in the train set: 800, number of used features: 8
[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.497500 -> initscore=-0.010000
[LightGBM] [Info] Start training from score -0.010000
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf

訓練したモデルに新しいデータを与え、そのパフォーマンスを観察してみましょう。

y_pred = pipe.predict(X_test) # Changes from bst to pipe

# For binary classification, you might want to threshold the predictions
y_pred_binary = np.round(y_pred)

# Confusion Matrix
cm = confusion_matrix(y_test, y_pred_binary)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.savefig("confusion-matrix lightgbm")  # Display the heatmap


print("Classification Report\n",
      classification_report(y_test, y_pred_binary))

混乱マトリックスlightgbm

Classification Report
               precision    recall  f1-score   support

         0.0       0.69      0.72      0.70       104
         1.0       0.68      0.65      0.66        96

    accuracy                           0.69       200
   macro avg       0.68      0.68      0.68       200
weighted avg       0.68      0.69      0.68       200

素晴らしいですね。このモデルはテストサンプルで69%の精度です。この2つのよく訓練されたモデルをONNX形式に保存し、MQL5に展開してみましょう。


XGBoostとLightGBMをONNXに保存すつ

この2つのモデルはどちらもカスタムライブラリで別々に提供されているため、保存するのが厄介です。保存プロセスは、Scikit-learn、TensorFlow、Kerasのモデルの保存のように簡単ではありません。詳しくは、Light GBMXGBoostの保存方法に関するONNXのドキュメントを参照してください。

まず、LightGBMモデルを保存するところから始めましょう。プロセスはどちらのモデルでも全く同じです

LightGBMモデルの保存 | Pythonコード

from skl2onnx.common.data_types import FloatTensorType
from skl2onnx import convert_sklearn, to_onnx, update_registered_converter
from skl2onnx.common.shape_calculator import calculate_linear_classifier_output_shapes
from onnxmltools.convert.xgboost.operator_converters.XGBoost import convert_xgboost
from onnxmltools.convert import convert_xgboost as convert_xgboost_booster

update_registered_converter(
    lgb.LGBMClassifier,
    "GBMClassifier",
    calculate_linear_classifier_output_shapes,
    convert_lightgbm,
    options={"nocl": [False], "zipmap": [True, False, "columns"]},
)

model_onnx = convert_sklearn(
    pipe,
    "pipeline_lightgbm",
    [("input", FloatTensorType([None, X_train.shape[1]]))],
    target_opset={"": 12, "ai.onnx.ml": 2},
)

# And save.
with open("lightgbm.eurusd.h1.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())

XGBoostモデルの保存|Pythonコード

update_registered_converter(
    xgb.XGBClassifier,
    "XGBClassifier",
    calculate_linear_classifier_output_shapes,
    convert_xgboost,
    options={"nocl": [False], "zipmap": [True, False, "columns"]},
)

model_onnx = convert_sklearn(
    pipe,
    "pipeline_xgboost",
    [("input", FloatTensorType([None, X_train.shape[1]]))],
    target_opset={"": 12, "ai.onnx.ml": 2},
)

# And save.
with open("xgboost.eurusd.h1.onnx", "wb") as f:
    f.write(model_onnx.SerializeToString())


MQL5でONNXモデルを読み込む

勾配ブースティング決定木(GBDT)の読み込みは、他のモデルの読み込みとは異なり、プロセスは同じであるにもかかわらず、厄介な場合があります。

MQL5|LightGBM.mqh

bool CLightGBM::OnnxLoad(long &handle)
 {
 
//--- since not all sizes defined in the input tensor we must set them explicitly
//--- first index - batch size, second index - series size, third index - number of series (only Close)
   
   OnnxTypeInfo type_info; //Getting onnx information for Reference In case you forgot what the loaded ONNX is all about

   long input_count=OnnxGetInputCount(handle);
   if (MQLInfoInteger(MQL_DEBUG))
      Print("model has ",input_count," input(s)");
   
   for(long i=0; i<input_count; i++)
     {
      string input_name=OnnxGetInputName(handle,i);
      if (MQLInfoInteger(MQL_DEBUG))
         Print(i," input name is ",input_name);
         
      if(OnnxGetInputTypeInfo(handle,i,type_info))
        {
          if (MQLInfoInteger(MQL_DEBUG))
            PrintTypeInfo(i,"input",type_info);
          ArrayCopy(inputs, type_info.tensor.dimensions);
        }
     }

   long output_count=OnnxGetOutputCount(handle);
   if (MQLInfoInteger(MQL_DEBUG))
      Print("model has ",output_count," output(s)");
      
   for(long i=0; i<output_count; i++)
     {
      string output_name=OnnxGetOutputName(handle,i);
      if (MQLInfoInteger(MQL_DEBUG))
         Print(i," output name is ",output_name);
         
      if(OnnxGetOutputTypeInfo(handle,i,type_info))
       {
         if (MQLInfoInteger(MQL_DEBUG))
            PrintTypeInfo(i,"output",type_info);
         ArrayCopy(outputs, type_info.tensor.dimensions);
       }
     }
   
//---
   
   replace(inputs);
   replace(outputs);
      
//--- Setting the input size

   for (long i=0; i<input_count; i++)   
     if (!OnnxSetInputShape(handle, i, inputs)) //Giving the Onnx handle the input shape
       {
         printf("Failed to set the input shape Err=%d",GetLastError());
         DebugBreak();
         return false;
       }
   
//--- Setting the output size
   
   for(long i=0; i<output_count; i++)
     {
      if(!OnnxSetOutputShape(handle,i,outputs))
       {
          printf("Failed to set the Output[%d] shape Err=%d",i,GetLastError());
          //DebugBreak();
          //return false;
       }
     }
     
   initialized = true;
   
   Print("ONNX model Initialized");
   return true;
 }

出力|[エキスパート]タブ:

JM 0 10:49:34.197 LightGBM EA (EURUSD,H1) model has 1 input(s)

MG 0 10:49:34.197 LightGBM EA (EURUSD,H1) 0 input name is input

KM 0 10:49:34.198 LightGBM EA (EURUSD,H1)    type ONNX_TYPE_TENSOR

CF 0 10:49:34.198 LightGBM EA (EURUSD,H1)    data type ONNX_TYPE_TENSOR

HP 0 10:49:34.198 LightGBM EA (EURUSD,H1)    shape [-1, 8]

EI 0 10:49:34.198 LightGBM EA (EURUSD,H1)    0 input shape must be defined explicitly before model inference

RN 0 10:49:34.198 LightGBM EA (EURUSD,H1)    shape of input data can be reduced to [8] if undefined dimension set to 1

EM 0 10:49:34.198 LightGBM EA (EURUSD,H1) model has 2 output(s)

MJ 0 10:49:34.198 LightGBM EA (EURUSD,H1) 0 output name is output_label

MR 0 10:49:34.198 LightGBM EA (EURUSD,H1)    type ONNX_TYPE_TENSOR

EI 0 10:49:34.198 LightGBM EA (EURUSD,H1)    data type ONNX_TYPE_TENSOR

RK 0 10:49:34.198 LightGBM EA (EURUSD,H1)    shape [-1]

RN 0 10:49:34.198 LightGBM EA (EURUSD,H1)    0 output shape must be defined explicitly before model inference

GJ 0 10:49:34.198 LightGBM EA (EURUSD,H1) 1 output name is output_probability

OR 0 10:49:34.198 LightGBM EA (EURUSD,H1)    type ONNX_TYPE_SEQUENCE

KN 0 10:49:34.198 LightGBM EA (EURUSD,H1)    data type ONNX_TYPE_SEQUENCE

OF 0 10:49:34.198 LightGBM EA (EURUSD,H1) no dimensions defined for 1 output

HM 0 10:49:34.198 LightGBM EA (EURUSD,H1) Failed to set the Output[1] shape Err=5802

IH 0 10:49:34.198 LightGBM EA (EURUSD,H1) ONNX model Initialized

Netron:

LightGBM in netronのモデルを詳しく見てみましょう。

最初の出力層は、未知のサイズの1次元整数配列を持つテンソル型です。この層を1の形にサイズ変更すればうまくいくはずです。2つ目の出力層には、未知のサイズの2つの1次元配列(1つはラベル、もう1つは確率)を持つマップ(Pythonの辞書に似ている)を持つシーケンスがあります。念のため言っておきますが、これはZipMapで説明されています。

この複雑なオブジェクトタイプに対してOnnxSetOutputShapeを使用してこの2番目の出力層のサイズを設定するのは難しく、奇妙なエラーが発生する可能性があります。原因はわかりませんでしたが、サイズを1に変更すると警告が表示されますが、それでも正常に動作します。ONNXモデルを適切な方法で実行した場合。 詳細...

   float output_data[];   
   struct Map
     {
      ulong          key[];
      float          value[];
     } output_data_map[];
   
//---

   ArrayResize(output_data, outputs.Size());
    
   if (!OnnxRun(onnx_handle, ONNX_DATA_TYPE_FLOAT, x_float, output_data, output_data_map))
     {
       printf("Failed to get predictions from Onnx err %d",GetLastError());
       return proba;
     }

LightGBMクラスとXGBoostの内部には、以下のメソッドがあります。

MQL5|LightGBM.mqh

class CLightGBM
  {
  
   bool initialized;
   long onnx_handle;
   void PrintTypeInfo(const long num,const string layer,const OnnxTypeInfo& type_info);
   long inputs[], outputs[];
   
   void replace(long &arr[]) { for (uint i=0; i<arr.Size(); i++) if (arr[i] < 0) arr[i] = UNDEFINED_REPLACE; }
   
   bool OnnxLoad(long &handle);
   
public:
                     CLightGBM(void);
                    ~CLightGBM(void);
                     
                     virtual bool Init(const uchar &onnx_buff[], ulong flags=ONNX_DEFAULT); //Initilaized ONNX model from a resource uchar array with default flag
                     virtual bool Init(string onnx_filename, uint flags=ONNX_DEFAULT); //Initializes the ONNX model from a .onnx filename given

                     virtual long predict_bin(const vector &x); //REturns the predictions for the current given matrix | useful in real-time prediction
                     virtual vector predict_proba(const vector &x); //Returns the predictions in probability terms | useful in real-time prediction
                     virtual matrix predict_proba(const matrix &x); //Returns the predicted probability for the whole matrix | useful for testing
                     virtual vector predict_bin(const matrix &x); //gives out the vector for all the predictions | useful for testing
  };


取引におけるLightGBMとXGBoostの使用

OnInit関数でモデルが初期化された後。

MQL5|LightGBM EA.mq5

int OnInit()
  {
   if (!lgbm.Init(lightgbm_onnx))
     return INIT_FAILED;
  }

読み込まれたONNXモデルは、訓練データと同じ方法で収集されたデータで展開できます。

void OnTick()
  {

   int size = CopyRates(Symbol(), PERIOD_CURRENT, 1, 1, rates_x); //We copy only one recent-closed bar
   
//---
   
   if (NewBar())
    {
       vector x = {
         rates_x[0].open,
         rates_x[0].high,
         rates_x[0].low,
         rates_x[0].close,
         rates_x[0].close-rates_x[0].open,
         rates_x[0].high-rates_x[0].low,
         rates_x[0].close-rates_x[0].low,
         rates_x[0].close-rates_x[0].high
       };
       
       long signal = lgbm.predict_bin(x);   
       
       Comment("Signal: ",signal);
 }

得られたシグナルをもとに、簡単な取引戦略を立ててみましょう。

   if (NewBar()) //Trade at the opening of a new candle
    {
       vector x = {
         rates_x[0].open,
         rates_x[0].high,
         rates_x[0].low,
         rates_x[0].close,
         rates_x[0].close-rates_x[0].open,
         rates_x[0].high-rates_x[0].low,
         rates_x[0].close-rates_x[0].low,
         rates_x[0].close-rates_x[0].high
       };
       
       
       long signal = lgbm.predict_bin(x);   
       
       Comment("Signal: ",signal);
       
//---
      
      MqlTick ticks;
      SymbolInfoTick(Symbol(), ticks);
      
      if (signal==1) //if the signal is bullish
       {
          if (!PosExists(POSITION_TYPE_BUY)) //There are no buy positions
            m_trade.Buy(lotsize, Symbol(), ticks.ask, ticks.bid-stoploss*Point(), ticks.ask+takeprofit*Point()); //Open a buy trade
       }
      else //Bearish signal
        {
          if (!PosExists(POSITION_TYPE_SELL)) //There are no Sell positions
            m_trade.Sell(lotsize, Symbol(), ticks.bid, ticks.ask+stoploss*Point(), ticks.bid-takeprofit*Point()); //open a sell trade
        }
    }

ストラテジーテスターで、EURUSDの1時間足で2023年1月1日から2024年5月23日までテストしてみました。

LightGBM

lightgbmレポート

XGBoost

xgboostモデルテスターレポート

両モデルとも赤字でした。XGBoostの損失はLightGBMより8ドル少なくなりました。どちらもほとんど同じような残高チャートでした。

Lightgbm & xgboostストラテジーテスター残高チャート

ストラテジーテスターのレポートでは、モデルが次のバーの動きを予測するという、訓練された仕事をきちんとこなしているかどうかを知ることはできません。エキスパートアドバイザーを収益性の高いものにするためには、多くのことを考慮しなければならないことはご存知の通りです。

モデルのパフォーマンスを理解するために、訓練データを収集したのと同じ方法でデータを収集するスクリプトを作ってみましょう。

void OnStart()
  {
//---
   if (!lgb.Init(lightgbm_onnx))
     return;
     
//--- custom out-of-sample testing 
   
   int bars = 9000;
   int start = 1000; 
   
   MqlRates rates_x[];
   ArraySetAsSeries(rates_x, true);
   int size = CopyRates(Symbol(), PERIOD_CURRENT, start, bars, rates_x); //We start at the bar 1000 and collect 9000 candles backward
   
   MqlRates rates_y[];
   ArraySetAsSeries(rates_y, true);
   CopyRates(Symbol(), PERIOD_CURRENT, start-1, bars, rates_y); //We do the same thing here but we only collect one bar forward making sure we get the prediction for the next candle
   
//---
   
   vector actual(size), predictions(size);
   for (int i=0; i<size; i++)
     {
       vector x = {
         rates_x[i].open,
         rates_x[i].high,
         rates_x[i].low,
         rates_x[i].close,
         rates_x[i].close-rates_x[i].open,
         rates_x[i].high-rates_x[i].low,
         rates_x[i].close-rates_x[i].low,
         rates_x[i].close-rates_x[i].high
       };
       
       actual[i] = rates_y[i].close > rates_x[i].open ? 1 : 0; //making the target variable
       predictions[i] = (double)lgb.predict_bin(x);
     }
   
   Metrics::classification_report(actual, predictions);
  }

結果|[エキスパート]タブ

EG 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) Confusion Matrix

PO 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) [[2857,1503]

CI 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) [926,3714]]

FM 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1)

NF 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) Classification Report

HJ 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1)

KP 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) [CLS]        precision  recall  specificity  f1 score  support

QQ 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) [0.0]         0.76       0.66       0.80       0.70      4360

CQ 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) [1.0]         0.71       0.80       0.66       0.75      4640

HS 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1)

NK 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) accuracy                                          0.73      9000

RG 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) average          0.73       0.73       0.73       0.73      9000

LI 0 15:18:16.874 LightGBM Performance TestScript (EURUSD,H1) Weighed avg      0.73       0.73       0.73       0.73      9000

このモデルは、サンプル外のデータでは73%の精度を示しました。ストラテジーテスターで上向きのカーブを描くことができなかったにもかかわらず、これは、私たちのモデルがうまくいっていることを物語っています。MQL5でGBDTを使用して独自の収益性の高いEAを作成するための良い出発点になるはずです。


勾配ブースティング決定木の利点

1.高い予測精度

GBDTは、予測精度の点で他の多くの機械学習アルゴリズムを上回ることが多いです。これは、決定木と勾配ブースティングの長所を組み合わせたもので、前の木の誤差に注目することでモデルの性能を反復的に向上させます。

2.過剰適合に対する頑健性

ブースティングプロセスは、既存のアンサンブルの誤差を修正する新しい木を追加することで、過剰適合を減らすのに役立ちます。学習率の調整、正則化、早期停止などのテクニックは、この頑健性をさらに高めることができます。

3. 大規模な前処理は不要

GBDTは、線形回帰やサポートベクトルマシンのような他のアルゴリズムで一般的に必要とされる、特徴のスケーリング、欠損値の処理、カテゴリ変数のエンコーディングのような大規模な前処理ステップを必要としません。

4.柔軟性とカスタマイズ

GBDTは、特定のタスクのパフォーマンスを最適化するために調整可能な、幅広いハイパーパラメータを提供します。これには、木の本数、学習率、木の最大深さ、ノードを分割するのに必要な最小サンプル数などが含まれます。

5.複雑な関係を扱う

GBDTは、特徴量とターゲット変数の間の複雑な非線形関係をモデル化することができます。そのため、単純なモデルでは見逃してしまうような、データ内の複雑なパターンを捉えるのに適しています。

6.正則化のテクニック

GBDTには、モデルの複雑さを制御し、過剰適合を防ぐための正則化パラメータが含まれています。max_depthmin_samples_splitmin_samples_leafなどのパラメータは、正則化において重要な役割を果たします。

7.スケーラビリティ

XGBoost、LightGBM、CatBoostなどの実装は、スピードとスケーラビリティのために最適化されています。大規模なデータセットを効率的に処理し、並列コンピューティングや分散コンピューティングなどのハードウェア機能を活用することができます。

どの機械学習モデルにも言えることですが、これらのモデルにも欠点があり、それを認めなければなりません。それらを使用することをやめさせるためではなく、理解を固めるためです。


勾配ブースティング決定木の欠点

1.計算の複雑さ:GBDTは、特に木の数が多く、深い木の場合、訓練に計算コストがかかることがあります。大規模なデータセットや深いデータセットでは、訓練プロセスにかなりのメモリを消費する可能性があります。

2.ハイパーパラメータの調整:両モデルともデフォルトのパラメータで動作可能であるにもかかわらず、最適なパフォーマンスを達成するために注意深く調整する必要がある多くのハイパーパラメータを持っています。木の本数、学習率、最大深度、葉ごとの最小サンプル数などのパラメータです。 

これらのパラメータを調整するのは、時間と計算量がかかることは周知の通りです。

3. ノイジーなデータに対する感度はまだ高い:それぞれの木は前の木の誤差を修正しようとするため、データ中のノイズはブースティング処理によって増幅される可能性があります。

4.多くの場合、大規模なデータセットを必要とします。 GBDTは優れたパフォーマンスを達成するために、しばしば大量のデータを必要とします。小さなデータセットでは、ブースティング処理を効果的におこなうための十分な情報が得られない可能性があります。


結論

勾配ブースティング木はFXトレーダーにとって貴重なツールです。これらのモデルは多くの機械学習モデルよりも優れたパフォーマンスを発揮することが分かっているからです。以前は、GRU、RNN、LSTMのような複雑なニューラルネットワークベースのモデルが、市場を打ち負かすのに最適なモデルだと信じられていましたが、もはやそうではありません。勾配ブースティング決定木は、そのシンプルさと予測タスクを達成できる能力により、機械学習コミュニティで人気が高まっています。

ご精読ありがとうございました。


機械学習モデルの開発を追跡し、本連載で説明されている多くのことは、このGitHubレポに掲載されています。

この投稿で取り上げているPythonコードは、こちらです。

添付表

EA|Expertsフォルダ:

ファイル名 説明と使用法
LightGBM EA.mq5                                  Light Gradient Boosted Machine (LightGBM)をテストするEA
XGBoost EA.mq5  Extreme Gradient Boosting (XGBoost)をテストするEA 

ライブラリ|インクルードフォルダ:

ファイル名 説明と使用法
LightGBM.mqh                                                                  Light Gradient Boosted Machine (LightGBM) ONNXモデルのロード、初期化、配置用ライブラリ 
XGBoost.mqh Extreme Gradient Boosting (XGBoost) ONNXモデルの読み込み、初期化、展開のためのライブラリ 

スクリプト|スクリプトフォルダ:

ファイル名 説明と使用法
LightGBM Performance TestScript.mq5 LightGBMモデルをリアルタイムでテストするスクリプト                               

Files | Filesフォルダ:

ファイル名 説明と使用法
EURUSD.PERIOD_H1.csv 訓練データセットを含むcsvファイル
lightgbm.eurusd.h1.onnx 
xgboost.eurusd.h1.onnx
ONNX形式のLightGBMとXGBoostモデル

Pythonのコード:

ファイル名 説明と使用法
lightgbm-xgboost.ipynb                         Jupyterノートブックには、この投稿で説明したすべてのpythonコードが含まれています。


情報源と参考文献


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

添付されたファイル |
Code_b_Files.zip (173.73 KB)
ビル・ウィリアムズ戦略:他の指標と予測の有無による比較 ビル・ウィリアムズ戦略:他の指標と予測の有無による比較
この記事では、ビル・ウィリアムズの有名な戦略の1つを取り上げ、それについて議論し、他の指標や予測を用いて戦略の改善を試みます。
MQL5取引ツールキット(第1回):ポジション管理EX5ライブラリ MQL5取引ツールキット(第1回):ポジション管理EX5ライブラリ
MQL5で様々なポジション操作を管理するための開発者用ツールキットの作成方法をご紹介します。この記事では、MQL5でポジション管理タスクを処理する際に発生するさまざまなエラーの自動処理とレポートも含め、簡単なものから高度なものまでポジション管理操作を実行する関数ライブラリ(ex5)の作成方法を紹介します。
どんな市場でも優位性を得る方法(第2回):テクニカル指標の予測 どんな市場でも優位性を得る方法(第2回):テクニカル指標の予測
取引されている銘柄の価格を予測するよりも、特定のテクニカル指標を予測する方が精度が高いことをご存知ですか。この洞察力をより良い取引戦略のために活用する方法を探るために、ぜひお読みください。
独自のLLMをEAに統合する(第3部):CPUを使った独自のLLMの訓練 独自のLLMをEAに統合する(第3部):CPUを使った独自のLLMの訓練
今日の人工知能の急速な発展に伴い、言語モデル(LLM)は人工知能の重要な部分となっています。私たちは、強力なLLMをアルゴリズム取引に統合する方法を考える必要があります。ほとんどの人にとって、これらの強力なモデルをニーズに応じて微調整し、ローカルに展開して、アルゴリズム取引に適用することは困難です。本連載では、この目標を達成するために段階的なアプローチをとっていきます。