見出し画像

第12章:戦略をブースティングする 第1節: ブースティング・ベースライン

はじめに

とうとう第12章です。ここで教師あり学習は終わって、13章以降は深層学習含めた教師なし学習をメインに取り扱います。また、教師あり学習のケースでも深層学習を取り扱ったりします。

※以下は個人的な見解になります。

この章まで出来れば機械学習及びファクター投資の知識としてはほぼ十分かと思われます。

実際、深層学習を用いたトレーディング手法はかなり研究されておりますが、難易度も高く、投資においてはブラックボックスになること自体がリスクであるので、実務上で使われるためにはかなりのハードルをクリアする必要があります。

それと比べて、ファクター投資とアンサンブルモデルによる学習は、負けた時にファクターがこの場面では効かなかった等、言い訳がつけやすく、モデルの向上もさせやすいため、難易度はどちらも高いですが、扱いやすさは断然ファクター研究の方になります。

インポートと設定

%matplotlib inline

import sys, os
import warnings
from time import time
from itertools import product
import joblib
from pathlib import Path
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns

from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
from sklearn.model_selection import cross_validate
from sklearn.dummy import DummyClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.inspection import partial_dependence, plot_partial_dependence
from sklearn.metrics import roc_auc_score
sys.path.insert(1, os.path.join(sys.path[0], '..'))
from utils import format_time
results_path = Path('results', 'baseline')
if not results_path.exists():
   results_path.mkdir(exist_ok=True, parents=True)
warnings.filterwarnings('ignore')
sns.set_style("whitegrid")
idx = pd.IndexSlice
np.random.seed(42)

データの準備

4章アルファ-ファクター研究: 第一節: 特徴量エンジニアリングからデータを作成してください。

DATA_STORE = '../data/assets.h5'
def get_data(start='2000', end='2018', task='classification', holding_period=1, dropna=False):
   
   idx = pd.IndexSlice
   target = f'target_{holding_period}m'
   with pd.HDFStore(DATA_STORE) as store:
       df = store['engineered_features']

   if start is not None and end is not None:
       df = df.loc[idx[:, start: end], :]
   if dropna:
       df = df.dropna()
       
   y = (df[target]>0).astype(int)
   X = df.drop([c for c in df.columns if c.startswith('target')], axis=1)
   return y, X

カテゴリーに分解する

cat_cols = ['year', 'month', 'age', 'msize', 'sector']
def factorize_cats(df, cats=['sector']):
   cat_cols = ['year', 'month', 'age', 'msize'] + cats
   for cat in cats:
       df[cat] = pd.factorize(df[cat])[0]
   df.loc[:, cat_cols] = df.loc[:, cat_cols].fillna(-1).astype(int)
   return df

ONE-HOT エンコーディング

One-hotに関してはこちら
以下引用

デジタル回路において、one-hot(ワン・ホット)は1つだけHigh(1)であり、他はLow(0)であるようなビット列のことを指す。 類似のものとして、0が1つだけで、他がすべて1であるようなビット列をone-cold(ワン・コールド)と呼ぶことがある。
def get_one_hot_data(df, cols=cat_cols[:-1]):
   df = pd.get_dummies(df,
                       columns=cols + ['sector'],
                       prefix=cols + [''],
                       prefix_sep=['_'] * len(cols) + [''])
   return df.rename(columns={c: c.replace('.0', '') for c in df.columns})

ホールドアウト法

ホールドアウト法に関しては、こちら

以下引用です。

教師あり学習を行う上で、モデルを作成し評価するための基本的な手法
教師ありデータを学習データとテストデータに分割する。
学習データでモデルを作成し、テストデータで性能を図る。
より効果的な方法として、データを学習データ、検証データ、テストデータと3つに分割し、検証データによって複数のモデルを評価・選択して最終的にテストデータで汎化誤差を評価する方法がある。

よく取られる手法です。

def get_holdout_set(target, features, period=6):
   idx = pd.IndexSlice
   label = target.name
   dates = np.sort(y.index.get_level_values('date').unique())
   cv_start, cv_end = dates[0], dates[-period - 2]
   holdout_start, holdout_end = dates[-period - 1], dates[-1]

   df = features.join(target.to_frame())
   train = df.loc[idx[:, cv_start: cv_end], :]
   y_train, X_train = train[label], train.drop(label, axis=1)

   test = df.loc[idx[:, holdout_start: holdout_end], :]
   y_test, X_test = test[label], test.drop(label, axis=1)
   return y_train, X_train, y_test, X_test

