EnsekiTT Blog

EnsekiTTが書くブログです。

IrisのデータをXGBoostで回帰分析してみる話

こんにちは、えんせきです。
冬場であまり運動が出来ていなくて3キロほど太りました。まずいですね。
すみません、嘘をつきました。寒くてラーメンばっかり食べてたからです。運動量は大して変わっていません。

つまりなにしたの?

前回XGBoostを使ってクラス分類ができることを確認した。今度は、アヤメのがく弁の長さをそれ以外の要素から予測する回帰問題として扱ってみる。
一応RMSEとして評価して寄与率の可視化も行った。
f:id:ensekitt:20180218231914j:plain

前回まで

アヤメの花弁とがく片の幅と長さからアヤメの種類を予測するクラス分類機を作った。
ensekitt.hatenablog.com

ensekitt.hatenablog.com

方針

  • パッケージの読み込み
  • データセットの読み込み

クラス分類の時と変わってアヤメの種類をOne Hot表現にエンコードした。

  • データセットを学習と評価用に分ける
  • ハイパーパラメータ探索しつつ学習する
  • 評価する(RMSE)
  • 予測と実測の可視化
  • 変数の重要度を可視化

パッケージの読み込み

import xgboost as xgb
from sklearn import datasets
from sklearn import model_selection
from sklearn.metrics import confusion_matrix, mean_squared_error
import sklearn.preprocessing as sp
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

ここは前回とほとんど同じ。グラフを可視化を行わないところとMSE、PreProcessing系が変わった。

データセットの読み込み

iris = datasets.load_iris()
iris_df = pd.DataFrame(iris.data)
iris_df = iris_df.rename(columns={
    0: 'sepal_length',
    1: 'sepal_width',
    2: 'petal_length',
    3: 'petal_width'})
iris_df['target'] = iris.target

# 数字のカテゴリをカテゴリ名に埋めなおしている
for i, name in enumerate(iris.target_names):
    iris_df['target'] = iris_df['target'].where(iris_df['target'] != i, name)

# ラベルエンコーダを使ってまた数字に戻している
le = sp.LabelEncoder()
le.fit(iris_df.target.unique())
iris_df.target = le.fit_transform(iris_df.target)
    
# OneHotEncoderでtargetを3つのクラスのOneHot表現に変更している
ohe = sp.OneHotEncoder()
enced = ohe.fit_transform(iris_df.target.values.reshape(1, -1).transpose())
temp = pd.DataFrame(index=iris_df.target.index, columns="target-" + le.classes_, data=enced.toarray())
iris_df = pd.concat([iris_df, temp], axis=1)
del iris_df['target']

iris_df.head()

f:id:ensekitt:20180218232419p:plain
データセットを読み込んでOne hot表現になおす。
One Hot表現にするところはこないだやった手法を使うために、一旦数字でラベリングされているものをカテゴリ名で埋めなおしている。
ensekitt.hatenablog.com

データセットを学習と評価用に分ける

train_df, test_df = model_selection.train_test_split(iris_df, test_size=0.3)
train_df_y = train_df[['sepal_length']]
train_df_x = train_df.copy().drop('sepal_length', axis=1)
test_df_y = test_df[['sepal_length']]
test_df_x = test_df.copy().drop('sepal_length', axis=1)

前回はTargetだったけど今回はSepal_length

ハイパーパラメータ探索しつつ学習する

clf = xgb.XGBRegressor()

# ハイパーパラメータ探索
clf_cv = model_selection.GridSearchCV(clf, {'max_depth': [2,4,6], 'n_estimators': [50,100,200]}, verbose=1)
clf_cv.fit(train_df_x, [i[0] for i in train_df_y.values])
print(clf_cv.best_params_, clf_cv.best_score_)

# 改めて最適パラメータで学習
clf = xgb.XGBRegressor(**clf_cv.best_params_)
clf.fit(train_df_x, [i[0] for i in train_df_y.values])

XGBRegressorを使う。使い方は全く同じ。便利過ぎる。

評価する(RMSE)

mean_pred = [train_df_y.mean() for i in range(len(test_df_y))]
rmse_base = np.sqrt(mean_squared_error(test_df_y, mean_pred))
print("学習データの平均を予測としたやつをBaseLineとする\nBaseLineのrmse: " + str(rmse_base))

pred = clf.predict(test_df_x)
rmse = np.sqrt(mean_squared_error(test_df_y, pred))
print("予測したやつのrmse: " + str(rmse))
学習データの平均を予測としたやつをBaseLineとする
BaseLineのrmse: 0.825791175637
予測したやつのrmse: 0.357222034136

RMSEは一見良くなっているのかわからないので、ベースラインとして、学習データのSepal_lengthの平均値を予測とした場合のRMSEを先に表示した。
ちゃんと予測が機能していることがわかる(RMSEは値が小さいほど予実があっているということになる)
RMSEなどの誤差関数は前にまとめているので興味があればこっちも見てね
ensekitt.hatenablog.com

予測と実測の可視化

plt.ylabel("Predict")
plt.xlabel("Actual")
plt.scatter(test_df_y, pred)
plt.plot([4.5, 8], [4.5, 8], c='r')
plt.show()

f:id:ensekitt:20180218233107p:plain
plotの4.5と8は予測と実測が完全に一致していた場合に乗るはずの線を表示するために作った。
Plotなので、X、Yで同じ値をいれてあげれば直線になる。
なかなかあってそう。
ちなみにBaseLineとして採用した平均値を予測とした場合の結果はこちら。
f:id:ensekitt:20180218233255p:plain
当然だけど全然あってない。XGBoostが仕事をしていることがこの比較でよく分かる。

変数の重要度を可視化

xgb.plot_importance(clf)
plt.show()

f:id:ensekitt:20180218233430p:plain
変数の重要度を可視化してみたら、意外なことにアヤメの種類は大して寄与していなくて、
結構がく片の幅に寄っていた。幅が決まれば長さもだいたい決まると思うとたしかにそのとおりだとは思うけど、
こうやって可視化してみると納得感があっていいですね。

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。