アンサンブル学習(スタッキング)を駆使してFX予想をしてみよう
こんばんは、新米データサイエンティスト(@algon_fx)です。三連休は久しぶりにゆっくり休もう、紅葉でも見にハイキングをしようと企んでいましたが…結果、データ三昧な連休でした…。それはそれで有意義ですが…。
最近の記事はプライスアクションの検知やら、酒田五法の検知などがメインでした。ローソク足パターン分析はとても強力なテクニカルツールです。テクニカル指標(例えばMACDやRSI)などと連携して使うとその効果は絶大です。
前回は検知する方法をまとめましたが、今回はさらに一段階ステップした手法をやってみましょう。以前にrubyerさんからリクエストを頂いた「アンサンブル学習」を使います。
本記事では過去レートからローソク足パターン分析を行い、売買シグナルが「成功」か「失敗」をアンサンブル学習を使って予測するFX機械学習トレード手法をまとめました。Pythonの簡単な基礎知識があれば、どなたでも試すことが可能です。
また機械学習を勉強していて、アンサンブル学習がいまいち分からないという方にも役に立つと思います。アンサンブル学習ですが、Kaggleなどのデータ解析競技で上位ランカーが好んで使う手法です。
是非、チャンレンジしてみてください!
アンサンブル学習とは
まずは「アンサンブル学習」とは何ぞや?ってお話から。アンサンブル(英:ensemble)とは日本語で合奏を意味します。なぜ合奏なのかと言うと、複数のモデルを構築して最終的に一つにまとめるからです。
もう少し解りやすく言いましょう。アサンブル学習は「三人寄れば文殊の知恵」です。一つのモデルで推測を行わず、複数のモデルを使って最終的に推測精度を改善する手法です。
言葉だと解りづらいので、例題を使って考えてみましょう。下の表はA〜Eまでの5つのモデルをアンサンブル学習した結果を簡略的に示したものです。
モデルAに注目してみて下さい。モデルAの推測結果と「正解」ラベルを比較してみると正解率は60%しかありません。他のB〜Eまでのモデルも同等に60%の正解率です。
「推測」の列がアンサンブルした結果です。この例では単純に各モデルの出力結果を多数決したものを推測結果としただけです。ですが、アンサンブルした結果をみて下さい。なんと100%の正解率が出ています。
ご覧の通り一つ一つのモデルの正解率は優れていません。この様に弱いモデル(学習器)を弱学習器と呼びます。アンサンブル学習ではこの弱学習器を構築して、最終的にまとめて正解率の高い推測を行う手法です。
アンサンブル学習ですが、大枠に分類すると3つの手法があります。「バギング」「ブースティング」「スタッキング」に分類されます。
バギングとはそれぞれの弱学習器のモデル訓練を「並列的」に行う手法です。つまりそれぞれの弱学習器は干渉し合いません。それぞれが独立した学習器で、最終的に結果をまとめる手法です。上の事例はバギングの事例です。ランダムフォレストは決定木を弱学習器としてバギングを行う手法です。
対してブースティングでは、弱学習のモデル訓練を「順次的」に行います。モデルAを訓練したら、モデルAの結果を取り入れてモデルB、さらにモデルCといった具合に弱学習器の訓練を行います。一つ前のモデルの結果を取りれるとありますが、ここに工夫があります。それぞれの学習器が間違った推測(つまり残差)を次の弱学習器は学習するのです。XGBoostは決定木の勾配ブースティングです。
そして、3つ目が「スタッキング」です。本記事ではスタッキングを使ってFX予測を行うので、より詳しく紐解いてみましょう。
スタッキングとは
さて、続いてアンサンブル学習の一つの手法「スタッキング」についてです。バギングとブースティングと比べてスタッキングは実はとても単純な仕組みです。
スタッキングですが、「ステージ」に分けて考えるとわかりやすいです。今回は仕組みを説明するために、単純な2ステージのスタッキングの事例を紹介します。
下の表をみて下さい。これは分類の問題を3つの機械学習手法で推測した結果を表したものです。これがステージ1です。

ステージ1では異なる機械学習手法を使って、同じ訓練データを学習します。それぞれのモデルの推測結果は若干違いますが、それは当然ですよね。学習するデータ(特徴量・ターゲット)が同じでも、アルゴリズムが異なるので推測結果が全く同等になることは滅多にありません。
スタッキングではこのそれぞれの手法が「弱学習器」となります。これらの結果を「まとめる」のですが、その方法に工夫があります。下の図をみて下さい。こちらがステージ2です。