データの読み込み

y, features = get_data()
X_dummies = get_one_hot_data(features)
X_factors = factorize_cats(features)
X_factors.info()
'''
<class 'pandas.core.frame.DataFrame'>
MultiIndex: 358914 entries, ('A', Timestamp('2001-01-31 00:00:00')) to ('ZUMZ', Timestamp('2018-02-28 00:00:00'))
Data columns (total 27 columns):
#   Column         Non-Null Count   Dtype  
---  ------         --------------   -----  
0   return_1m      358914 non-null  float64
1   return_2m      358914 non-null  float64
2   return_3m      358914 non-null  float64
3   return_6m      358914 non-null  float64
4   return_9m      358914 non-null  float64
5   return_12m     358914 non-null  float64
6   Mkt-RF         358914 non-null  float64
7   SMB            358914 non-null  float64
8   HML            358914 non-null  float64
9   RMW            358914 non-null  float64
10  CMA            358914 non-null  float64
11  momentum_2     358914 non-null  float64
12  momentum_3     358914 non-null  float64
13  momentum_6     358914 non-null  float64
14  momentum_9     358914 non-null  float64
15  momentum_12    358914 non-null  float64
16  year           358914 non-null  int64  
17  month          358914 non-null  int64  
18  return_1m_t-1  357076 non-null  float64
19  return_1m_t-2  355238 non-null  float64
20  return_1m_t-3  353400 non-null  float64
21  return_1m_t-4  351562 non-null  float64
22  return_1m_t-5  349724 non-null  float64
23  return_1m_t-6  347886 non-null  float64
24  age            358914 non-null  int64  
25  msize          358914 non-null  int64  
26  sector         358914 non-null  int64  
dtypes: float64(22), int64(5)
memory usage: 85.3+ MB
'''
y_clean, features_clean = get_data(dropna=True)
X_dummies_clean = get_one_hot_data(features_clean)
X_factors_clean = factorize_cats(features_clean)

交差検証セットアップ

カスタム時系列Kフォールド生成器

class OneStepTimeSeriesSplit:
   """Generates tuples of train_idx, test_idx pairs
   Assumes the index contains a level labeled 'date'"""

   def __init__(self, n_splits=3, test_period_length=1, shuffle=False):
       self.n_splits = n_splits
       self.test_period_length = test_period_length
       self.shuffle = shuffle

   @staticmethod
   def chunks(l, n):
       for i in range(0, len(l), n):
           yield l[i:i + n]

   def split(self, X, y=None, groups=None):
       unique_dates = (X.index
                       .get_level_values('date')
                       .unique()
                       .sort_values(ascending=False)
                       [:self.n_splits*self.test_period_length])

       dates = X.reset_index()[['date']]
       for test_date in self.chunks(unique_dates, self.test_period_length):
           train_idx = dates[dates.date < min(test_date)].index
           test_idx = dates[dates.date.isin(test_date)].index
           if self.shuffle:
               np.random.shuffle(list(train_idx))
           yield train_idx, test_idx

   def get_n_splits(self, X, y, groups=None):
       return self.n_splits
cv = OneStepTimeSeriesSplit(n_splits=12, 
                           test_period_length=1, 
                           shuffle=False)
run_time = {}

CV 評価関数

metrics = {'balanced_accuracy': 'Accuracy' ,
          'roc_auc': 'AUC',
          'neg_log_loss': 'Log Loss',
          'f1_weighted': 'F1',
          'precision_weighted': 'Precision',
          'recall_weighted': 'Recall'
}
def run_cv(clf, X=X_dummies, y=y, metrics=metrics, cv=cv, fit_params=None, n_jobs=-1):
   start = time()
   scores = cross_validate(estimator=clf,
                         X=X,
                         y=y,
                         scoring=list(metrics.keys()),
                         cv=cv,
                         return_train_score=True,
                         n_jobs=n_jobs,
                         verbose=1,
                         fit_params=fit_params)
   duration = time() - start
   return scores, duration

CV結果ハンドラー関数

CVの結果を操作してプロットする補助関数

