Pythonにおける勾配ブースティング予測モデルをラクチンに作成するラッパーを公開しました
こんばんは、ひぐです。
今回はPythonの勾配ブースティングライブラリを使いやすくしたラッパーについて紹介します。 今回紹介するラッパーを使うと以下のメリットがあります。
- PandasのDataFrameといくつかの引数を渡すだけで予測結果が返ってくる
- 本来はそれぞれ使い方が微妙に異なるライブラリを、全く同じ記法で使える
- 出力した予測値を正解データとすぐに比較できる、可視化メソッドが使える
- パラメータやValidationの分け方を簡単に指定できる
- ターゲットエンコーディングが必要な場合、列と関数を渡すことで自動でリークしないように計算してくれる
機械学習を用いたデータ分析で必ず必要になる予測モデルを作成するプロセスですが、
これらをいつも同じ使い方で使用できるのは大きなメリットだと思います!
よければ是非使ってください!
使い方
用意するもの
- train/testデータ(DataFrame)
- ハイパーパラメータ(dict)
まず使用するハイパーパラメータを定義します。
from script import RegressionPredictor cat_params = { 'loss_function': 'RMSE', 'num_boost_round': 5000, 'early_stopping_rounds': 100, } xgb_params = { 'num_boost_round':5000, 'early_stopping_rounds':100, 'objective': 'reg:linear', 'eval_metric': 'rmse', } lgbm_params = { 'num_iterations': 5000, 'learning_rate': 0.05, 'objective': 'regression', 'metric': 'rmse', 'early_stopping_rounds': 100}
そしてインスタンスの呼び出し、学習します。
catPredictor = RegressionPredictor(train_df, train_y, test_df, params=cat_params,n_splits=10, clf_type='cat') catoof, catpreds, catFIs=catPredictor.fit() xgbPredictor = RegressionPredictor(train_df, train_y, test_df, params=xgb_params,n_splits=10, clf_type='xgb') xgboof, xgbpreds, xgbFIs = xgbPredictor.fit() lgbPredictor = RegressionPredictor(train_df, train_y, test_df, params=lgbm_params,n_splits=10, clf_type='lgb') lgboof, lgbpreds, lgbFIs = lgbPredictor.fit() rfPredictor = RegressionPredictor(train_df, train_y, test_df, sk_model=RandomForestRegressor(rf_params), n_splits=10, clf_type='sklearn') rfoof, rfpreds, rfFIs = rfPredictor.fit()
これだけです!
fitの返り値はそれぞれ、trainの予測データ、testの予測データ、Feature Importanceのnumpy arrayです 。
Kaggleなどのデータ分析の場合、これらをcsvにするだけですぐに提出できるようになります。
予測値についてデータ可視化したい場合は以下のようにします。
lgbPredictor.plot_FI(50)
lgbPredictor.plot_pred_dist()
ソースコード
class RegressionPredictor(object): ''' 回帰をKfoldで学習するクラス。 TODO:分類、多クラス対応/Folderを外部から渡す/predictのプロット/できれば学習曲線のプロット ''' def __init__(self, train_X, train_y, split_y, test_X, params=None, Folder=None, sk_model=None, n_splits=5, clf_type='xgb'): self.kf = Folder(n_splits=n_splits) self.columns = train_X.columns.values self.train_X = train_X self.train_y = train_y self.test_X = test_X self.params = params self.oof = np.zeros((len(self.train_X),)) self.preds = np.zeros((len(self.test_X),)) if clf_type == 'xgb': self.FIs = {} else: self.FIs = np.zeros(self.train_X.shape[1], dtype=np.float) self.sk_model = sk_model self.clf_type = clf_type @staticmethod def merge_dict_add_values(d1, d2): return dict(Counter(d1) + Counter(d2)) def rmse(self): return int(np.sqrt(mean_squared_error(self.oof, self.train_y))) def get_model(self): return self.model def _get_xgb_callbacks(self): '''nround,early_stopをparam_dictから得るためのメソッド''' nround=1000 early_stop_rounds=10 if self.params['num_boost_round']: nround=self.params['num_boost_round'] del self.params['num_boost_round'] if self.params['early_stopping_rounds']: early_stop_rounds=self.params['early_stopping_rounds'] del self.params['early_stopping_rounds'] return nround ,early_stop_rounds def _get_cv_model(self, tr_X, val_X, tr_y, val_y, val_idx): if self.clf_type == 'cat': clf_train =Pool(tr_X,tr_y) clf_val =Pool(val_X,val_y) clf_test =Pool(self.test_X) self.model=CatBoost(params=self.params) self.model.fit(clf_train,eval_set=[clf_val]) self.oof[val_idx]=self.model.predict(clf_val) self.preds += self.model.predict(clf_test) / self.kf.n_splits self.FIs += self.model.get_feature_importance() elif self.clf_type == 'lgb': clf_train = lgb.Dataset(tr_X, tr_y) clf_val = lgb.Dataset(val_X, val_y, reference=lgb.train) self.model = lgb.train(self.params, clf_train, valid_sets=clf_val) self.oof[val_idx] = self.model.predict(val_X, num_iteration=self.model.best_iteration) self.preds += self.model.predict(self.test_X, num_iteration=self.model.best_iteration) / self.kf.n_splits self.FIs += self.model.feature_importance() elif self.clf_type == 'xgb': clf_train = xgb.DMatrix(tr_X, label=tr_y, feature_names=self.columns) clf_val = xgb.DMatrix(val_X, label=val_y, feature_names=self.columns) clf_test = xgb.DMatrix(self.test_X, feature_names=self.columns) evals = [(clf_train, 'train'), (clf_val, 'eval')] evals_result = {} nround,early_stop_rounds= self._get_xgb_callbacks() self.model = xgb.train(self.params, clf_train, num_boost_round=nround, early_stopping_rounds=early_stop_rounds, evals=evals, evals_result=evals_result) self.oof[val_idx] = self.model.predict(clf_val) self.preds += self.model.predict(clf_test) / self.kf.n_splits self.FIs = self.merge_dict_add_values(self.FIs, self.model.get_fscore()) elif self.clf_type == 'sklearn': self.model = self.sk_model self.model.fit(tr_X, tr_y) self.oof[val_idx] = self.model.predict(val_X) self.preds += self.model.predict(self.test_X) / self.kf.n_splits self.FIs += self.model.feature_importances_ else: raise ValueError('clf_type is wrong.') def fit(self): for train_idx, val_idx in self.kf.split(self.train_X, self.train_y): X_train = self.train_X.iloc[train_idx, :] X_val = self.train_X.iloc[val_idx, :] y_train = self.train_y[train_idx] y_val = self.train_y[val_idx] self._get_cv_model(X_train, X_val, y_train, y_val, val_idx) print('this self.model`s rmse:',self.rmse()) return self.oof, self.preds, self.FIs def plot_FI(self, max_row=50): plt.figure(figsize=(10, 20)) if self.clf_type == 'xgb': df = pd.DataFrame.from_dict(self.FIs, orient='index').reset_index() df.columns = ['col', 'FI'] else: df = pd.DataFrame({'FI': self.FIs, 'col': self.columns}) df = df.sort_values('FI', ascending=False).reset_index(drop=True).iloc[:max_row, :] sns.barplot(x='FI', y='col', data=df) plt.show() def plot_pred_dist(self): fig, axs = plt.subplots(1, 2, figsize=(18, 5)) sns.distplot(self.oof, ax=axs[1], label='oof') sns.distplot(self.train_y, ax=axs[0], label='train_y') sns.distplot(self.preds, ax=axs[0], label='test_preds') plt.show()
以上です!
未実装な部分はいっぱいあるので逐次修正していきたいと思います!
ゆくゆくは親クラスを作って、分類回帰でクラスを分けて継承していくみたいにしたいと思います。
こういうふうに実装した方がいいよなど知見があればコメント頂けたら幸いです。
最後まで読んでいただきありがとうございました~