【Titanic】ランダムフォレスト でパラメータチューニング

はじめに

以前,Titanicを解きましたが,訓練データの精度98%でテストデータの精度が70%ほどで過学習を起こしていました.(Kaggle入門【Titanic】)
今回は過学習をできるだけ抑えて,テストデータの精度を上げたいと思います.
精度を上げるために,パラメータチューニングを行います.しかし,訓練データを単にパラメータチューニングしても過学習は防げません.そこで,交差検証でチューニングを評価することにより過学習を抑えて精度を上げることができます.

データの見直し

パラメータ1つで表せているのか?

パラメータチューニングを行う前に,特徴量を少し見直します.まずは年齢の分布を見てみましょう.
https://cdn-ak.f.st-hatena.com/images/fotolife/m/munemakun/20180708/20180708151303.png
10代前半の生存率が高いことがわかりますよね.10前半(子ども)とそれ以外(大人)では生存率の分布が異なっているように見えます.つまり,この分布を年齢という1つのカテゴリで表現することは,難しいと思います.そこで,以前の記事では,子どもと大人にカテゴリを分けました.
このように1つのカテゴリに対して意味が異なるものが含まれる場合はパラメータを増やす必要があります.

パラメータの拡張

Titanicの可視化では,SibSp(タイタニックに同乗している兄弟/配偶者の数),Parch(タイタニックに同乗している親/子供の数)からFamilyという新しいカラムを作成しました.
ただ,家族がいるか?ということと,家族が何人いるか?ということは意味が異なるように感じます.
例えば,(独身 o r家族が1人いる)ということと,(家族が2人いる or 家族が3人いる)ことを同列に扱ってしまってはいけないような気がします.両者には大きな差があると思います.
つまり,家族の人数というカテゴリを拡張して,家族がいるかどうかというカテゴリを増やすことにします.

#IsFamily 家族がいるか

train['Family'] =  train["Parch"] + train["SibSp"]
train['IsFamily']=1
train['IsFamily'].loc[train['Family']==0]=0

test['Family'] =  test["Parch"] + test["SibSp"]
test['IsFamily']=1
test['IsFamily'].loc[test['Family']==0]=0

# Parch と SibSp を削除
train = train.drop(['SibSp','Parch'], axis=1)
test    = test.drop(['SibSp','Parch'], axis=1)

この時点で精度は75%程になりました.

また,チケットクラスも1~3で表すのではなく,ダミー変数を用いて3つに分割(Class1,Class2,Class3)します.

#Pclass

pclass_dummies_titanic  = pd.get_dummies(train['Pclass'])
pclass_dummies_titanic.columns = ['Class_1','Class_2','Class_3']

pclass_dummies_test  = pd.get_dummies(test['Pclass'])
pclass_dummies_test.columns = ['Class_1','Class_2','Class_3']

train.drop(['Pclass'],axis=1,inplace=True)
test.drop(['Pclass'],axis=1,inplace=True)

train = train.join(pclass_dummies_titanic)
test    = test.join(pclass_dummies_test)

パラメータチューニング

ここからは,グリッドサーチ でハイパーパラメータ(人が決めるパラメータ)を決めていきます.交差検証で検証する事で,過学習を抑えます.
scikit-learnのGridSearchCVは,グリッドサーチでハイパーパラメータを変更して交差検証で最も良いスコアとなるハイパーパラメータを見つけ,学習します.
ランダムフォレストでGridSearchCVを使って最適なモデルを作成します.

#グリッドサーチの範囲を指定
parameters = {
    "n_estimators":[i for i in range(10,100,10)],
    "criterion":["gini","entropy"],
    "max_depth":[i for i in range(1,6,1)],
     'min_samples_split': [2, 4, 10,12,16],
    "random_state":[3],
}
scorer = make_scorer(fbeta_score, beta=0.5)
#モデルを作成
clf = sklearn.model_selection.GridSearchCV(RandomForestClassifier(), parameters,cv=5,n_jobs=-1)
clf_fit=clf.fit(X_train, Y_train)
#最も良い学習モデルで学習
predictor=clf_fit.best_estimator_

訓練データに対する性能を見てみます

 prediction=predictor.predict(X_train)
table=sklearn.metrics.confusion_matrix(Y_train,prediction)
tn,fp,fn,tp=table[0][0],table[0][1],table[1][0],table[1][1]
print("TPR\t{0:.3f}".format(tp/(tp+fn)))
print("SPC\t{0:.3f}".format(tn/(tn+fp)))
print("PPV\t{0:.3f}".format(tp/(tp+fp)))
print("ACC\t{0:.3f}".format((tp+tn)/(tp+fp+fn+tn)))
print("MCC\t{0:.3f}".format((tp*tn-fp*fn)/((tp+fp)*(tp+fn)*(tn+fp)*(tn+fn))**(1/2)))
print("F1\t{0:.3f}".format((2*tp)/(2*tp+fp+fn)))
     
# 4. printing parameters of the predictor
print(sorted(predictor.get_params(True).items()))
     
# 5. printing importances of the predictor
print(predictor.feature_importances_)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/munemakun/20180709/20180709191418.png?1531131280
訓練データに対する精度は83%になりました.

性能評価

実際にテストデータを予測したところ78%でした.80%には届きませんでした ...
70%→78%になり,過学習の改善はされたので良かったです.
今回使わなかった'Name'や'Ticket'のデータを上手く使えば80%を超えそうなのでもう少し頑張りたいと思います.

#テストデータ予測
Y_pred=predictor.predict(X_test)
submission = pd.DataFrame({
        "PassengerId": test["PassengerId"],
        "Survived": Y_pred
    })
submission.to_csv('titanic.csv', index=False)


↓scikit-learnのGridSearchCVの使い方についてこちらを参考にさせて頂きました
Scikit-learnによるランダムフォレスト