Kaggle入門1-②

f:id:kimoppy126:20180819213535p:plain

Kaggleを制するにはKernelを制すべし。って誰かが言っていたので、適当なKernelを動かしてみることにしました。

www.kimoton.com

の続きです。

前回までのあらすじ。

データの前処理(Imputation)を行った後、
様々な側面からデータを可視化した結果、性別(Sex)、階級 (PClass)、年齢 (Age)などが生存率に影響を与え得ることを明らかにしたのでした。

今回は、

  • 特徴エンジニアリング
  • モデルの作成と予測
  • 各モデルパフォーマンスの評価
  • GridSearchCVを用いたハイパーパラメータの調整
  • 予測結果のSubmission

という一連の流れを見ていきます。
目指せKaggleマスター!

特徴エンジニアリング

特徴エンジニアリングとは、

データ内の既存の生の特徴から、関連する特徴を作成し、学習アルゴリズムの予測力を高めようとします。 データ サイエンスにおける特徴エンジニアリング - Team Data Science Process | Microsoft Docs

データの変換や追加により、新たなカラム(変数)を作成することで機械学習による予測に役立てることを指します。
ではでは、早速データを見てみましょう。

training.head()

#   PassengerId  Survived  Pclass                                               Name     Sex   Age  SibSp  Parch     Fare Embarked
#0            1         0       3                            Braund, Mr. Owen Harris    male  22.0      1      0   7.2500        S
#1            2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1      0  71.2833        C
#2            3         1       3                             Heikkinen, Miss. Laina  female  26.0      0      0   7.9250        S
#3            4         1       1       Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1      0  53.1000        S
#4            5         0       3                           Allen, Mr. William Henry    male  35.0      0      0   8.0500        S

One-Hot-Encodingへの変換

maleSCといったカテゴリー変数が存在するのがわかります。
カテゴリー変数はデータとして扱いづらいため、
まず、カテゴリー変数をOne-Hot-Encodingに変換します。

One-hot-Endcodingについては以下を参照。
参考:Why One-Hot Encode Data in Machine Learning?

training.loc[training["Sex"] == "male", "Sex"] = 0
training.loc[training["Sex"] == "female", "Sex"] = 1

training.loc[training["Embarked"] == "S", "Embarked"] = 0
training.loc[training["Embarked"] == "C", "Embarked"] = 1
training.loc[training["Embarked"] == "Q", "Embarked"] = 2

testing.loc[testing["Sex"] == "male", "Sex"] = 0
testing.loc[testing["Sex"] == "female", "Sex"] = 1

testing.loc[testing["Embarked"] == "S", "Embarked"] = 0
testing.loc[testing["Embarked"] == "C", "Embarked"] = 1
testing.loc[testing["Embarked"] == "Q", "Embarked"] = 2

One-Hot-Encodingに変換されていることを確認します。
sampleメソッドを使うと、指定した行数ランダムに取得することができます。

training.sample(5)

#     PassengerId  Survived  Pclass                           Name  Sex   Age  SibSp  Parch     Fare  Embarked
#229          230         0       3        Lefebre, Miss. Mathilde    1  28.0      3      1  25.4667         0
#488          489         0       3  Somerton, Mr. Francis William    0  30.0      0      0   8.0500         0
#202          203         0       3     Johanson, Mr. Jakob Alfred    0  34.0      0      0   6.4958         0
#425          426         0       3         Wiseman, Mr. Phillippe    0  28.0      0      0   7.2500         0
#724          725         1       1  Chambers, Mr. Norman Campbell    0  27.0      1      0  53.1000         0

新たなカラム(変数)の追加

次に、FamSizeという変数名で新たにカラムを追加します。
ここで、
FamSize (家族の数) = SibSp (兄弟の数) + Parch (両親、子供の数) + 1 (自分)
となります。
家族の数をふつーに求めているだけですね。

training["FamSize"] = training["SibSp"] + training["Parch"] + 1
testing["FamSize"] = testing["SibSp"] + testing["Parch"] + 1

さらに、IsAloneという変数名で、独身か、否かのデータを追加します。
独身か否か、というデータも、生存者率に影響を与える可能性がありますからね。

training["IsAlone"] = training.FamSize.apply(lambda x: 1 if x == 1 else 0)
testing["IsAlone"] = testing.FamSize.apply(lambda x: 1 if x == 1 else 0)

