8回目で精度の高い学習モデルを構築するために「交差検証」「ROC 曲線とAUC」について実装追加しました。
11回目ではファンダメンタルズ指標を追加することで「AUC=0.70」を超えました。
これは、無謀にも「株の自動売買で億り人」を夢見て人生を浪費している一人の中年男の物語です。
今回は学習モデルの「ハイパーパラメータの調整」を実装を学び、加えて過学習の抑制についても検討してみます。
パラメータのチューニング
パラメータとは機械学習モデルにおける設定値や制限値のことです。ハイパーパラメータとも呼びます。
そして、モデルが最適解を出せるパラメータを走査し、設定することを「パラメータチューニング」といいます。
代表的なチューニング方法として2種についてまとめます。
グリードサーチ/ランダムサーチ
グリッドサーチは、あらかじめパラメータの候補値を指定し、その候補パラメータを組み合わせて学習を試行することにより最適なパラメータを走査する方法です。
ランダムサーチは、パラメータの設定範囲および試行回数を指定し、指定値範囲内から無作為に抽出したパラメータにより学習を試行することにより最適なパラメータを走査する方法です。
【メリット】
探索するパラメータの候補を把握しやすい
【デメリット】
組み合わせによる探索点の数が膨大になり得るため、探索するパラメータやその候補の数を多くすることができない
ベイズ最適化(Bayesian Optimization)
ベイズ最適化 (Bayesian Optimization) は、過去の実験結果から次の実験パラメータを、確率分布から求めることで最適化する手法です。
ランダムサーチではまったく精度が出なかったパラメータ付近も探索しますが、ベイズ最適化では探索履歴を使うことで精度が良い可能性の高いパラメータを効率良く探索することを試みます。
インストール
$ /c /Python38 /Scripts /pip install bayesian -optimization
Collecting bayesian -optimization
Downloading https : //files.pythonhosted.org/packages/72/0c/173ac467d0a53e33e41b 521e4ceba74a8ac7c7873d7b857a8fbdca88302d/bayesian-optimization-1.0.1.tar.gz
Requirement already satisfied : numpy > =1.9.0 in c : \ python38 \ lib \ site -packages ( from bayesian -optimization ) ( 1.17.4 )
Requirement already satisfied : scipy > =0.14.0 in c : \ python38 \ lib \ site -packages ( from bayesian -optimization ) ( 1.4.1 )
Requirement already satisfied : scikit -learn > =0.18.0 in c : \ python38 \ lib \ site -packages ( from bayesian -optimization ) ( 0.2 2 )
Requirement already satisfied : joblib > =0.11 in c : \ python38 \ lib \ site -packages ( from scikit -learn > =0.18.0 -> bayesian -opti mization ) ( 0.14.1 )
Installing collected packages : bayesian -optimization
Running setup . py install for bayesian -optimization : started
Running setup . py install for bayesian -optimization : finished with status 'done'
Successfully installed bayesian -optimization -1.0.1
実装方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
categorical_features = [ "grade" , "view" , "waterfront" ]
def bayes_parameter_opt_lgb ( X , y , init_round =15 , opt_round =25 , n_folds =5 , random_seed =6 , n_estimators =10000 , learning_rate =0.05 , output_process =False ) :
# prepare data
train_data = lgb . Dataset ( data =X , label =y , categorical_feature = categorical_features , free_raw_data =False )
# parameters
def lgb_eval ( num_leaves , feature_fraction , bagging_fraction , max_depth , lambda_l1 , lambda_l2 , min_split_gain , min_child_weight ) :
params = { 'application' : 'binary' , 'num_iterations' : n_estimators , 'learning_rate' : learning_rate , 'early_stopping_round' : 100 , 'metric' : 'auc' }
params [ "num_leaves" ] = int ( round ( num_leaves ) )
params [ 'feature_fraction' ] = max ( min ( feature_fraction , 1 ) , 0 )
params [ 'bagging_fraction' ] = max ( min ( bagging_fraction , 1 ) , 0 )
params [ 'max_depth' ] = int ( round ( max_depth ) )
params [ 'lambda_l1' ] = max ( lambda_l1 , 0 )
params [ 'lambda_l2' ] = max ( lambda_l2 , 0 )
params [ 'min_split_gain' ] = min_split_gain
params [ 'min_child_weight' ] = min_child_weight
cv_result = lgb . cv ( params , train_data , nfold =n_folds , seed =random_seed , stratified =True , verbose_eval =200 , metrics =[ 'auc' ] )
return max ( cv_result [ 'auc-mean' ] )
# range
lgbBO = BayesianOptimization ( lgb_eval , { 'num_leaves' : ( 24 , 45 ) ,
'feature_fraction' : ( 0.1 , 0.9 ) ,
'bagging_fraction' : ( 0.8 , 1 ) ,
'max_depth' : ( 5 , 8.99 ) ,
'lambda_l1' : ( 0 , 5 ) ,
'lambda_l2' : ( 0 , 3 ) ,
'min_split_gain' : ( 0.001 , 0.1 ) ,
'min_child_weight' : ( 5 , 50 ) } , random_state =0 )
# optimize
lgbBO . maximize ( init_points =init_round , n_iter =opt_round )
# output optimization process
if output_process ==True : lgbBO . points_to_csv ( "bayes_opt_result.csv" )
# return best parameters
return lgbBO . res [ 'max' ] [ 'max_params' ]
ベイズ最適化でのパラメータ探索
最近では hyperoptというライブラリが比較的よく使われています。
また2018年にoptunaというライブラリが公開されました。
hyperopt(ハイパーオプト)
TPE(tree-structured parzen estimator) というアルゴリズムで計算します。
次の設定をすることで、パラメータの探索を自動的に行い、探索したパラメータとそのときの評価指標によるスコアを出力できます。
最小化したい評価指標の設定
探索するパラメータの範囲を定義する
探索回数の指定
optuna(オプチュナ)
2018年末に公開されたフレームワークです。
開発会社は「Preferred Networks」。Chainerの生みの会社です。
最適化アルゴリズム自体はhyperoptと同様にTPEを用いていますが、次のような改善がされています。
Define by Run スタイルAPI
学習曲線を用いた試行の枝刈り
並列分散最適化
何のパラメータを学習させるか?
重要と思われるパラメータからチューニングします。重要とされる順序は次のとおりです。
その他、パラメータには特徴があります。
モデル訓練のスピードをあげる
bagging_fraction(初期値1.0)とbagging_freq(初期値0)を使う
feature_fraction(初期値1.0)で特徴量のサブサンプリングを指定
小さいmax_bin(初期値 255)を使う
save_binary(初期値 False)を使う
分散学習を使う
推測精度を向上させる
大きいmax_bin(初期値255)を使う
小さいlearning_rate(初期値0.1)と大きいnum_iterations(初期値100)を使う
大きいnum_leaves(初期値31)を使う
訓練データのレコード数を増やす(可能であれば)
過学習対策
小さいmax_binを使う(初期値255)
小さいnum_leavesを使う(初期値31)
min_data_in_leaf(初期値20)とmin_sum_hessian_in_leaf(初期値1e-3)を使う
bagging_fraction(初期値1.0)とbagging_freq(初期値0)を使う
feature_fraction(初期値1.0)で特徴量のサブサンプリングを指定
訓練データのレコード数を増やす(可能であれば)
lambda_l1(初期値0.0)、lambda_l2(初期値0.0)、min_gain_to_split(初期値0.0)で正則化を試す
max_depth(初期値-1)を指定して決定木が深くならないよう調整する
optunaを使った実装内容
前回、KFoldやAUC、交差検証も取り入れたので、それらを全て含めたクラスを定義しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import optuna
import numpy as np
import pandas as pd
from lightgbm import LGBMClassifier
from sklearn . metrics import roc_auc_score
from sklearn . model_selection import KFold , StratifiedKFold
# Optuna(ハイパーパラメータ自動最適化ツール)
class Objective :
def __init__ ( self , x , y , excluded_feats , num_folds = 4 , stratified = False ) :
self . x = x
self . y = y
self . excluded_feats = excluded_feats
self . stratified = stratified
self . num_folds = num_folds
def __call__ ( self , trial ) :
df_train = self . x
y = self . y
excluded_feats = self . excluded_feats
stratified = self . stratified
num_folds = self . num_folds
# Cross validation model
if stratified :
folds = StratifiedKFold ( n_splits = num_folds , shuffle = True , random_state = 1001 )
else :
folds = KFold ( n_splits = num_folds , shuffle = True , random_state = 1001 )
oof_preds = np . zeros ( df_train . shape [ 0 ] )
feats = [ f for f in df_train . columns if f not in excluded_feats ]
for n_fold , ( train_idx , valid_idx ) in enumerate ( folds . split ( df_train [ feats ] , y ) ) :
X_train , y_train = df_train [ feats ] . iloc [ train_idx ] , y . iloc [ train_idx ]
X_valid , y_valid = df_train [ feats ] . iloc [ valid_idx ] , y . iloc [ valid_idx ]
clf = LGBMClassifier ( objective = 'binary' ,
reg_alpha = trial . suggest_loguniform ( 'reg_alpha' , 1e -4 , 100.0 ) ,
reg_lambda = trial . suggest_loguniform ( 'reg_lambda' , 1e -4 , 100.0 ) ,
num_leaves = trial . suggest_int ( 'num_leaves' , 10 , 40 ) ,
silent = True )
# trainとvalidを指定し学習
clf . fit ( X_train , y_train , eval_set = [ ( X_train , y_train ) , ( X_valid , y_valid ) ] ,
eval_metric = 'auc' , verbose = 0 , early_stopping_rounds = 200 )
oof_preds [ valid_idx ] = clf . predict_proba ( X_valid , num_iteration = clf . best_iteration_ ) [ : , 1 ]
accuracy = roc_auc_score ( y , oof_preds )
return 1.0 - accuracy
def get_target_value ( df ) :
# 予測値(3日後の始値の上昇値)
df [ 'target' ] = ( df [ 'open' ] . shift ( -3 ) - df [ 'open' ] . shift ( -1 ) ) / df [ 'open' ] . shift ( -1 )
df . loc [ ( df [ 'target' ] > 0.03 ) , 'target' ] = 1
df . loc [ ( -0.03 > df [ 'target' ] ) , 'target' ] = 0
return df
if __name__ == '__main__' :
df_train = pd . read_csv ( "tosho/7203.csv" , skiprows =0 ,
skipfooter =0 , engine ="python" ,
names =( "date" , "open" , "high" , "low" , "close" , "volume" ) )
df_train = get_target_value ( df_train )
df_train = df_train . dropna ( subset =[ "target" ] )
df_train = df_train [ ( df_train [ 'target' ] == 1 ) | ( df_train [ 'target' ] == 0 ) ]
excluded_feats = [ 'target' , 'date' ]
# ハイパーパラメータ探索
objective = Objective ( x =df_train , y =df_train [ 'target' ] ,
excluded_feats =excluded_feats , num_folds = 5 , stratified = True )
study = optuna . create_study ( sampler = optuna . samplers . RandomSampler ( seed = 0 ) )
study . optimize ( objective , n_trials = 50 )
結果は次のようになっています。
[ I 2020 -02 -13 08 : 54 : 40 , 769 ] Finished trial #0 resulted in value: 0.4242554162931452. Current best value is 0.4242554162931452 with parameters: {'reg_alpha': 0.19628224813442816, 'reg_lambda': 1.9549524484259886, 'num_leaves': 13}.
[ I 2020 -02 -13 08 : 54 : 41 , 585 ] Finished trial #1 resulted in value: 0.4176141583656743. Current best value is 0.4176141583656743 with parameters: {'reg_alpha': 0.022735723377092197, 'reg_lambda': 10.386580256500284, 'num_leaves': 18}.
. . . .
[ I 2020 -02 -13 08 : 55 : 25 , 740 ] Finished trial #49 resulted in value: 0.42349120530966555. Current best value is 0.40829885799874166 with parameters: {'reg_alpha': 0.053599319721492705, 'reg_lambda': 67.99782871248236, 'num_leaves': 40}.
出力されたパラメーターを使って実際に学習させてみます。
# LightGBM
clf = LGBMClassifier ( reg_alpha = 0.44004414216369864 ,
reg_lambda = 0.07343092808809583 ,
num_leaves = 29 )
# trainとvalidを指定し学習
7203
Starting cross_validation . Train shape : ( 1201 , 22 ) , test shape : ( 5409 , 21 )
Full AUC score 0.689215
[ 0.30833605 0.28220892 0.27507515 . . . 0.32182241 0.34561377 0.50051291 ]
AUCの出力は「0.69」です。
実際には精度が上がる銘柄と上がらない銘柄がありました。
max_depthを指定して過学習を防いでみる
「max_depth=4」を指定してLightGBMを解いてみます。
作られた決定木です。
これぐらいの木であれば過学習は避けられているかもしれません。
因みに「AUC=0.65」程度でした。
下がりましたが、前回スコアを0.7まで上げていたので昔の値に戻った程度です。
なお「1998/01/05~2015/12/14」を利用して学習させています。
つまり「2015/12/15~2020/01/24」が未知の予測(フォワードテスト)となります。
その結果です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
株価データ: 日足
銘柄リスト: 日経225 採用銘柄
1998 /01 /05 ~2020 /02 /14 における成績です。
----------------------------------------
全トレード数 7269
勝ちトレード数( 勝率) 5357 ( 73.70 % )
負けトレード数( 負率) 1912 ( 26.30 % )
全トレード平均利率 3.09 %
勝ちトレード平均利率 4.88 %
負けトレード平均損率 -1.94 %
勝ちトレード最大利率 85.07 %
負けトレード最大損率 -13.97 %
全トレード平均期間 4.44
勝ちトレード平均期間 4.43
負けトレード平均期間 4.44
----------------------------------------
必要資金 ¥9 , 976 , 900
最大ポジション( 簿価) ¥9 , 997 , 900
最大ポジション( 時価) ¥11 , 823 , 600
純利益 ¥207 , 281 , 100
勝ちトレード総利益 ¥240 , 597 , 300
負けトレード総損失 -¥33 , 316 , 270
全トレード平均利益 ¥28 , 516
勝ちトレード平均利益 ¥44 , 913
負けトレード平均損失 -¥17 , 425
勝ちトレード最大利益 ¥797 , 500
負けトレード最大損失 -¥129 , 600
プロフィットファクター 7.22
最大ドローダウン( 簿価) -¥1 , 407 , 968
最大ドローダウン( 時価) -¥1 , 636 , 608
----------------------------------------
現在進行中のトレード数 0
----------------------------------------
平均年利 98.93 %
平均年利( 直近5 年) 7.56 %
最大連勝 33 回
最大連敗 8 回
----------------------------------------
[ 年度別レポート]
年度 取引回数 運用損益 年利 勝率 PF 最大DD
2020 年 45 回 -¥238 , 900 円 -2.39 % 37.78 % 0.53 倍 -5.27 %
2019 年 514 回 ¥954 , 900 円 9.57 % 54.67 % 1.20 倍 -10.43 %
2018 年 472 回 -¥483 , 300 円 -4.84 % 49.58 % 0.90 倍 -13.06 %
2017 年 494 回 ¥1 , 165 , 100 円 11.68 % 55.47 % 1.38 倍 -6.98 %
2016 年 629 回 ¥2 , 372 , 400 円 23.78 % 53.26 % 1.29 倍 -13.97 %
2015 年 525 回 ¥12 , 996 , 000 円 130.26 % 77.71 % 9.09 倍 -5.07 %
2014 年 325 回 ¥9 , 181 , 900 円 92.03 % 79.08 % 10.14 倍 -5.63 %
2013 年 435 回 ¥15 , 987 , 500 円 160.25 % 82.99 % 15.71 倍 -6.75 %
2012 年 433 回 ¥12 , 356 , 400 円 123.85 % 77.83 % 9.82 倍 -4.59 %
2011 年 246 回 ¥8 , 249 , 500 円 82.69 % 84.55 % 15.91 倍 -4.23 %
2010 年 285 回 ¥7 , 295 , 700 円 73.13 % 80.70 % 9.04 倍 -6.90 %
2009 年 326 回 ¥16 , 984 , 600 円 170.24 % 85.89 % 21.34 倍 -7.58 %
2008 年 320 回 ¥19 , 998 , 300 円 200.45 % 92.19 % 36.55 倍 -11.74 %
2007 年 262 回 ¥9 , 402 , 500 円 94.24 % 88.17 % 22.73 倍 -5.67 %
2006 年 251 回 ¥8 , 826 , 400 円 88.47 % 82.07 % 15.88 倍 -10.34 %
2005 年 228 回 ¥8 , 600 , 800 円 86.21 % 86.40 % 25.11 倍 -3.90 %
2004 年 191 回 ¥6 , 512 , 900 円 65.28 % 82.72 % 12.81 倍 -8.82 %
2003 年 338 回 ¥17 , 201 , 800 円 172.42 % 89.05 % 42.56 倍 -4.09 %
2002 年 268 回 ¥12 , 570 , 700 円 126.00 % 88.06 % 29.07 倍 -3.27 %
2001 年 287 回 ¥14 , 371 , 800 円 144.05 % 86.76 % 25.50 倍 -5.81 %
2000 年 395 回 ¥22 , 974 , 300 円 230.27 % 89.87 % 45.68 倍 -3.74 %
利益曲線は次のとおりです。
前回同様、フォワードテスト後に寝ています。
決定木を4階層にすることで、勝率、プロフィットファクターは下がりましたがフォワードテストの勝率が少しだけ改善しました。
誤差の範囲かもしれませんが、多少の自動売買の活路がみえました。
ちなみに「max_depth=3」の場合は次の通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
全トレード数 5118
勝ちトレード数( 勝率) 3729 ( 72.86 % )
負けトレード数( 負率) 1389 ( 27.14 % )
純利益 ¥143 , 632 , 800
プロフィットファクター 6.87
平均年利 70.78 %
平均年利( 直近5 年) 8.37 %
----------------------------------------
[ 年度別レポート]
年度 取引回数 運用損益 年利 勝率 PF 最大DD
2020 年 35 回 -¥21 , 000 円 -0.22 % 60.00 % 0.93 倍 -5.71 %
2019 年 397 回 ¥1 , 323 , 500 円 13.70 % 55.16 % 1.40 倍 -11.63 %
2018 年 324 回 ¥166 , 100 円 1.72 % 51.85 % 1.05 倍 -10.58 %
2017 年 355 回 ¥509 , 100 円 5.27 % 54.93 % 1.22 倍 -6.98 %
2016 年 531 回 ¥2 , 065 , 900 円 21.38 % 54.05 % 1.33 倍 -12.66 %
2015 年 388 回 ¥9 , 353 , 200 円 96.79 % 78.09 % 9.02 倍 -5.13 %
2014 年 208 回 ¥6 , 372 , 300 円 65.95 % 84.13 % 13.06 倍 -5.40 %
2013 年 304 回 ¥12 , 330 , 800 円 127.61 % 84.87 % 21.07 倍 -4.00 %
2012 年 352 回 ¥9 , 451 , 000 円 97.81 % 76.14 % 8.77 倍 -6.80 %
2011 年 176 回 ¥6 , 643 , 600 円 68.75 % 80.68 % 13.49 倍 -4.44 %
2010 年 149 回 ¥4 , 257 , 000 円 44.05 % 81.21 % 11.15 倍 -3.81 %
2009 年 259 回 ¥14 , 073 , 200 円 145.64 % 83.01 % 16.30 倍 -7.58 %
2008 年 229 回 ¥13 , 998 , 300 円 144.87 % 89.08 % 18.55 倍 -12.69 %
2007 年 128 回 ¥4 , 207 , 200 円 43.54 % 87.50 % 19.93 倍 -3.47 %
2006 年 126 回 ¥4 , 041 , 000 円 41.82 % 78.57 % 9.26 倍 -6.20 %
2005 年 96 回 ¥3 , 661 , 700 円 37.89 % 85.42 % 20.69 倍 -7.82 %
2004 年 92 回 ¥3 , 572 , 600 円 36.97 % 84.78 % 14.46 倍 -4.20 %
2003 年 222 回 ¥10 , 508 , 400 円 108.75 % 88.29 % 37.16 倍 -4.78 %
2002 年 214 回 ¥10 , 475 , 700 円 108.41 % 88.32 % 32.11 倍 -5.68 %
2001 年 222 回 ¥10 , 136 , 900 円 104.90 % 81.98 % 15.97 倍 -7.33 %
2000 年 311 回 ¥16 , 506 , 300 円 170.82 % 87.78 % 31.52 倍 -4.44 %
利益曲線は次のとおりです。
「max_depth=4」の場合と大きな差はありません。
これ以上の深さの変更には意味は無さそうです。
まとめ
Optunaでパラメータチューニングは、一度実装できれば非常に簡単に出来ることが分かりました。
しかしパラメータを変更すると、逆に精度が落ちてしまうこともあり、非常に使い辛いです。
また株価予測でチューニングすべきは過学習にならない為であり、そのためには「max_depth」に多少の価値があることが分かりました。