def stack_results(scores):
   columns = pd.MultiIndex.from_tuples(
       [tuple(m.split('_', 1)) for m in scores.keys()],
       names=['Dataset', 'Metric'])
   data = np.array(list(scores.values())).T
   df = (pd.DataFrame(data=data,
                      columns=columns)
         .iloc[:, 2:])
   results = pd.melt(df, value_name='Value')
   results.Metric = results.Metric.apply(lambda x: metrics.get(x))
   results.Dataset = results.Dataset.str.capitalize()
   return results
def plot_result(df, model=None, fname=None):
   m = list(metrics.values())
   g = sns.catplot(x='Dataset', 
                   y='Value', 
                   hue='Dataset', 
                   col='Metric',
                   data=df, 
                   col_order=m,
                   order=['Train', 'Test'],
                   kind="box", 
                   col_wrap=3,
                   sharey=False,
                   height=4, aspect=1.2)
   df = df.groupby(['Metric', 'Dataset']).Value.mean().unstack().loc[m]
   for i, ax in enumerate(g.axes.flat):
       s = f"Train: {df.loc[m[i], 'Train']:>7.4f}\nTest:  {df.loc[m[i], 'Test'] :>7.4f}"
       ax.text(0.05, 0.85, s, fontsize=10, transform=ax.transAxes, 
               bbox=dict(facecolor='white', edgecolor='grey', boxstyle='round,pad=0.5'))
   g.fig.suptitle(model, fontsize=16)
   g.fig.subplots_adjust(top=.9)
   if fname:
       g.savefig(fname, dpi=300);

ベースライン分類器

dummy_clf = DummyClassifier(strategy='stratified',
                           random_state=42)
algo = 'dummy_clf'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   dummy_cv_result, run_time[algo] = run_cv(dummy_clf)
   joblib.dump(dummy_cv_result, fname)
else:
   dummy_cv_result = joblib.load(fname)
dummy_result = stack_results(dummy_cv_result)
dummy_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像1

plot_result(dummy_result, model='Dummy Classifier')

画像2

ランダムフォレスト

設定

rf_clf = RandomForestClassifier(n_estimators=100,
                               criterion='gini', 
                               max_depth=None, 
                               min_samples_split=2, 
                               min_samples_leaf=1, 
                               min_weight_fraction_leaf=0.0, 
                               max_features='auto',
                               max_leaf_nodes=None, 
                               min_impurity_decrease=0.0, 
                               min_impurity_split=None, 
                               bootstrap=True, 
                               oob_score=True, 
                               n_jobs=-1,
                               random_state=42, 
                               verbose=1)

交差検証

algo = 'random_forest'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   rf_cv_result, run_time[algo] = run_cv(rf_clf, y=y_clean, X=X_dummies_clean)
   joblib.dump(rf_cv_result, fname)
else:
   rf_cv_result = joblib.load(fname)

結果のプロット

rf_result = stack_results(rf_cv_result)
rf_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像3

plot_result(rf_result, model='Random Forest')

画像4

AdaBoost

前の章で説明したように、max_depthへの変更は、min_samples_splitなどの調整を使用して、適切な正則化制約と組み合わせる必要があります。

base_estimator = DecisionTreeClassifier(criterion='gini', 
                                       splitter='best',
                                       max_depth=1, 
                                       min_samples_split=2, 
                                       min_samples_leaf=20, 
                                       min_weight_fraction_leaf=0.0,
                                       max_features=None, 
                                       random_state=None, 
                                       max_leaf_nodes=None, 
                                       min_impurity_decrease=0.0, 
                                       min_impurity_split=None, 
                                       class_weight=None)

設定

ada_clf = AdaBoostClassifier(base_estimator=base_estimator,
                            n_estimators=100,
                            learning_rate=1.0,
                            algorithm='SAMME.R',
                            random_state=42)

交差検証

algo = 'adaboost'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   ada_cv_result, run_time[algo] = run_cv(ada_clf, y=y_clean, X=X_dummies_clean)
   joblib.dump(ada_cv_result, fname)
else:
   ada_cv_result = joblib.load(fname)

結果のプロット

ada_result = stack_results(ada_cv_result)
ada_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像5

plot_result(ada_result, model='AdaBoost')

画像6

GradientBoostingClassifier

設定