次に、Nameカラムから敬称部分を抜き出し、新たなデータとして追加します。
ここでは拡張正規表現を用いて抜き出すことにします。
([A-Za-z]+)\.で、任意の英字文に続く.までの文字列を抜き出すことができます。

for name in training["Name"]:
    training["Title"] = training["Name"].str.extract("([A-Za-z]+)\.",expand=True)
    
for name in testing["Name"]:
    testing["Title"] = testing["Name"].str.extract("([A-Za-z]+)\.",expand=True)

では、どのような敬称が使われているのか、確認しましょう。
set()関数で重複を除いて出力します。

titles = set(training["Title"]) #making it a set gets rid of all duplicates
set(titles)

#{'Capt', 'Col', 'Countess', 'Don', 'Dr', 'Jonkheer', 'Lady', 'Major', 'Master', 'Miss', 'Mlle', 'Mme', 'Mr', 'Mrs', 'Ms', 'Rev', 'Sir'}

結構多いですね。。
pandas.DataFrameモジュールでは、リストとカラム名を与えることで、データフレームが作成できます。
データフレームを作成することでより見やすく敬称のリストを見てみましょう。

titles = list(titles)

title_dataframe = pd.DataFrame({
    "Titles" : titles,
    "Frequency" : frequency_titles
})

print(title_dataframe)

#      Titles  Frequency
#0        Rev          6
#1       Miss        182
#2         Mr        517
#3   Countess          1
#4     Master         40
#5       Mlle          2
#6       Lady          1
#7         Dr          7
#8        Col          2
#9        Don          1
#10       Mrs        125
#11      Capt          1
#12     Major          2
#13       Mme          1
#14        Ms          1
#15  Jonkheer          1
#16       Sir          1

データ数が1個とか2個とかしかないものは、予測結果に悪影響を与え得るため、まとめてOthersというカラムに格納しましょう。
特定のデータを置き換える際には、keyを置き換え元、valueを置き換え先とした辞書を作成し (title_replacements)、replaceメソッドを使用します。

title_replacements = {"Mlle": "Other", "Major": "Other", "Col": "Other", "Sir": "Other", "Don": "Other", "Mme": "Other",
          "Jonkheer": "Other", "Lady": "Other", "Capt": "Other", "Countess": "Other", "Ms": "Other", "Dona": "Other"}

training.replace({"Title": title_replacements}, inplace=True)
testing.replace({"Title": title_replacements}, inplace=True)

そのままでは扱いづらいので、その他の変数と同様にOne-Hot-Encodingに変換しましょう。

training.loc[training["Title"] == "Miss", "Title"] = 0
training.loc[training["Title"] == "Mr", "Title"] = 1
training.loc[training["Title"] == "Mrs", "Title"] = 2
training.loc[training["Title"] == "Master", "Title"] = 3
training.loc[training["Title"] == "Dr", "Title"] = 4
training.loc[training["Title"] == "Rev", "Title"] = 5
training.loc[training["Title"] == "Other", "Title"] = 6

testing.loc[testing["Title"] == "Miss", "Title"] = 0
testing.loc[testing["Title"] == "Mr", "Title"] = 1
testing.loc[testing["Title"] == "Mrs", "Title"] = 2
testing.loc[testing["Title"] == "Master", "Title"] = 3
testing.loc[testing["Title"] == "Dr", "Title"] = 4
testing.loc[testing["Title"] == "Rev", "Title"] = 5
testing.loc[testing["Title"] == "Other", "Title"] = 6

データを確認してみましょう。

 training.sample(5)

#     PassengerId  Survived  Pclass                       Name  Sex   Age  SibSp  Parch     Fare  Embarked  Title  FamSize  IsAlone
#208          209         1       3  Carr, Miss. Helen "Ellen"    1  16.0      0      0   7.7500         2      0        1        1
#629          630         0       3   O'Connell, Mr. Patrick D    0  28.0      0      0   7.7333         2      1        1        1
#768          769         0       3        Moran, Mr. Daniel J    0  28.0      1      0  24.1500         2      1        2        0
#889          890         1       1      Behr, Mr. Karl Howell    0  26.0      0      0  30.0000         1      1        1        1
#615          616         1       2        Herman, Miss. Alice    1  24.0      1      2  65.0000         0      0        4        0

