【kaggle】タイタニックデータをlightGBMでOPTUNA(シンプルに)

機械学習

【PFN】OPTUNAを簡単に使ってみた 』でOPTUNA(オプチュナ)の使い方やら挙動やらを簡単にみていきましたので、それを、この界隈の方なら必ず通るであろう、タイタニックデータで使ってみようと思います。ひとまず、実データでOPTUNAを動かすことが目的なので、Feature Engineeringをせずシンプルにやっていきます。

kaggleタイタニックデータ

これがここでDLできます⇒Titanic – Machine Learning from Disaster
同じものかどうか分かりませんが、カグルの日本版的なSIGNATEにもありますね。
タイタニック号の乗船者リスト(trainとtest)用意したから、
trainデータでモデル作ってtestデータで生存者予測してみればすごくいいMLの練習になるんだよ、
っていうやつですね。

passengerId通し番号
survived(目的変数)      生存0 =いいえ、1=はい
pclassチケットクラス1 = 1st, 2 = 2nd, 3 = 3rd
name名前
sex性別               
Age年齢
sibspタイタニック号に乗っている兄弟/配偶者の数
parchタイタニック号に乗っている親/子供の数       
ticketチケット番号
fare運賃
cabin客室番号
embarked乗船港C =チェルブール、Q =クイーンズタウン、S=サウサンプトン

これらがまとまってます。

具体的に見ていく

前回同様グーグルコラボでやるので、インストールから。

!pip install optuna

インストール完了。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")
import optuna

いつものと、optunaをインポート。
DLしたデータを読み込みます。

df_train=pd.read_csv("/content/drive/MyDrive/Colab Notebooks/Machine Learning/data/kaggle/titanic/train.csv")
df_test=pd.read_csv("/content/drive/MyDrive/Colab Notebooks/Machine Learning/data/kaggle/titanic/test.csv")

trainとtest、それぞれこんなです。

df_train.head()
indexPassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.25NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Thayer)female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.925NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1C123S
4503Allen, Mr. William Henrymale35.0003734508.05NaNS
df_train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
df_test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  418 non-null    int64  
 1   Pclass       418 non-null    int64  
 2   Name         418 non-null    object 
 3   Sex          418 non-null    object 
 4   Age          332 non-null    float64
 5   SibSp        418 non-null    int64  
 6   Parch        418 non-null    int64  
 7   Ticket       418 non-null    object 
 8   Fare         417 non-null    float64
 9   Cabin        91 non-null     object 
 10  Embarked     418 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB

ぱっと見よくわからないチケット番号と欠損値の多い客室番号やらを落とします。

df_train=df_train.drop(["PassengerId","Name","Ticket","Cabin"],axis=1)
df_test=df_test.drop(["PassengerId","Name","Ticket","Cabin"],axis=1)

trainみてやると

df_train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 8 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Survived  891 non-null    int64  
 1   Pclass    891 non-null    int64  
 2   Sex       891 non-null    object 
 3   Age       714 non-null    float64
 4   SibSp     891 non-null    int64  
 5   Parch     891 non-null    int64  
 6   Fare      891 non-null    float64
 7   Embarked  889 non-null    object 
dtypes: float64(2), int64(4), object(2)
memory usage: 55.8+ KB

年齢も欠損多いので、dropnaでないやつ落とします。

df_train=df_train.dropna()
df_train.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 712 entries, 0 to 890
Data columns (total 8 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Survived  712 non-null    int64  
 1   Pclass    712 non-null    int64  
 2   Sex       712 non-null    object 
 3   Age       712 non-null    float64
 4   SibSp     712 non-null    int64  
 5   Parch     712 non-null    int64  
 6   Fare      712 non-null    float64
 7   Embarked  712 non-null    object 
dtypes: float64(2), int64(4), object(2)
memory usage: 50.1+ KB

これで学習データ形成します。

x=df_train.drop("Survived",axis=1)
t=df_train.iloc[:,0:1]

testデータ加工

続いて、testデータの方ですが、

df_test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 7 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Pclass    418 non-null    int64  
 1   Sex       418 non-null    object 
 2   Age       332 non-null    float64
 3   SibSp     418 non-null    int64  
 4   Parch     418 non-null    int64  
 5   Fare      417 non-null    float64
 6   Embarked  418 non-null    object 
dtypes: float64(2), int64(3), object(2)
memory usage: 23.0+ KB

年齢と運賃をどうにかしないとなので、

df_test["Age"]=df_test["Age"].fillna(df_test["Age"].median())
df_test["Fare"]=df_test["Fare"].fillna(df_test["Fare"].median())

各々中央値でサクッと埋めます。
あとは、カテゴリデータを数値に変換していきます。

from sklearn.preprocessing import LabelEncoder
lbl=LabelEncoder()

x["Embarked"]=lbl.fit_transform(x["Embarked"])
x["Sex"]=lbl.fit_transform(x["Sex"])
df_test["Embarked"]=lbl.fit_transform(df_test["Embarked"])
df_test["Sex"]=lbl.fit_transform(df_test["Sex"])

これにて準備完了。

lightGBMをOPTUNAで探索

from sklearn.model_selection import train_test_split
from sklearn import metrics

これらインポートして、分ける。

x_train,x_val,t_train,t_val=train_test_split(x,t,test_size=0.3,stratify=t,random_state=0)
print(x_train.shape,x_val.shape,t_train.shape,t_val.shape)
(498, 7) (214, 7) (498, 1) (214, 1)

lightGBMデータ作って、

import lightgbm as lgb
lgb_train=lgb.Dataset(x_train,t_train)
lgb_eval=lgb.Dataset(x_val,t_val)

さて、ここです。

def objective(trial):
  params={"metric":"auc",
          "objective":"binary", 
          "max_depth":trial.suggest_int("max_depth",5,1000),
          "num_leaves":trial.suggest_int("num_leaves",2,1000),
          "min_child_samples":trial.suggest_int("min_child_samples",1,300),
          "learning_rate":trial.suggest_uniform("learning_rate",0.001,2)}


  lgbm=lgb.train(params,
                 lgb_train,
                 valid_sets=lgb_eval,
                 num_boost_round=1000,
                 early_stopping_rounds=10,
                 verbose_eval=False)

  return metrics.accuracy_score(t_val["Survived"],np.round(lgbm.predict(x_val)))

めちゃくちゃ簡単で、いつものをdef objective(trial)でくくって、中身を探索範囲に書き換えるだけ。
accuracyを、記載のハイパーパラメータの範囲で最大化させます。

study=optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=1000)

