機械学習初心者がタイタニックチュートリアルを初めてやって上位30%に入った話

初投稿です、表題にあるようにどのような流れである程度のスコアを出していったのかを書いていこうと思います。

###僕のスペック
couseraのmachine learningを受講したほか、ProgateやUdemyなどで一通り機械学習やpythonの基礎についてはある程度理解しているレベル、実際にモデル構築をした経験は無し

ちなみにモデルを作る際には
AI Academyさんのデータ分析だったり、
https://aiacademy.jp/texts/#data_practice
Pythonで始める機械学習
https://www.oreilly.co.jp/books/9784873117980/
という本を参考にしました。

###①欠損値を埋める
まずデータの欠損値を埋めていきます。
「Age」「Cabin」「Embarked」に欠損値があるので、とりあえず最初はAgeに平均を入れました。
###②ダミー変数化する
Embarkedをpd.get_dummiesでダミー化しました
###③スケール化する
今回はMinMaxScalerを使用しました、他のStandardScalerなども調べてみましたが、どれでやってもあまり変わりなさそうだったので、とりあえずこれで。
###④モデルを作る
Name,Tiket,Cabinはとりあえず省いてランダムフォレスト、SVM、勾配ブースティングとGridSearchを用いながら試してみましたが、一番性能が良かったのがSVMなので、とりあえず提出。

スコアも76パーセントほどで順位も7000位（正確なスコアは忘れた）、うーんこの。というわけでググってどうすればいいかをパクりつつ色々試してみました。



###⑤Cabinを使う
欠損データは多いですが、どこの客室にいたかによって生存確率は変わってくるらしいので、Cabin_iniという変数を作ってそれをダミー変数化しました
###⑥isAloneとFamilySizeのカラムを作る
事件時一人でいたかどうかと、家族の合計の人数は大きく生存率に影響を与えるらしいので、このカラムを追加。家族の人数は、1+SibSp+Parchで書きました。
###⑦年齢の埋め方を変える
年齢に今まで平均を入れていましたが、名前の部分がMrだったMrsだったりMasterだったりで平均年齢が大きく変わってくるので、それによって入れる年齢を変えました。それぞれの敬称のカラムを作ってその平均を出して平均ごとにAgeの欠損値に入れていきました。この作業がむちゃくちゃ初心者には難しく、５時間以上かかった気がします（笑）
###⑧特徴量エンジニアリング
ランダムフォレストを使って特徴量エンジニアリングを行いました。特徴量は合計で17個ほどあり、何個使うのが一番スコアが良いのか色々試してみました。ちなみにスコアはこんな感じ。上の数字が訓練データをクロスバリデーションで二つに分割したうちの訓練データ、下がテストデータでのスコアです。一番二つの差が少なくなっていてなおかつスコアも良かったSVCパラメータ上位14個を採用しました。

SVCパラメータ５個
0.8323353293413174
0.8071748878923767
SVCパラメータ上位７個
0.8338323353293413
0.8116591928251121
SVCパラメータ上位11個
0.8517964071856288
0.8161434977578476
SVCパラメータ上位14個
0.8473053892215568
0.8430493273542601
SVCパラメータ全部打ち込む
0.8727544910179641
0.8026905829596412


これで提出した結果、正答率0.78947で3034位に入れました。ここまでで合計5日、15時間ほどかかった気がしますw
全然喜べるようなスコアではないですが、嬉しいです笑

<img width="942" alt="スクリーンショット 2019-04-20 18.04.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351911/d4e8dea0-895a-588e-62af-c0770fb91162.png">

最後に今回使ったコードを載せておきます。
とても見にくいですが参考までに。Jupyter Notebookで書いたものを省略しながらくっつけ合わせただけなのでエラー出るかも。色の付け方わからないので汚くなりました、ごめんなさいw

本当は家族ごとグループ化して家族の生死による判断とかもやってみたかったのですが、ググっても良く分からなかったので断念。また、ランダムフォレストや勾配ブースティングの方がスコアいいとありましたが、パラメータの調節の下手さのせいかあまりいいスコアが出なかったので、また機会があれば80パーセント狙いたいです。

次はHousePriceの方のチュートリアルをやって書いてみたいと思います。

##今回用いたコード