ここまで終わると上記のようにName以外が数値データとなります。

モデルの作成と予測

データのフォーマッティングが終わったら、
いよいよscikit-learnを使って、生存者率の予測を行っていきます。

以下では

  • SVC
  • Linear SVC
  • Random Forest
  • Logistic Regression
  • K Nearest Neighbors
  • Gaussian Naive Bayes
  • Decision Tree
  • XGBClassifier

の8つのモデルについて評価を行います。

ライブラリの読み込み

まずは、モデルの構築と予測に使用するライブラリを読み込みます。

from sklearn.svm import SVC, LinearSVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier

さらに、モデルの評価に使用するmake_scorere関数とaccuracy_score関数を読み込みます。

from sklearn.metrics import make_scorer, accuracy_score 

ハイパーパラメータを調整する際には、GridSearchを用いた交差検証(Cross-validation)を行います。
これは指定したパラメータの全ての組み合わせに対して学習を行い,もっとも良い精度を示したパラメータを採用する方法です。
Cross-validationに使用するGridSearchCV関数を読み込みます。

from sklearn.model_selection import GridSearchCV

データの定義

次に、使用するデータを定義します。
生存者率の推定には"Pclass", "Sex", "Age", "Embarked", "Fare", "FamSize", "IsAlone", "Title"の8つの変数を使用します。
ここで、y_testは定義していません。これから予測するのがy_testだからです。

features = ["Pclass", "Sex", "Age", "Embarked", "Fare", "FamSize", "IsAlone", "Title"]
X_train = training[features] #define training features set
y_train = training["Survived"] #define training label set
X_test = testing[features] #define testing features set

ハイパーパラメータを調整する際には、検証用の第3のデータセットを用意することが一般的です。
X_validy_validという変数名で、検証用データを定義します。

from sklearn.model_selection import train_test_split #to create validation data set

X_training, X_valid, y_training, y_valid = train_test_split(X_train, y_train, test_size=0.2, random_state=0) #X_valid and y_valid are the validation sets

SVC Modelの構築

scikit-learnでは、モデルの種類によらず、以下のようにモデルの構築、予測、スコアの算出まで行うことができます。
{モデル名}.fit:モデルの構築
{モデル名}.predict:モデルを使用した予測 accuracy_score:スコアの算出

svc_clf = SVC() 
svc_clf.fit(X_training, y_training)
pred_svc = svc_clf.predict(X_valid)
acc_svc = accuracy_score(y_valid, pred_svc)

print(acc_svc)

#0.7150837988826816

LinearSVC Modelを使用した推定

与えるモデル名を置き換えるだけでその実装が完了できます。
SVC ModelからLinearSVC Modelに変更する際の例を見てみましょう。

linsvc_clf = LinearSVC()
linsvc_clf.fit(X_training, y_training)
pred_linsvc = linsvc_clf.predict(X_valid)
acc_linsvc = accuracy_score(y_valid, pred_linsvc)

print(acc_linsvc)

#0.7541899441340782

ここで変わったのは最初に与えている関数のみ( + 定義する変数名)です。

以下、同様にその他のモデルについてスコアの算出まで行いましょう。

RandomForest Model

rf_clf = RandomForestClassifier()
rf_clf.fit(X_training, y_training)
pred_rf = rf_clf.predict(X_valid)
acc_rf = accuracy_score(y_valid, pred_rf)

print(acc_rf)
#0.8156424581005587

LogisiticRegression Model

logreg_clf = LogisticRegression()
logreg_clf.fit(X_training, y_training)
pred_logreg = logreg_clf.predict(X_valid)
acc_logreg = accuracy_score(y_valid, pred_logreg)

print(acc_logreg)
#0.8044692737430168

kNN

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_training, y_training)
pred_knn = knn_clf.predict(X_valid)
acc_knn = accuracy_score(y_valid, pred_knn)

print(acc_knn)

#0.7430167597765364

GaussianNB Model

gnb_clf = GaussianNB()
gnb_clf.fit(X_training, y_training)
pred_gnb = gnb_clf.predict(X_valid)
acc_gnb = accuracy_score(y_valid, pred_gnb)

print(acc_gnb)

#0.7821229050279329

DecisionTree Model