gb_clf = GradientBoostingClassifier(loss='deviance',                # deviance = logistic reg; exponential: AdaBoost
                                   learning_rate=0.1,              # shrinks the contribution of each tree
                                   n_estimators=100,               # number of boosting stages
                                   subsample=1.0,                  # fraction of samples used t fit base learners
                                   criterion='friedman_mse',       # measures the quality of a split
                                   min_samples_split=2,            
                                   min_samples_leaf=1, 
                                   min_weight_fraction_leaf=0.0,   # min. fraction of sum of weights
                                   max_depth=3,                    # opt value depends on interaction
                                   min_impurity_decrease=0.0, 
                                   min_impurity_split=None, 
                                   init=None, 
                                   random_state=None, 
                                   max_features=None, 
                                   verbose=0, 
                                   max_leaf_nodes=None, 
                                   warm_start=False, 
                                   presort='auto', 
                                   validation_fraction=0.1, 
                                   n_iter_no_change=None, 
                                   tol=0.0001)

交差検証

algo = 'sklearn_gbm'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   gb_cv_result, run_time[algo] = run_cv(gb_clf, y=y_clean, X=X_dummies_clean)
   joblib.dump(gb_cv_result, fname)
else:
   gb_cv_result = joblib.load(fname)

結果のプロット

gb_result = stack_results(gb_cv_result)
gb_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像7

plot_result(gb_result, model='Gradient Boosting Classifier')

画像8

Partial Dependence Plot

こちらの記事参考。

以下引用です。

各変数の重要度がわかったら、次に行うべきは重要な変数とアウトカムの関係を見ることだと思います。 ただ、一般にブラックボックスモデルにおいてインプットとアウトカムの関係は非常に複雑で、可視化することは困難です。
そこで、複雑な関係を要約する手法の一つにPartial Dependence Plot(PDP)があります。PDPは興味のある変数以外の影響を周辺化して消してしまうことで、インプットとアウトカムの関係を単純化しようというものです。
X_ = X_factors_clean.drop(['year', 'month'], axis=1)
fname = results_path / f'{algo}_model.joblib'
if not Path(fname).exists():
   gb_clf.fit(y=y_clean, X=X_)
   joblib.dump(gb_clf, fname)
else:
   gb_clf = joblib.load(fname)
# mean accuracy
gb_clf.score(X=X_, y=y_clean)
'''
0.5611655700281992
'''
y_score = gb_clf.predict_proba(X_)[:, 1]
roc_auc_score(y_score=y_score, y_true=y_clean)
'''
0.5640006909636653
'''

特徴量重要度

(pd.Series(gb_clf.feature_importances_, 
         index=X_.columns)
.sort_values(ascending=False)
.head(15)).sort_values().plot.barh(title='Feature Importance')
sns.despine()
plt.tight_layout();

画像9

One-wayとTwo-way PDP

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 10))

plot_partial_dependence(
   estimator=gb_clf,
   X=X_,
   features=['return_12m', 'return_6m', 'CMA', ('return_12m', 'return_6m')],
   percentiles=(0.05, 0.95),
   n_jobs=-1,
   n_cols=2,
   response_method='decision_function',
   grid_resolution=250,
   ax=axes)

for i, j in product([0, 1], repeat=2):
   if i!=1 or j!= 0:
       axes[i][j].xaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y))) 

axes[1][1].yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y))) 

axes[0][0].set_ylabel('Partial Dependence')
axes[1][0].set_ylabel('Partial Dependence')
axes[0][0].set_xlabel('12-Months Return')
axes[0][1].set_xlabel('6-Months Return')
axes[1][0].set_xlabel('Conservative Minus Aggressive')

axes[1][1].set_xlabel('12-Month Return')
axes[1][1].set_ylabel('6-Months Return')
fig.suptitle('Partial Dependence Plots', fontsize=16)
fig.tight_layout()
fig.subplots_adjust(top=.95)
fig.savefig('figures/partial_dep_2d', dpi=300);

画像10

Two-way partial dependence 3D plot

targets = ['return_12m', 'return_6m']
pdp, axes = partial_dependence(estimator=gb_clf,
                              features=targets,
                              X=X_,
                              grid_resolution=100)

XX, YY = np.meshgrid(axes[0], axes[1])
Z = pdp[0].reshape(list(map(np.size, axes))).T

fig = plt.figure(figsize=(14, 8))
ax = Axes3D(fig)
surface = ax.plot_surface(XX, YY, Z,
                         rstride=1,
                         cstride=1,
                         cmap=plt.cm.BuPu,
                         edgecolor='k')
ax.set_xlabel('12-Month Return')
ax.set_ylabel('6-Month Return')
ax.set_zlabel('Partial Dependence')
ax.view_init(elev=22, azim=30)
ax.yaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y))) 
ax.xaxis.set_major_formatter(FuncFormatter(lambda y, _: '{:.0%}'.format(y))) 