ステージ1で得た推測結果をステージ2では特徴量として使います。ステージ1ではロジスティック回帰、ランダムフォレスト、多層パーセプトロンを使って推測結果を得ました。ステージ2はそれらの推測結果と正解ラベル(ターゲット)を別の機械学習手法で学習を行います。上の事例ではステージ2ではXGBoostを使っています。
この様にスタッキングとは推測した結果を特徴量として用いて新たなモデリングを行う手法です。今回は仕組み説明のため非常に単純な構造を紹介しましたが、Kaggleなどのトップランカーは数十個のモデルを複雑に組み合わせて最終的な精度の改善を行います。
まるで魔法みたいな手法ですよね。
スタッキングを使ってFX予想
いよいよ本題です。本記事ではスタッキングを使ってFX予想を行ってみましょう。機械学習を使ってFX予想を行う方法は星の数ほどあります。
初めてFX予想をトライされる方は、本記事はハードルが高いかもしれません。下記に初心者向けのFX機械学習予想の方法をまとめています。こちらを先にトライされるのをオススメします。また、本記事ではローソク足パターン分析の「プライスアクション」を学習データとして用います。
(先にオススメ)FXトレードでロジスティック回帰を独自テクニカル指標として活用する方法(機械学習初心者向け)
(先にオススメ)決定木を使ってFX予想をやってみる(分類木編)
(先にオススメ)プライスアクションをPythonで検知する方法
それでは、本記事の目標です。
・過去為替データからスラストアップを検知
・スラストアップを買いシグナルとして扱う
・シグナル発生の2分後に+2pip取れればシグナル成功
・2pip未満の場合はシグナル失敗
・シグナルが成功するか失敗するかを予測する
続いて、本記事の概要は以下の通りです。
・Python3とSKlearn/XGBoostを使用します
・データはドル円1分足データ(OANDA APIより取得)
・ノウハウ的な記事ではありません
・より洗練させれば実践トレードでも十分に役に立ちます
・Pythonと機械学習の初歩的な知識が必要です
実装までの流れは以下の通りです。
(1)ライブラリとデータの準備
(2)プライスアクション(スラストアップ)を検出
(3)最低限の前処理(特徴量作成/標準化)
(4)ロジスティック回帰でモデリング
(5)ランダムフォレストでモデリング
(6)多層パーセプトロンでモデリング
(7)XGBoostで最終推測/評価
手順の3〜4がスタッキングのステージ1です。3つの異なる機械学習を使って、売買シグナルの成功/失敗を推測します。それらの推測結果を特徴量としてXGBoostで最終的な推測を出力します。つまりxgboostがステージ2となります。
では、やってみましょう!
STEP1 ライブラリとデータの読み込み
今回は私がOANDA FX APIから取得したドル円の1分足データを使いましょう。こちらの為替レートですが実際の過去レートです。下記からCSVファイルでダウンロードが可能です。
日本ではFX APIはオアンダ・ジャパンしか使えません。FX APIをPythonで動かしてみたい方は「Pythonを使ったOANDA API v1で知っておきたい10の基本操作」の記事を参照ください。
まずは必要なライブラリをインポートしましょう。XGBoostとScikit-learnはpip経由でインストールが可能です。Anacondaを使っている方はconda installで可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# ライブラリのインポート import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.finance as mpf from matplotlib.finance import candlestick2_ohlc # Scikit-learn from sklearn.preprocessing import StandardScaler from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score from sklearn.metrics import precision_score from sklearn.metrics import recall_score from sklearn.metrics import f1_score from sklearn.model_selection import train_test_split pd.options.display.max_rows = 1000 pd.options.display.max_columns = 1000 %matplotlib inline |
続いて過去レートデータのCSVファイルをPandasのデータフレーム形式で読み込みます。CSVファイルは上のリンクからダウンロードしてください。
1 2 3 4 5 6 7 8 9 10 |
# 過去レートデータの読み込み df = pd.read_csv("../../algoFX/priceaction/usd_1min_api.csv") # カラム名を変更 df.columns = ['t', 'c', 'h', 'l', 'o'] # 最初の5行を確認 df.head() |
1 2 3 4 5 6 7 8 9 |
--出力 t c h l o 0 2018-09-14 09:00:00 112.050 112.060 112.037 112.037 1 2018-09-14 09:01:00 112.057 112.062 112.053 112.053 2 2018-09-14 09:02:00 112.057 112.062 112.056 112.059 3 2018-09-14 09:03:00 112.062 112.074 112.059 112.059 4 2018-09-14 09:04:00 112.065 112.075 112.065 112.065 |
ご覧通りドル円の1分足データです。tが時間、cは終値、hは高値、lは安値、oは始値です。OANDA APIでは他にもvolume(取引高)などのデータも取得可能ですが今回は除外しています。
1 2 3 4 |
# データのサイズを確認 print(df.shape) |
1 2 3 4 |
--出力 (40662, 5) |
2018年9月14日から40,662分のドル円レートのデータが入っています。
STEP2 特徴量エンジニアリング
続いて今回のFX予想で使う特徴量を作りましょう。本記事は儲かるFX予想を公開するのが目的ではありません。ですので、使う特徴量はとても簡単なものにしました。
今回使う特徴量は下記です。
・始値、終値、安値、高値
・ボリンジャーバンド
・単純移動平均(期間150)
ボリンジャーバンドと単純移動平均はデータに含まれていないのでデータから算出してあげましょう。それぞれのテクニカル指標の詳細は「Pythonでボリンジャーバンドを作る」、「単純移動平均の計算方法」をご覧ください。
まずはボリンジャーバンドをレートから算出してあげましょう。期間は20としています。
1 2 3 4 5 6 7 |
# ボリンジャーバンドの算出 df['mean'] = df['c'].rolling(window=20).mean() df['std'] = df['c'].rolling(window=20).std() df['up'] = df['mean'] + (df['std'] * 2) df['low'] = df['mean'] - (df['std'] * 2) |
続いてSMA(単純移動平均)を計算します。こちらの期間は150としました。
1 2 3 4 5 6 7 |
# SMA150を計算 df['sma150'] = df['c'].rolling(150).mean() # 確認 df.tail() |
1 2 3 4 5 6 7 8 |
t c h l o mean std up low sma150 40657 2018-10-26 08:55:00 112.370 112.379 112.370 112.377 112.37855 0.009971 112.398492 112.358608 112.389673 40658 2018-10-26 08:56:00 112.370 112.374 112.370 112.373 112.37865 0.009869 112.398388 112.358912 112.389053 40659 2018-10-26 08:57:00 112.374 112.374 112.368 112.372 112.37895 0.009616 112.398182 112.359718 112.388527 40660 2018-10-26 08:58:00 112.349 112.372 112.349 112.372 112.37805 0.011459 112.400968 112.355132 112.387833 40661 2018-10-26 08:59:00 112.362 112.362 112.348 112.351 112.37775 0.011809 112.401369 112.354131 112.387260 |
単純移動平均の150ですが、データの最初の149行目は算出が行えません。ですので、これらの行はデータから落としてあげましょう。インデックスも振り直します。
1 2 3 4 5 |
# nanのrowを削除 df = df[149:] df = df.reset_index(drop=True) |
SMA150とボリンジャーバンドがちゃんと処理できているか確認します。Matplotlibを使ってローソク足チャートを描いてみましょう。データが4万件と膨大なので一部のデータのみ切り出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ローソク足表示 fig = plt.figure(figsize=(18, 9)) ax = plt.subplot(1, 1, 1) candle_temp = df[2000:2100] candle_temp = candle_temp.reset_index() candlestick2_ohlc( ax, candle_temp["o"], candle_temp["h"], candle_temp["l"], candle_temp["c"], width=0.9, colorup="r", colordown="b" ) ax.plot(candle_temp['sma150']) ax.plot(candle_temp['mean']) ax.plot(candle_temp['up']) ax.plot(candle_temp['low']) |
少し見辛いですが緑、黄、赤がボリンジャーバンドです。青が単純移動平均150です。たまたま切り出した箇所ですが、ちょうどローソク足がSMA150を上から下へ抜けていますね。つまり上昇トレンドから下降トレンドへ転換した可能性が高い箇所のチャートです。(偶然w)
STEP3 プライスアクションを検出
続いて今回の主役でもプライスアクションのスラストアップを過去レートから検出します。プライスアップの詳細は「プライスアップを抽出する方法」でまとめたので、こちらご参照ください。
では一気に処理をしてしまいましょう。Pandasのメソッドチェーンで警告(Warning)が起きますが、意図通り処理できているので無視して大丈夫です。
1 2 3 4 |
# チェイニングのWarningを非表示 pd.options.mode.chained_assignment = None |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# データをずらす df['c1'] = df['c'].shift(1) df['c2'] = df['c'].shift(2) df['h1'] = df['h'].shift(1) df['h2'] = df['h'].shift(2) # 不要なデータを削除 df = df[2:] # rule 2 df['rule2'] = 0 rule_2_mask = (df['c1'] - df['h2']) > 0.0 df['rule2'][rule_2_mask] = 1 # rule 3 df['rule3'] = 0 rule_3_mask = (df['c'] - df['h1']) > 0.0 df['rule3'][rule_3_mask] = 1 |
1 2 3 4 5 6 7 |
# スラストアップが出現した場所を確認 rule2 = df['rule2'] == 1.0 rule3 = df['rule3'] == 1.0 print(df[ rule2 & rule3].shape) df[ rule2 & rule3][120:125] |
1 2 3 4 5 6 7 8 9 |
--出力 t c h l o mean std up low sma150 c1 c2 h1 h2 rule2 rule3 1016 2018-09-15 05:10:00 112.016 112.020 112.016 112.018 112.01220 0.010596 112.033392 111.991008 112.024220 112.014 112.007 112.014 112.009 1 1 1034 2018-09-15 05:34:00 112.010 112.010 112.005 112.005 111.99695 0.013446 112.023841 111.970059 112.023453 112.002 111.988 112.006 111.994 1 1 1045 2018-09-15 05:51:00 112.067 112.079 112.050 112.050 112.00905 0.020166 112.049383 111.968717 112.023853 112.046 112.025 112.046 112.028 1 1 1061 2018-09-17 06:38:00 112.043 112.044 112.035 112.040 112.05605 0.018875 112.093800 112.018300 112.026547 112.041 112.038 112.041 112.038 1 1 1095 2018-09-17 07:36:00 111.986 111.994 111.982 111.982 111.97500 0.012691 112.000381 111.949619 112.018220 111.980 111.960 111.980 111.960 1 1 |
rule2とrule3が適合した場所(値が1)のデータがスラストアップが出現した箇所です。ちなみにですが、rule1は今回は適用していません。前回の記事ではrule1で安値圏を適用するため単純移動平均との乖離を使いました。今回は安値圏・高値圏を問わず全てのスラストアップを抽出しました。
スラストアップがしっかり摘出できているか確認してみましょう。rule1とrule2が「1」のレコードを2箇所ほど抜き出してローソク足チャートに落としてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ローソク足表示 その1 fig = plt.figure(figsize=(18, 9)) ax = plt.subplot(1, 1, 1) candle_temp = df[837:877] candle_temp = candle_temp.reset_index() candlestick2_ohlc( ax, candle_temp["o"], candle_temp["h"], candle_temp["l"], candle_temp["c"], width=0.9, colorup="r", colordown="b" ) ax.plot(candle_temp['sma150']) ax.plot(candle_temp['mean']) ax.plot(candle_temp['up']) ax.plot(candle_temp['low']) |
赤矢印の部分がスラストアップです。ちゃんと抽出できていますね。スラストアップは「上昇トレンド」を示す買いのシグナルです。上の例を見てみると、スラストアップが出現した直後は一旦価格が戻りますが、その後は緩やかな上昇トレンドになっています。
もう一つ確認をしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ローソク足表示 その2 fig = plt.figure(figsize=(18, 9)) ax = plt.subplot(1, 1, 1) candle_temp = df[1025:1065] candle_temp = candle_temp.reset_index() candlestick2_ohlc( ax, candle_temp["o"], candle_temp["h"], candle_temp["l"], candle_temp["c"], width=0.9, colorup="r", colordown="b" ) ax.plot(candle_temp['sma150']) ax.plot(candle_temp['mean']) ax.plot(candle_temp['up']) ax.plot(candle_temp['low']) |
こちらのスラストアップでは、直後は少しだけレートが上がりましたが・・その後は下降トレンドへ転じています。つまり「だまし」の売買シグナルなわけです。このようなシグナルをアンサンブル学習を使って見抜きたい訳です。
STEP4 データの前処理
必要な特徴量やスラストアップをデータから作れました。次は機械学習モデルへ訓練させるためのデータの前処理を行ってあげましょう。
まずはスラストアップのフラグを作成します。前のステップではrule1とrule2が1の場所がスラストアップの出現場所でした。ルールを一纏めにしちゃいましょう。
1 2 3 4 5 6 7 |
# スラストアップのフラグ追加 df['thrustup'] = 0 df['thrustup'][ rule2 & rule3] = 1 del df['rule2'] del df['rule3'] |
続いて機械学習で推測するターゲットを作成しましょう。今回はスラストアップが出現して5分後に+2pipとなっていれば「成功」、出現して+2pipへ満たない場合は「失敗」と定義します。
ターゲットをどのように定義するのかも機械学習FX予想の醍醐味です。自分のトレードのスタイルに合わせてターゲットを検証すると、より便利な自分だけのトレードツールが出来上がります。
1 2 3 4 5 6 7 8 |
# ターゲットを抽出 = 5分後に+ 2pipで成功(値1) df['c5'] = df['c'].shift(-5) df['diff'] = df['c5'] - df['c'] positive = (df['diff'] > 0.02) & (df['thrustup'] == 1) df['target'] = 0 df['target'][positive] = 1 |
実際にスラストアップの2分後に+2pipとなった場所を確認してみます。
1 2 3 4 |
# 成功したシグナルの場所を確認 df[df['target'] == 1].head() |
1 2 3 4 5 6 7 8 9 |
--出力 t c h l o mean std up low sma150 c1 c2 h1 h2 thrustup c5 diff target 191 2018-09-14 14:52:00 111.858 111.860 111.854 111.855 111.86425 0.010740 111.885731 111.842769 111.847127 111.853 111.848 111.853 111.850 1 111.882 0.024 1 211 2018-09-14 15:12:00 111.882 111.888 111.882 111.883 111.87340 0.008864 111.891128 111.855672 111.847413 111.880 111.863 111.880 111.871 1 111.907 0.025 1 480 2018-09-14 19:57:00 111.801 111.802 111.792 111.792 111.78535 0.007562 111.800474 111.770226 111.812747 111.789 111.782 111.791 111.782 1 111.829 0.028 1 675 2018-09-14 23:14:00 112.010 112.011 111.995 111.996 112.01125 0.025387 112.062025 111.960475 111.958720 111.994 111.980 111.998 111.985 1 112.048 0.038 1 676 2018-09-14 23:15:00 112.027 112.027 112.007 112.007 112.01050 0.024642 112.059783 111.961217 111.959507 112.010 111.994 112.011 111.998 1 112.053 0.026 1 |
上記のレコードの480をローソク足に落として確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ローソク足表示 その1 fig = plt.figure(figsize=(18, 9)) ax = plt.subplot(1, 1, 1) candle_temp = df[460:500] candle_temp = candle_temp.reset_index() candlestick2_ohlc( ax, candle_temp["o"], candle_temp["h"], candle_temp["l"], candle_temp["c"], width=0.9, colorup="r", colordown="b" ) ax.plot(candle_temp['sma150']) ax.plot(candle_temp['mean']) ax.plot(candle_temp['up']) ax.plot(candle_temp['low']) |
赤矢印がスラストアップが検出された箇所です。その直後から綺麗な上昇トレンドを描いています。利用的なシグナルポイントですね。
最後にデータのクリーンアップをしましょう。使わないデータなどを削除して、特徴量・ターゲットへ分割します。また、訓練データとテストデータへの切り分けも行っちゃいましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# データクリーンアップ del df['t'] del df['c1'] del df['c2'] del df['c5'] del df['h1'] del df['h2'] del df['thrustup'] del df['diff'] # 特徴量とターゲットへ分割 X = df[df.columns[:-1]].values y = df['target'].values print(X.shape) print(y.shape) |
1 2 3 4 5 |
--出力 (40511, 9) (40511,) |
ターゲットの分布を確認してみましょう。今回のデータでシグナルが成功、失敗の割合を確認します。
1 2 3 4 |
# ターゲットの分布を確認 df['target'].value_counts() |
1 2 3 4 5 6 |
--出力 0 39923 1 588 Name: target, dtype: int64 |
成功したシグナルは588件と非常に少ないです。片方のクラスに偏ったskewed dataです。
続いて各特徴量の「標準化」を行いましょう。標準化ですが、全体の平均と標準偏差で求めます。いわゆる「正規化」と意味合いは同等です。
1 2 3 4 5 6 |
# 特徴量の標準化 sc = StandardScaler() sc.fit(X) X = sc.transform(X) |
最後に訓練データとテストデータに分割して前処理完了です。
1 2 3 4 |
# 訓練とテスデータへ分割 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) |
次のステップからはいよいよスタッキングのステージ1を進めていきましょう!
STEP5 ロジスティック回帰で推測
今回は3つの機械学習手法をスタッキングします。まずは機械学習の初歩の初歩、ロジスティック回帰のモデリングを行いましょう。
ロジスティック回帰の詳しい説明は「FXトレードでロジスティック回帰を独自テクニカル指標として活用する方法(機械学習初心者向け)」をご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Sklearnからインポート from sklearn.linear_model import LogisticRegression # モデル訓練 clf = LogisticRegression() clf.fit(X_train, y_train) # クラスに属する確率を出力(訓練/テストデータ) logi_train = clf.predict_proba(X_train) logi_test = clf.predict_proba(X_test) # 結果を出力 logi_test[0:5] |
1 2 3 4 5 6 7 8 |
--出力 array([[0.98949815, 0.01050185], [0.9858642 , 0.0141358 ], [0.96650468, 0.03349532], [0.98233325, 0.01766675], [0.98463593, 0.01536407]]) |
出力のNumpy配列の左側が0に属する確率です。右側は1に属する確率です。スタッキングを行うため、テストデータと訓練データの推測結果を出力しています。
確率の閾値を設定してテストデータの推測結果を評価してみましょう。閾値の設定は適当です。今回は1に属する確率が3%より大きければ「1」、それ以外は「0」としました。
1 2 3 4 |
# ターゲットを閾値で変更 logi_score = np.where(logi_test[:,1] > 0.03, 1, 0) |
1 2 3 4 5 |
# 混同行列 matrix = confusion_matrix(y_test, logi_score, labels=[1, 0]) matrix |
1 2 3 4 5 |
--出力 array([[ 13, 101], [ 230, 7759]]) |
まぁ微妙ではありますね。混同行列は分類問題のもっとも基本的な評価指標の一つです。上の結果として、モデルが売買シグナルが「成功」すると予測したのは全部で243件あり、そのうち本当に「成功」したのは13件という結果です。
ロジスティック回帰のモデルの評価指標を確認します。今回のように一部のクラスに偏ったデータの場合、正解率(Accuracy)は役に立ちません。ですので、精度(precision)、再現率(recall)、F値を使いましょう。
1 2 3 4 5 6 |
# モデルの評価指標 print(precision_score(y_test, logi_score)) print(recall_score(y_test, logi_score)) print(f1_score(y_test, logi_score)) |
1 2 3 4 5 6 |
--出力 0.053497942386831275 0.11403508771929824 0.07282913165266106 |
F値は精度と再現率を組み合わせた評価指標です。0〜1の間で出力され、値が1に近ければ近いほどモデルの精度は高いことを示します。
ロジスティック回帰は0.0728でした。最終的にスタッキングしてF値が改善すれば成功です。
STEP6 ランダムフォレストで推測
2つ目の手法は「ランダムフォレスト」を使いましょう。以前に当ブログで紹介した「決定木」のアンサンブル学習手法です。(参照:決定木でFX予想)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Sklearnをインポート from sklearn.ensemble import RandomForestClassifier # モデル訓練 RF = RandomForestClassifier(n_estimators = 500, max_depth=2, class_weight={0:1, 1:50}, random_state=42) RF = RF.fit(X_train, y_train) # 予測出力(訓練/テストデータ) rf_train = RF.predict_proba(X_train) rf_test = RF.predict_proba(X_test) # 確認 rf_test[0:5] |
1 2 3 4 5 6 7 8 |
--出力 array([[0.63952349, 0.36047651], [0.58511569, 0.41488431], [0.50001559, 0.49998441], [0.58325573, 0.41674427], [0.55052875, 0.44947125]]) |
Scikit-learnのランダムフォレストですが、クラスに属する確率を出力することが可能です。ロジスティック回帰と同様にテスト/訓練データの推測結果(確率)を出力しています。
ランダムフォレストの推測精度も確認してみましょう。確率の閾値は0.5としています。
1 2 3 4 5 6 7 8 |
# ターゲットを閾値で変更 rf_score = np.where(rf_test[:,1] > 0.5, 1, 0) # 混同行列 matrix = confusion_matrix(y_test, rf_score, labels=[1, 0]) matrix |
1 2 3 4 5 |
--出力 array([[ 10, 104], [ 143, 7846]]) |
パッとみた感じ、ロジスティック回帰よりは正しく推測ができているように見れます。F値も算出して確認してみましょう。
1 2 3 4 5 6 |
# 精度(precision)と検出率(Recall) print(precision_score(y_test, rf_score)) print(recall_score(y_test, rf_score)) print(f1_score(y_test, rf_score)) |
1 2 3 4 5 6 |
--出力 0.06535947712418301 0.08771929824561403 0.0749063670411985 |
ロジスティック回帰ではF値が0.0728でしたが、ランダムフォレストでは0.0749と若干ではありますが精度が高いのが確認できます。
STEP7 多層パーセプトロンで推測
3つ目の機械学習手法は「多層パーセプトロン」を使いましょう。多層パーセプトロンとはニューラルネットワークの種類の一つで、とても単純な構造を持ったニューラルネットワークです。
参照:TensorFlowでニューラルネットワークを使ってFX予想をする方法
ではやってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# ライブラリをインポート from sklearn.neural_network import MLPClassifier # 訓練 mlp = MLPClassifier(solver="adam", random_state=42, max_iter=100, verbose=True) mlp.fit(X_train, y_train) # 予測(確率) mlp_train = mlp.predict_proba(X_train) mlp_test = mlp.predict_proba(X_test) # 確認 mlp_test[0:5] |
1 2 3 4 5 6 7 8 |
--出力 array([[0.99119408, 0.00880592], [0.97665143, 0.02334857], [0.98076076, 0.01923924], [0.98459524, 0.01540476], [0.97588562, 0.02411438]]) |
同様の流れですが、確率の閾値を適当に設定して評価してみましょう。閾値は0.022としました。
1 2 3 4 5 6 7 8 |
# ターゲットを閾値で変更 mlp_score = np.where(mlp_test[:,1] > 0.022, 1, 0) # 混同行列 matrix = confusion_matrix(y_test, mlp_score, labels=[1, 0]) matrix |
1 2 3 4 5 |
--出力 array([[ 17, 97], [1187, 6802]]) |
これは明らかに他の2つよりも精度が悪いのが見て取れますね。F値を確認します。
1 2 3 4 5 6 |
# 評価指標を算出 print(precision_score(y_test, mlp_score)) print(recall_score(y_test, mlp_score)) print(f1_score(y_test, mlp_score)) |
1 2 3 4 5 6 |
--出力 0.014119601328903655 0.14912280701754385 0.02579666160849772 |
悪いですね・・。今までロジスティック回帰、ランダムフォレスト、多層パーセプトロンの3つの手法のモデリングを行いました。それぞれのF値は下記の通りです。
ロジスティック回帰:0.072
ランダムフォレスト:0.074
多層パーセプトロン:0.025
では、これら3つの手法はスタッキングしてみましょう!
STEP8 XGBoostでスタッキング
さて、いよいよ本題でもあるスタッキングです。すでに解説した通り、スタッキングでは3つのモデルの推測結果を特徴量として使い、新たな手法で推測を行います。
まずは今までの推測結果などを出力して確認して見ましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 今までの推測結果データを確認 print("Train Data------------------") print(y_train.shape) print(logi_train.shape) print(rf_train.shape) print(mlp_train.shape) print("Test Data------------------") print(y_test.shape) print(logi_test.shape) print(rf_test.shape) print(mlp_test.shape) print("Check------------------") print(y_test[33]) print(logi_test[33]) print(rf_test[33]) print(mlp_test[33]) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
--出力 Train Data------------------ (32408,) (32408, 2) (32408, 2) (32408, 2) Test Data------------------ (8103,) (8103, 2) (8103, 2) (8103, 2) Check------------------ 1 [0.97969375 0.02030625] [0.50483493 0.49516507] [0.98332249 0.01667751] |
訓練データ、テストデータ共にサイズがあっているので大丈夫そうです。また、最後のcheckではテストデータの33番目のレコードを確認しました。正解ラベルは「1」ですが、それぞれの手法で推測した1に属する確率は「0.02」「0.49」「0.01」と異なります。
では、XGBoostを使ってスタッキングしてみます。XGBoostを初めて扱う方は「FXの売買シグナルの「だまし」を機械学習で見破る方法(XGBoost編)」をご覧ください。
まずはアンサンブル学習(スタッキング)用に訓練データとテストデータを整えてあげましょう。下記のコードをみるとわかりますが、ステージ1で得た推測結果を特徴量として使っています。
1 2 3 4 5 6 7 8 |
# アンサンブル用 訓練データ作成 X_train_ens = pd.DataFrame(logi_train[:,1], columns=['logi']) X_train_ens['rf'] = rf_train[:, 1] X_train_ens['mlp'] = mlp_train[:, 1] X_train_ens = X_train_ens.values y_train_ens = y_train.copy() |
1 2 3 4 5 6 7 8 |
# アンサンブル用 テストデータ作成 X_test_ens = pd.DataFrame(logi_test[:,1], columns=['logi']) X_test_ens['rf'] = rf_test[:, 1] X_test_ens['mlp'] = mlp_test[:, 1] X_test_ens = X_test_ens.values y_test_ens = y_test.copy() |
では、XGBoostに頑張ってもらいましょう!XGBoostは為替レートのデータを学習している訳ではありません。くどい様ですが、他の手法が出力した推測結果を学習します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# XGBoostインポート import xgboost as xgb from xgboost import XGBClassifier # モデル訓練 xgboost = xgb.XGBClassifier(random_state=42) xgboost.fit(X_train_ens, y_train_ens) # テストデータで予測 fin_proba = xgboost.predict_proba(X_test_ens) fin_proba[0:5] |
1 2 3 4 5 6 7 8 |
--出力 array([[0.99773943, 0.00226058], [0.9809577 , 0.0190423 ], [0.93685687, 0.06314312], [0.9784069 , 0.02159309], [0.97920734, 0.02079269]], dtype=float32) |
テストデータでの推測が確認できます。スタッキングが上手く作用してれば、今までの3つの弱学習器のF値から改善しているはずです!ドキドキですね・・
XGBoostの推測値も確率ですので、同様に適当な閾値を設定してクラスへ変換しましょう。今回は0.034とします。
1 2 3 4 |
# ターゲットを閾値で変更 fin_score = np.where(fin_proba[:,1] > 0.034, 1, 0) |
ではまずは混同行列を出力してみます。
1 2 3 4 5 |
# 混同行列 matrix = confusion_matrix(y_test, fin_score, labels=[1, 0]) matrix |
1 2 3 4 5 |
--出力 array([[ 26, 88], [ 464, 7525]]) |
おおお!パッと見た感じ、改善してそうですね!3つのモデルをスタッキングして推測した結果、売買シグナルが実際に成功した「114件」のうち、「22件」は正しく予測できた様です。(微妙ですがw)
では、F値を確認してみましょう。
1 2 3 4 5 6 |
# F値を確認 print(precision_score(y_test, fin_score)) print(recall_score(y_test, fin_score)) print(f1_score(y_test, fin_score)) |
1 2 3 4 5 6 |
--出力 0.053061224489795916 0.22807017543859648 0.08609271523178806 |
よかったです!しっかりスタッキングした結果、改善しています!下記にステージ1の3つのモデルと、スタッキングした後のF値をまとめました。
ロジスティック回帰:0.072
ランダムフォレスト:0.074
多層パーセプトロン:0.025
スタッキング(XGBoost):0.086
しっかりスコアが改善しています!
まとめ
今回は中級者向けにアンサンブル学習のスタッキングを実装する流れをまとめました。初心者の方には少しハードルが高かったかもしれませんが・・FX機械学習トレードに慣れてくるとスタッキングをして1%でも精度を改善したくなる場面が確実に出てくると思います。
また今回は「あえて」高い精度が出ない様なデータと手法でまとめています。ここから、しっかり検証して改善をすると実践トレードでも十分に約立つモデルの構築が可能です。
改善のポイントとしては・・
・弱学習器のハイパーパラメータ調整
・XGBoostのハイパーパラメータ調整
・標準化/正規化の吟味
・特徴量エンジニアリング
・弱学習器の追加(今回は3つだがもっと必要)
是非、自分好みの最強な売買シグナルを作って見てください!
ブログ読んでいただきありがとうございます!Twitterでも色々と発信しているので、是非フォローお願いします!
ディスカッション
コメント一覧
まだ、コメントがありません