dt_clf = DecisionTreeClassifier()
dt_clf.fit(X_training, y_training)
pred_dt = dt_clf.predict(X_valid)
acc_dt = accuracy_score(y_valid, pred_dt)

print(acc_dt)

#0.776536312849162

XGBoost Model

from xgboost import XGBClassifier

xg_clf = XGBClassifier(objective="binary:logistic", n_estimators=10, seed=123)
xg_clf.fit(X_training, y_training)
pred_xg = xg_clf.predict(X_valid)
acc_xg = accuracy_score(y_valid, pred_xg)

print(acc_xg)
#0.7988826815642458

各モデルパフォーマンスの評価

モデル名と、その精度をデータフレームに格納し、精度順にソートしましょう。

model_performance = pd.DataFrame({
    "Model": ["SVC", "Linear SVC", "Random Forest", 
              "Logistic Regression", "K Nearest Neighbors", "Gaussian Naive Bayes",  
              "Decision Tree", "XGBClassifier"],
    "Accuracy": [acc_svc, acc_linsvc, acc_rf, 
              acc_logreg, acc_knn, acc_gnb, acc_dt, acc_xg]
})

model_performance.sort_values(by="Accuracy", ascending=False)

#                  Model  Accuracy
#2         Random Forest  0.815642
#3   Logistic Regression  0.804469
#7         XGBClassifier  0.798883
#5  Gaussian Naive Bayes  0.782123
#6         Decision Tree  0.776536
#1            Linear SVC  0.754190
#4   K Nearest Neighbors  0.743017
#0                   SVC  0.715084

Random Forestモデルの精度が最も良いようです。テストデータにはこのモデルを使用して推定を行いましょう。

GridSearchCVを用いたハイパーパラメータの調整

rf_clf = RandomForestClassifier()

parameters = {"n_estimators": [4, 5, 6, 7, 8, 9, 10, 15], 
              "criterion": ["gini", "entropy"],
              "max_features": ["auto", "sqrt", "log2"], 
              "max_depth": [2, 3, 5, 10], 
              "min_samples_split": [2, 3, 5, 10],
              "min_samples_leaf": [1, 5, 8, 10]
             }

grid_cv = GridSearchCV(rf_clf, parameters, scoring = make_scorer(accuracy_score))
grid_cv = grid_cv.fit(X_train, y_train)

print("Our optimized Random Forest model is:")
grid_cv.best_estimator_

#RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
#            max_depth=10, max_features='log2', max_leaf_nodes=None,
#            min_impurity_decrease=0.0, min_impurity_split=None,
#            min_samples_leaf=5, min_samples_split=10,
#            min_weight_fraction_leaf=0.0, n_estimators=8, n_jobs=1,
#            oob_score=False, random_state=None, verbose=0,
#            warm_start=False)

調整したハイパーパラメータを使ったモデルを使用して、訓練データによる学習を行います。

rf_clf = grid_cv.best_estimator_

rf_clf.fit(X_train, y_train)

#RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
#            max_depth=10, max_features='log2', max_leaf_nodes=None,
#            min_impurity_decrease=0.0, min_impurity_split=None,
#            min_samples_leaf=5, min_samples_split=10,
#            min_weight_fraction_leaf=0.0, n_estimators=7, n_jobs=1,
#            oob_score=False, random_state=None, verbose=0,
#            warm_start=False)

予測結果のSubmission

はい、お疲れさまでした。
予測が完了したら、KaggleのCompetitionにsubmitするためのデータを作成しましょう。
pandasのデータフレームは、to_csvメソッドでcsv形式での出力が可能です。

最後のsubmission.shapeでは、submitするデータが正しい次元数になっているかを確認しています。

submission_predictions = rf_clf.predict(X_test)
submission = pd.DataFrame({
        "PassengerId": testing["PassengerId"],
        "Survived": submission_predictions
    })
submission.to_csv("titanic.csv", index=False)
print(submission.shape)

#(418, 2)

おわりに

今回はTutrial的な内容のKernelをざーっと見ていきましたが、そんな難しいことはやっていない印象でした。
一連の流れとしては、

  1. Imputation

  2. 前処理

  3. モデル構築とその評価

  4. 学習

  5. 推定

これらの流れの中でデータの特徴を見るためにところどころ可視化が入ってくる、といった感じでしょうか。
次回はもう少し複雑なKernelにも挑戦してみます!