# fig.colorbar(surface)
fig.suptitle('Partial Dependence by 6- and 12-month Returns', fontsize=16)
fig.tight_layout()
fig.savefig('figures/partial_dep_3d', dpi=300)

画像11

XGBoost

設定

xgb_clf = XGBClassifier(max_depth=3,                  # Maximum tree depth for base learners.
                       learning_rate=0.1,            # Boosting learning rate (xgb's "eta")
                       n_estimators=100,             # Number of boosted trees to fit.
                       silent=True,                  # Whether to print messages while running
                       objective='binary:logistic',  # Task and objective or custom objective function
                       booster='gbtree',             # Select booster: gbtree, gblinear or dart
#                         tree_method='gpu_hist',
                       n_jobs=-1,                    # Number of parallel threads
                       gamma=0,                      # Min loss reduction for further splits
                       min_child_weight=1,           # Min sum of sample weight(hessian) needed
                       max_delta_step=0,             # Max delta step for each tree's weight estimation
                       subsample=1,                  # Subsample ratio of training samples
                       colsample_bytree=1,           # Subsample ratio of cols for each tree
                       colsample_bylevel=1,          # Subsample ratio of cols for each split
                       reg_alpha=0,                  # L1 regularization term on weights
                       reg_lambda=1,                 # L2 regularization term on weights
                       scale_pos_weight=1,           # Balancing class weights
                       base_score=0.5,               # Initial prediction score; global bias
                       random_state=42)              # random seed

交差検証

algo = 'xgboost'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   xgb_cv_result, run_time[algo] = run_cv(xgb_clf)
   joblib.dump(xgb_cv_result, fname)
else:
   xgb_cv_result = joblib.load(fname)

結果のプロット

xbg_result = stack_results(xgb_cv_result)
xbg_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像12

plot_result(xbg_result, model='XG Boost', fname=f'figures/{algo}_cv_result')

画像13

特徴量重要度

xgb_clf.fit(X=X_dummies, y=y)
fi = pd.Series(xgb_clf.feature_importances_, 
              index=X_dummies.columns)
fi.nlargest(25).sort_values().plot.barh(figsize=(10, 5), 
                                       title='Feature Importance')
sns.despine()
plt.tight_layout();

画像14

LightGBM

設定

lgb_clf = LGBMClassifier(boosting_type='gbdt',
#                          device='gpu',
                        objective='binary',          # learning task
                        metric='auc',
                        num_leaves=31,               # Maximum tree leaves for base learners.
                        max_depth=-1,                # Maximum tree depth for base learners, -1 means no limit.
                        learning_rate=0.1,          # Adaptive lr via callback override in .fit() method  
                        n_estimators=100,            # Number of boosted trees to fit
                        subsample_for_bin=200000,    # Number of samples for constructing bins.
                        class_weight=None,           # dict, 'balanced' or None
                        min_split_gain=0.0,          # Minimum loss reduction for further split
                        min_child_weight=0.001,      # Minimum sum of instance weight(hessian)
                        min_child_samples=20,        # Minimum number of data need in a child(leaf)
                        subsample=1.0,               # Subsample ratio of training samples
                        subsample_freq=0,            # Frequency of subsampling, <=0: disabled
                        colsample_bytree=1.0,        # Subsampling ratio of features
                        reg_alpha=0.0,               # L1 regularization term on weights
                        reg_lambda=0.0,              # L2 regularization term on weights
                        random_state=42,             # Random number seed; default: C++ seed
                        n_jobs=-1,                   # Number of parallel threads.
                        silent=False,
                        importance_type='gain',      # default: 'split' or 'gain'
                       )

交差検証

カテゴリカル特徴量を用いる

algo = 'lgb_factors'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   lgb_factor_cv_result, run_time[algo] = run_cv(lgb_clf, X=X_factors, fit_params={'categorical_feature': cat_cols})
   joblib.dump(lgb_factor_cv_result, fname)
else:
   lgb_factor_cv_result = joblib.load(fname)

プロットの結果

lgb_factor_result = stack_results(lgb_factor_cv_result)
lgb_factor_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像15

plot_result(lgb_factor_result, model='Light GBM | Factors', fname=f'figures/{algo}_cv_result')

画像16

ダミー変数を用いる

