将棋の初手七六歩と同じくらい使われるのが、テーブルデータの機械学習での初手勾配ブースティング木です。
最初に勾配ブースティングにかけて、どの特徴量が有効そうかも見れてしまうとさらにウレシイということで、勾配ブースティングの特徴量の重要性を表示してみます。
実行環境とデータ
Googlw Colaboratory で試しました。
お手元の Jupyter でも Python スクリプトでも問題なく実行できます。
ノートブックは以下です。
https://colab.research.google.com/drive/1N1gtzTHFRKsbm88NyuEKqBr9wNS3tU7K?usp=sharing
データは以下の「スピードデートテスト」を使います。
https://knowledge-ja.domo.com/Training/Self-Service_Training/Onboarding_Resources/Fun_Sample_Datasets
4分間の参加者全員とのデートの後、再びデートしたいと思ったか、またそのデートの評価を行うという実験のデータです。とても面白そうなデータなのですが、200項目近くある割に説明が無い項目があったりと、結構データを見るのが大変だったので今回はデータにはあまりこだわらず実行方法を見てみます。
ソースコードと実行結果
まずデータを取得します。
! wget https://knowledge-ja.domo.com/@api/deki/files/5950/Speed_Dating_Data.csv
テキスト列のエンコード用のパッケージをインストールします。
! pip install category_encoders
CSV のデータをデータフレームに読み込みます。
import pandas as pd
speed_date = pd.read_csv("Speed_Dating_Data.csv", encoding='cp932')
speed_date
データの前処理を行います。少々データは荒れています。
- マッチングの成立 match 列を目的変数として、予測します。
- 13〜97列目までを説明変数とします(それ以降の列は後日の調査に基づくデータとのこと)。
-
dec_o
列はデートしようと思ったか、という列でマッチングの答えに関連しすぎると思われたので外しました(入れると実際、答えに対する関連はダントツで高く出ます)。 - 数値にカンマが入っている列があるので除去します。
- ラベルデータを数値に変換します。
import category_encoders as encoders
label_cols = ['field', 'from', 'career', 'undergra']
# id 列などを除く加工
exist_match_df = speed_date
object_col = exist_match_df['match']
object_col = object_col.values.astype(int)
feature_cols = exist_match_df.iloc[:,13:97]
feature_cols = feature_cols.drop('dec_o', axis=1)
col_names = feature_cols.columns.values
feature_cols['zipcode'] = feature_cols['zipcode'].str.replace(',', '')
feature_cols['income'] = feature_cols['income'].str.replace(',', '')
feature_cols['tuition'] = feature_cols['tuition'].str.replace(',', '')
feature_cols['mn_sat'] = feature_cols['mn_sat'].str.replace(',', '')
ordinal_encoder = encoders.OrdinalEncoder(cols=label_cols, handle_unknown='impute')
feature_cols = ordinal_encoder.fit_transform(feature_cols)
feature_cols = feature_cols.values.astype(float)
feature_cols
加工したデータを元に学習を行います。
- xgboost で学習を実行します。
- 5分割の交差検証を行います。
- Accuracy などを表示します。
import xgboost as xgb
from sklearn.model_selection import cross_validate, cross_val_predict, KFold
kfold = KFold(n_splits=5)
score_func = ["accuracy", "precision_macro", "recall_macro", "f1_macro"]
clf = xgb.XGBClassifier(objective="binary:logistic", max_depth=10, n_estimatoers=10000, early_stopping_rounds=20)
score = cross_validate(clf, feature_cols, object_col, cv=kfold, scoring=score_func, return_estimator=True)
print('acc: ' + str(score["test_accuracy"].mean()))
print('precision: ' + str(score["test_precision_macro"].mean()))
print('recall: ' + str(score["test_recall_macro"].mean()))
print('F1: ' + str(score["test_f1_macro"].mean()))
acc: 0.8350436362341039
precision: 0.6755307380632243
recall: 0.5681596439505251
F1: 0.5779607716750095
recall などをみると、あまりよく学習できたとは言えませんが、今回の本題はこの後です。
実行結果の出力
先ほどの cross_validate() の return_estimator=True
によって estimator が得られます。
estimator は feature_importances_ として関連の高かった説明変数を持っていますので、これを出力します。
import numpy as np
estimators = score["estimator"]
sum_score = np.zeros(len(col_names))
for i in range(5):
sum_score += estimators[i].feature_importances_
df_score = pd.DataFrame(sum_score/5, index=col_names, columns=["score"])
df_score.sort_values("score", ascending=False)
このようにして学習を行った際に重要とみなした変数を得ることができます。