1000回探索してもらいます。
長いので最後の1行の出力だけ書くと

Trial 999 finished with value: 0.8691588785046729
and parameters: {'max_depth': 5, 'num_leaves': 152, 'min_child_samples': 28, 'learning_rate': 0.8356853957155714}.
Best is trial 892 with value: 0.9065420560747663.

1000回目の探索は上記パラメータで正解率0.869、最高は893回目の0.907だったよー、と教えてくれました。ありがとう。

ベストモデルを評価

study.best_params,study.best_value
({'learning_rate': 0.6015951276617406,
  'max_depth': 822,
  'min_child_samples': 27,
  'num_leaves': 998},
 0.9065420560747663)

studyのbest_paramsに上記のように格納されているので、改めてこれらでモデル構築

bestparams={"metric":"auc",
            "objective":"binary", 
            "max_depth":study.best_params["max_depth"],
            "num_leaves":study.best_params["num_leaves"],
            "min_child_samples":study.best_params["min_child_samples"],
            "learning_rate":study.best_params["learning_rate"]}


best_lgbm=lgb.train(bestparams,
                    lgb_train,
                    valid_sets=lgb_eval,
                    num_boost_round=1000,
                    early_stopping_rounds=20,
                    verbose_eval=50)

ベストモデルでの正解率(train,val)は、

print(metrics.accuracy_score(t_train["Survived"],np.round(best_lgbm.predict(x_train))))
print(metrics.accuracy_score(t_val["Survived"],np.round(best_lgbm.predict(x_val))))
0.8755020080321285
0.9065420560747663

こんな感じ。テストデータでの予測は、

np.round(best_lgbm.predict(df_test))
array([0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 1., 0., 1., 1., 0.,
       0., 1., 0., 0., 0., 1., 1., 1., 0., 1., 0., 1., 0., 0., 0., 1., 0.,
       1., 0., 1., 0., 0., 0., 0., 1., 0., 1., 1., 0., 0., 0., 1., 1., 1.,
       0., 1., 1., 0., 0., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1., 1., 0.,
       0., 1., 1., 0., 1., 0., 1., 0., 0., 1., 0., 1., 1., 0., 0., 0., 0.,
       0., 1., 1., 1., 1., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0.,
       0., 0., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 1., 1.,
       1., 1., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 1., 0., 1., 0., 1., 0., 0.,
       0., 0., 0., 1., 1., 1., 0., 1., 1., 1., 0., 0., 0., 0., 0., 1., 0.,
       0., 0., 0., 0., 0., 1., 1., 0., 1., 1., 0., 0., 1., 0., 1., 0., 1.,
       0., 0., 0., 0., 0., 0., 0., 1., 0., 1., 1., 0., 0., 1., 1., 0., 1.,
       0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 1., 0., 1., 0., 1.,
       0., 1., 0., 1., 1., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0., 0., 0.,
       1., 1., 1., 1., 0., 0., 0., 0., 1., 0., 1., 0., 1., 0., 0., 0., 0.,
       0., 0., 0., 1., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 0., 0., 0.,
       1., 1., 0., 1., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0.,
       0., 1., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 1., 1.,
       0., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0.,
       1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 1., 0., 1., 0., 0., 0., 1., 1., 0., 0., 1., 1., 0., 1.,
       0., 0., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 1., 0., 0.,
       1., 1., 0., 0., 0., 0., 0., 0., 0., 1., 0., 1., 0., 0., 0., 0., 0.,
       1., 0., 0., 0., 1., 0., 1., 0., 0., 1., 0., 1., 1., 0., 0., 0., 0.,
       1., 1., 1., 1., 0., 0., 1., 0., 0., 0.])

こんなんなりました、さて、提出データ作ってカグルに送信!