algo = 'lgb_dummies'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   lgb_dummy_cv_result, run_time[algo] = run_cv(lgb_clf)
   joblib.dump(lgb_dummy_cv_result, fname)
else:
   lgb_dummy_cv_result = joblib.load(fname)
lgb_dummy_result = stack_results(lgb_dummy_cv_result)
lgb_dummy_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()

画像17

plot_result(lgb_dummy_result, model='Light GBM | Factors', fname=f'figures/{algo}_cv_result')

画像18

CatBoostClassifier

詳しくはこちら

設定

cat_clf = CatBoostClassifier()

交差検証

s = pd.Series(X_factors.columns.tolist())
cat_cols_idx = s[s.isin(cat_cols)].index.tolist()
algo = 'catboost'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   cat_cv_result, run_time[algo] = run_cv(cat_clf,
                                          X=X_factors,
                                          fit_params={
                                              'cat_features': cat_cols_idx},
                                          n_jobs=1)
   joblib.dump(cat_cv_result, fname)
else:
   cat_cv_result = joblib.load(fname)

結果のプロット

cat_result = stack_results(cat_cv_result)
cat_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()
plot_result(cat_result, model='CatBoost', fname=f'figures/{algo}_cv_result')

画像19

GPU

dockerの環境にGPU入れていなかったので、私はここをスキップしました。

設定

cat_clf_gpu = CatBoostClassifier(task_type='GPU')

交差検証

s = pd.Series(X_factors.columns.tolist())
cat_cols_idx = s[s.isin(cat_cols)].index.tolist()
algo = 'catboost_gpu'
fname = results_path / f'{algo}.joblib'
if not Path(fname).exists():
   cat_gpu_cv_result, run_time[algo] = run_cv(cat_clf_gpu,
                                              X=X_factors,
                                              fit_params={
                                                  'cat_features': cat_cols_idx},
                                              n_jobs=1)
   joblib.dump(cat_gpu_cv_result, fname)
else:
   cat_gpu_cv_result = joblib.load(fname)

結果のプロット

cat_gpu_result = stack_results(cat_gpu_cv_result)
cat_gpu_result.groupby(['Metric', 'Dataset']).Value.mean().unstack()
plot_result(cat_gpu_result, model='CatBoost', fname=f'figures/{algo}_cv_result')

結果の比較

results = {'Baseline': dummy_result,
          'Random Forest': rf_result,
          'AdaBoost': ada_result,
          'Gradient Booster': gb_result,
          'XGBoost': xbg_result,
          'LightGBM Dummies': lgb_dummy_result,
          'LightGBM Factors': lgb_factor_result,
          'CatBoost': cat_result}
df = pd.DataFrame()
for model, result in results.items():
   df = pd.concat([df, result.groupby(['Metric', 'Dataset']
                                      ).Value.mean().unstack()['Test'].to_frame(model)], axis=1)

df.T.sort_values('AUC', ascending=False)

画像20

algo_dict = dict(zip(['dummy_clf', 'random_forest', 'adaboost', 'sklearn_gbm', 
                     'xgboost', 'lgb_factors', 'lgb_dummies', 'catboost'],
                    ['Baseline', 'Random Forest', 'AdaBoost', 'Gradient Booster', 
                     'XGBoost', 'LightGBM Dummies', 'LightGBM Factors', 'CatBoost']))
r = pd.Series(run_time).to_frame('t')
r.index = r.index.to_series().map(algo_dict)
r.to_csv(results_path / 'runtime.csv')
auc = pd.concat([v.loc[(v.Dataset=='Test') & (v.Metric=='AUC'), 'Value'].to_frame('AUC').assign(Model=k) 
                for k, v in results.items()])
auc = auc[auc.Model != 'Baseline']
fig, axes = plt.subplots(figsize=(15, 5), ncols=2)
idx = df.T.drop('Baseline')['AUC'].sort_values(ascending=False).index
sns.barplot(x='Model', y='AUC', 
           data=auc, 
           order=idx, ax=axes[0])
axes[0].set_xticklabels([c.replace(' ', '\n') for c in idx])
axes[0].set_ylim(.49, .58)
axes[0].set_title('Predictive Accuracy')

(r.sort_values('t')).plot.barh(title='Runtime', ax=axes[1], logx=True, legend=False)
axes[1].set_xlabel('Seconds (log scale)')
sns.despine()
fig.tight_layout();

画像21


































































この記事が気に入ったらサポートをしてみませんか?