```python

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
import csv as csv

path = "../input/"
train_df = pd.read_csv(path+"train.csv", header=0)
test_df = pd.read_csv(path+"test.csv", header=0)
train_df['honorific'] = train_df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
test_df['honorific'] = test_df['Name'].map(lambda x: x.split(', ')[1].split('. ')[0])
test_df.head()
print(test_df["Age"].mean())
train_df["Age"].groupby(train_df['honorific']).agg(["mean","count"])



##敬称ごとに年齢の欠損値埋め
train_df["honorific"].drop_duplicates().values.tolist()
def honorificAverage(df):
    for honorific in df["honorific"].drop_duplicates().values.tolist():
        Ave = train_df.loc[train_df["honorific"]  == honorific, "Age"].mean()
        df.loc[(df["honorific"]  == honorific) & (df['Age'].isnull()), "Age"] = Ave
honorificAverage(train_df)
honorificAverage(test_df)


##Cabinの最初の文字の処理
train_df["Cabin_ini"] = train_df['Cabin'].map(lambda x:str(x)[0])
train_df["Survived"].groupby(train_df["Cabin_ini"]).agg(["mean","count"])
test_df["Cabin_ini"] = test_df['Cabin'].map(lambda x:str(x)[0])
train_df["Survived"].groupby(train_df["Cabin_ini"]).agg(["mean","count"])
train_df["Cabin_ini"] = train_df["Cabin_ini"].replace('T', 'n')


##訓練データとテストデータのisAloneとFamilySizesカラムの処理
SibSps = np.array(train_df["SibSp"])
Parchs = np.array(train_df["Parch"])
FamilySizes = SibSps + Parchs + 1
train_df["FamilySize"] = pd.DataFrame(FamilySizes)
def isAlone(X):
    a = 0
    if(X==1):
        a = 1
    return a
        
train_df["IsAlone"] = list(map(isAlone,FamilySizes)) 

SibSps = np.array(test_df["SibSp"])
Parchs = np.array(test_df["Parch"])
FamilySizes = SibSps + Parchs + 1
test_df["FamilySize"] = pd.DataFrame(FamilySizes)
# X_test["FammilySize"] = map(lambda x:str()X_test["SibSp"]+["Parch"]+1
def isAlone(X):
    a = 0
    if(X==1):
        a = 1
    return a
        
test_df["IsAlone"] = list(map(isAlone,FamilySizes)) 


##ダミー変数の処理
dummies = pd.get_dummies(train_df["Embarked"], dummy_na = True, prefix="Embarked")
train_df = pd.concat([train_df,dummies],axis=1)
dummies = pd.get_dummies(train_df["Cabin_ini"], dummy_na = True, prefix="Cabin")
train_df = pd.concat([train_df,dummies],axis=1)
dummies = pd.get_dummies(test_df["Embarked"], dummy_na = True, prefix="Embarked")
test_df = pd.concat([test_df,dummies],axis=1)
dummies = pd.get_dummies(test_df["Cabin_ini"], dummy_na = True, prefix="Cabin")
test_df = pd.concat([test_df,dummies],axis=1)


train_df["Sex"] = train_df["Sex"].replace("male",0).replace("female",1)
test_df["Sex"] = test_df["Sex"].replace("male",0).replace("female",1)

##データの結合
X_train = pd.concat([train_df["Pclass"],train_df["Sex"],train_df["Age"],train_df["SibSp"],train_df["Parch"],train_df["Fare"],
                     train_df["FamilySize"],train_df["Embarked_C"],train_df["Embarked_S"],train_df["Cabin_B"],train_df["Cabin_C"]
                     ,train_df["Cabin_D"],train_df["Cabin_E"],train_df["IsAlone"]],axis=1)
X_test = pd.concat([test_df["Pclass"],test_df["Sex"],test_df["Age"],test_df["SibSp"],test_df["Parch"],test_df["Fare"],
                    test_df["FamilySize"],test_df["Embarked_C"],test_df["Embarked_S"],test_df["Cabin_B"],test_df["Cabin_C"],
                    test_df["Cabin_D"],test_df["Cabin_E"],test_df["IsAlone"]],axis=1)
y_train = train_df["Survived"]
X_test['Fare'] = X_test['Fare'].fillna(train_df['Fare'].mean()) 


##スケール化、StandardScalerとか他のスケール化は試してない
from sklearn.preprocessing import MinMaxScaler
columns = X_train.columns.values
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_train = pd.DataFrame(X_train,columns=columns)
X_test = scaler.transform(X_test)
X_test = pd.DataFrame(X_test,columns=columns)


##ここから下はパラメータ選択に使用
#best_model = grid_search.best_estimator_
# fi = best_model.feature_importances_
# fi_df = pd.DataFrame({'feature': list(X_train.columns),
#                        'feature importance': fi[:]}).sort_values('feature importance', ascending = False)
# fi_df
# from sklearn.model_selection import GridSearchCV
# from sklearn.ensemble import RandomForestClassifier
# param_grid = {"n_estimators":[30,50,100,300,500],"max_features":[5,7,9],"random_state":[0]}
# grid_search = GridSearchCV(RandomForestClassifier(), param_grid, cv=3)
# grid_search.fit(X_train,y_train)


##ここから下はパラメータ調整。適当にネットからコピペしました
parameter_grid = [{"C":[1000,3000,6000,10000,30000,100000],"gamma":[0.0003,0.001,0.003,0.01,0.03]}]
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
grid_search = GridSearchCV(SVC(), parameter_grid, cv=3)
grid_search.fit(X_train,y_train)

print(grid_search.best_score_)
print(grid_search.best_params_)

def plot_heatmap_from_grid(grid):
    # チューニング対象のパラメータを特定する。
    params = [k for k in grid.cv_results_.keys() if k.startswith('param_')]
    if len(params) != 2: raise Exception('grid has to have exact 2 parameters.') 

    # ヒートマップの行、列、値に使うキーを定義する。
    index = params[0]
    columns = params[1]
    values = 'mean_test_score'

    # gridから必要なキーのみを抽出する。
    df_dict = {k: grid.cv_results_[k] for k in grid.cv_results_.keys() & {index, columns, values}}

    # dictをDataFrameに変換してseabornでヒートマップを表示する。
    import pandas as pd
    df = pd.DataFrame(df_dict)
    data = df.pivot(index=index, columns=columns, values=values)
    import seaborn as sns
    sns.heatmap(data, annot=True, fmt='.3f')
plot_heatmap_from_grid(grid_search)


from sklearn.svm import SVC
svc = SVC(C=10000,gamma= 0.01)
from sklearn.model_selection import train_test_split


##機械学習の部分
X_train_train,X_val,y_train_train,y_val = train_test_split(X_train,y_train, random_state=0)
svc.fit(X_train_train,y_train_train)
print(svc.score(X_train_train,y_train_train))
print(svc.score(X_val,y_val))

svc.fit(X_train,y_train)
predictions = svc.predict(X_test)

df_result = pd.DataFrame()
df_result['PassengerId'] = test_df['PassengerId']
df_result['Survived'] = predictions
df_result.to_csv('result.csv',index=False)
```