Kaggle Titanic competitionでようやくTop 5%に乗った話

# Kaggle Titanic コンペ

今更感はありますが、Kaggleのチュートリアルコンペである[Titanic: Machine Learning from Disaster](https://www.kaggle.com/c/titanic)でいろいろ試していた結果、ようやくtop 5%に入るscore 0.82296を出せました。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/269860/3c2cb64f-b446-ea4c-2e5f-40fe9758a70e.png)


Kernelは[これ](https://www.kaggle.com/hfujita/titanic-survival-prediction-using-family-status)です。


コンペの詳細は他の方々の記事を参照していただければと思います。

・[タイタニック号乗客の生存予測モデルを立ててみる](https://qiita.com/suzumi/items/8ce18bc90c942663d1e6)

・[Kaggleでタイタニックの生存者を予測をしてSubmitしてみた](https://qiita.com/teru855/items/02bd885179bd8e39ba43)

ざっくり言えば、内容としては、乗客の年齢や性別、等級などをもとに、タイタニック号の乗客の生死を予測するモデルを作るというものです。

チュートリアルということもあり、コンペのKernelを含めて様々な解法が提案されていますが、本質的でない特徴量をいかに間引くかがoverfittingを避けるうえでは重要になってきます。ランダムフォレストを使うと、明らかに生存と無関係であろうPassengerIdがAgeより重要と判定されたりしますが、だからと言ってPassengerIdをモデル作成に使うのは明らかに問題です。Ticketの文字数や欠損値だらけのCabinを無理に取り入れても、良いモデルにはならないでしょう。


## 工夫した点

結果的には、自分と同じlast nameでかつfamily size = SibSp + Parchが等しい人々の生存率の平均を新しい特徴量として入れるのが最も効果的でした。


計算の際、平均値の計算に自分自身を入れてしまうとoverfittingするので、i番目の乗客の計算では

```python
temp = pd.concat([tot.iloc[:i,:], tot.iloc[i+1:,:]])

family = temp[(temp.Name == hisname) * (temp.FamSize == hisfam)]
```

のようにしてfamilyから除いてあげる必要があります(下記参照)。


## コード

家族の生存率の計算の部分だけ抜粋するとこんな感じです。

生存率が計算できない人々 = 一人で乗船 or 他の家族のメンバーが全員test data側
については、0.5で埋めています。


```python

#file import

train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")

train["n"] = 0
test["n"] = 1

tot = pd.concat([train,test],sort = False)

#create new feature "FamDeath"

lnames = tot.Name.map(lambda x: x.split(",")[0])

tot.Name = lnames

tnum = tot.Ticket.map(lambda x: x.split(" ")[-1])

tot.Ticket = tnum

tot["FamSize"] = tot.SibSp + tot.Parch

nlist = tot.Name.value_counts().index
tot["FamDeath"] = np.nan


for i in range(len(tot)):
    
    if tot.iloc[i,:].FamSize > 0: 
        
        hisname = tot.iloc[i,:].Name
        hisfam = tot.iloc[i,:].FamSize

        temp = pd.concat([tot.iloc[:i,:], tot.iloc[i+1:,:]])

        family = temp[(temp.Name == hisname) * (temp.FamSize == hisfam)]
        
        if len(family) == 0:
            continue

        tot.FamDeath.iloc[i] = family.Survived.mean()

tot.FamDeath.fillna(0.5,inplace=True)



```


## 特徴量の重要性(RF)

![features.png](https://qiita-image-store.s3.amazonaws.com/0/269860/8ac1699c-8780-d69a-d1fc-48aaca18abd4.png)

今回は、とにかく一度すべての特徴量を数値化しランダムフォレストにかけた後、自明に生死と関係ないPassengerIdよりも重要度の低い特徴量を間引くという戦略をとりました。

その結果、特徴量として以下の6つが選択されました。

- Title: Mr.など、Nameから抽出
- Sex
- Pclass
- FamDeath: 家族の生存率(上述)
- FamilySize = SibSp + Parch + 1: Alone(==1),Small (<5人), large(>= 5人)
- Fare

意外なことにAgeが弾かれましたが、これは離散化のbin次第かもしれません。
子供の生存率が高いことなどは、SexやTitle (Masterは男の子の敬称)、そしてFamDeathによって十分に取り込まれており、Age自身はあまり重要でないという可能性もありますが。


## コメント

最終的なscoreは0.82296でしたが、FareやAgeのbin次第でもう少し伸びるかもしれません。

しかし、[こちら](https://www.kaggle.com/ldfreeman3/a-data-science-framework-to-achieve-99-accuracy/comments)でも議論されていますが、このタイタニックコンペ、trainとtestのデータの性質が結構違います。主たる原因は単なるデータ数の少なさにあるのでしょうが。

実際、ランダムフォレストの木の数を増やしたり、grid searchをもっと徹底的にやった場合、逆にsubmission scoreが低下するという現象を確認しています。

このような状況では、コンペの性質が、予言能力の高いモデルの作成を競うというよりも、"test dataに対するover fitting度"を競うというものに変わってきてしまいますので、Kaggleに慣れること or データ分析の勉強が目的であればあまりスコアにこだわる必要はないように思います。Public LBしか公開されないタイプのコンペですし。0.8を超えられれば十分ではないでしょうか。