# Kaggle Competition - Titanic Dataset
> [name=謝朋諺(Adam Hsieh)]
> [time=Thu, Jul 1, 2021 3:55 PM]
###### tags: `Tensorflow` `keras` `EDA`
   
---
## Reference
> [[資料分析&機器學習] 第4.1講 : Kaggle競賽-鐵達尼號生存預測 (前16%排名)](https://medium.com/jameslearningnote/%E8%B3%87%E6%96%99%E5%88%86%E6%9E%90-%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92-%E7%AC%AC4-1%E8%AC%9B-kaggle%E7%AB%B6%E8%B3%BD-%E9%90%B5%E9%81%94%E5%B0%BC%E8%99%9F%E7%94%9F%E5%AD%98%E9%A0%90%E6%B8%AC-%E5%89%8D16-%E6%8E%92%E5%90%8D-a8842fea7077)
> [[機器學習專案] Kaggle競賽-鐵達尼號生存預測(Top 3%)](https://yulongtsai.medium.com/https-medium-com-yulongtsai-titanic-top3-8e64741cc11f)
> [Auto Keras](https://autokeras.com/)
---
## Outline
[TOC]
---
## Final Result

但由於前面有很多人直接拿正解上傳,100% 正確率的人大概有 730 位,截至今日 (2021/07/21) 總上傳帳號有 55,548,因此若扣掉作弊的人本文大概可到達 **Top 0.56%** 的成績。
## Features
- 首先先用 info 來確認缺失值,由下表可以看出這上面在很多欄位上都有空值,包含 **Age, Fare, Cabin, Embarked**。。
- **Survived** 就是本次要預測的值,0 代表死亡 1 代表生存。
<table>
<tr>
<td align="center">Train</td>
<td align="center">Test</td>
</tr>
<tr>
<td><img src="https://i.imgur.com/Kt7Boby.png"></td>
<td><img src="https://i.imgur.com/jOfklNw.png"></td>
</tr>
</table>
- 下表看統計各個欄位的各種統計值,先大致有個概念。
<table>
<tr>
<td align="center">Train</td>
<td align="center">Test</td>
</tr>
<tr>
<td><img src="https://i.imgur.com/RH8vEmP.png">
</td>
<td><img src="https://i.imgur.com/RyxG86t.png">
</td>
</tr>
</table>
- 再來就是先把兩表合併看一下各個欄位在整體資料的分布以及分析。

## Analyze
### Pclass

- 看起來 Ticket 類別 1 的存活機率比另外兩個高,可能是頭等票次等票的存活率差別?!
### Sex

- 可以發現女生存活機率高於男生很多,看來有好好實施女生先上救生船的政策?!
### Age

- 年齡小的確生存機率有高一些,所以看來女性與小孩都有好好的先救出。
### Fare

- 票價越低生存機率也越低...某種程度上也顯示出貧富差距對於生存的可能。
### Family Size

- 資料中並沒有 Family Size,因此我們把 $SibSp+Parch+1$ 作為他的家庭人數,可看出人數適中的家庭比較有機會活下來。
### Embarked

- 可以看出在 C 港口上船的人存活率稍高,在這邊看到某些文章有說可能是那個港口的人民也相對比較富有,所以在那個港口上船的人存活率較高。
## Feature Work
### Null Value
- 首先我們得先對空值的欄位看是要捨棄還是填入值並且塞進模型做訓練,有缺失值得欄位有:Age、Fare、Cabin、Embarked,裡面有缺失值中最重要的應該是 Age 這個欄位。

- Age 補充空值的方法一開始我是用全部資料的中位數來填補,但效果不彰,後來看到有文章分享可以利用 Title 裡面稱謂的中位數作為補充,如都叫 Mr. 的 Age 中位數為 29 歲或都叫 Miss 的中位數為 22 歲,這樣會更接近實際情況一點。
- Cabin 特徵缺失過多所以我決定在訓練時不採用。
- 其餘的 Fare 跟 Embaeked 由於只差 1, 2 個數量因此並直接使用中位數來填補。
### Age Minimum
這邊我先看看 Age 缺失值在其他欄位上的表現。

從上圖可看出沒有 Age 的比例對上存活率、票等、性別之間的關聯。

- 一開始我只用 Age 的中位數作為補充但發現效果不彰,畢竟缺失值存活率有 29.38%(如上表),裡面的資料也很重要,尤其在票等 3 失去的資料最為多,因此在這邊我又只做票等 1, 2 的年紀與存活率的圖表:

可以看出在 16 歲以下存活機率特高,有此看出我們只判斷小於 16 歲或大於 16 歲的幫助會更大一點,我也上傳過兩種版本,的確分年齡區間比起只判斷小於 16 歲效果更差。
### Fare
票價也算是滿重要的一環,由於他算是數值資料但是作為區間的類別型資料效果會比較顯著。
| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
| || |
:::info
:bulb: 上表之所以有 NaN 是因為最貴的票價剛好屬於 Test Data,所以會以 NaN 呈現。
:::
- 為了要分區間,就要先定義分幾個區間是最好的,在這邊我們使用特徵選擇的技巧利用 RFECV 與 Scikit-learn 的模型 (這邊使用 RandomForestTree) 試試看分幾個區間的分數較高,RFECV 在交叉驗證循環中執行 RFE 以找到最佳特徵數量,雖然下圖以 Validation 的分數來看 Cut6 分數較高,但上傳至 Kaggle 的 test data 是 Cut5 比較高,可能就像上面參考資料的作者所說的,模型可能有點 Overfitting 才會這樣,因此我們就以五個區間來實作。

### Connected Survival
- 這個部分算是很多 Kaggle 上的大神們都推崇的方法,就是擁有同樣票號的人大部分都有一起死亡或一起存活下來的現象,因此如何做出這種關聯非常重要,我們沿用上面參考資料作者做法,若有同樣票根的人有人生存就設為 1.0,若有人死亡則設為 0.0,若是不確定的話就都設為 0.5:

這部分做完其實效果就很卓越了,我大概可以從 0.76XXX -> 0.78XXX
### Title
- 接下來這步是在 Kaggle 中的筆記本上看到的技巧,由於在 **Name** 裡面名字太多無法做處理,但稱謂卻是有規律的,因此我們主要是拿稱謂出來做實驗。
- 為了要類別化,我們把某些不常用的稱謂跟類似的稱謂合併如:
| Rare | Royal | Miss |
| --- | --- | --- |
|'Capt', 'Col', 'Dr', 'Major', 'Rev'| 'Jonkheer', 'Sir', 'Lady', 'Don', 'Countess', 'Dona' | 'Mlle', 'Mme', 'Ms', 'Miss' |
另外還有本來就很多的: 'Master', 'Mr', 'Mrs' 就繼續沿用。

- 由上圖可看出不同稱謂的存活率高低,果然叫 Mr. 的都沒怎麼活下來,另外 'Royal' 由於是合併起來的,所以波動幅度比較大。
### Family Size
- Family Size 承前面說的我把 $SibSp+Parch+1$ 作為他的家庭人數,但是我更希望他以類別型資料存在,從前面分析可看出家庭人數適中存活機率會較大,因此我透過圖表分析做了一下變化:
| Category | Single Person | Small Famiily | Median Family | Large Family |
| --------- |:-------------:|:-------------:|:-------------:|:------------:|
| **Count** | 1 | 2 ~ 4 | 5 ~ 7 | > 8 |
- 透過上表轉換可得類別式的資料,甚至可以在後面資料處理轉為 ont-hot 格式,兩者資料放在下表中。
|  |  |
| --- | --- |
|  |  |
- 很明顯地看出大家庭幾乎全軍覆沒,小家庭是唯一存活率高於五成的。
### Ticket Count
- 概念有點像 Family Size,由於有些人是情侶或朋友,所以不會被算在同個家庭中,因此做法差不多,只是間距抓的不太一樣而已。
| Category | Ticket_1 | Ticket_2_4 | Ticket_5_8 | Ticket_11 |
|:---------:|:--------:|:----------:|:----------:|:---------:|
| **Count** | 1 | 2 ~ 4 | 5 ~ 8 | 11 |
- 在這邊一樣可看出最多人同個票號的也是全部陣亡,而適中的票號人數存活率也比較高。
|  |  |
| --- | --- |
|  |  |
## Preprocessing
- Sex: 對欄位內容做了 Label Encoding,Male -> 0, Female -> 1。
- Fare: 我們取五個區間利用 Pandas 的 pd.qcut 切分我們要的區間。
- Embarked: 對欄位內容做了 Label Encoding,C -> 0, Q -> 1, S -> 2。
- Connected Survival, Ticket Count, Title, Age Minimum: 使用上述方法去做轉換。

- 上圖使用 seaborn 的 heatmap 計算每個欄位彼此的 correlation 如上圖所示。
## Training
### 1st. Version
- 由上面熱力圖可以看出在這邊我都還沒有將 Family Size 與 Ticket Count 拿出來計算,有些轉換前特徵也不會拿出來用。
- Embarked 特徵的效果也很差,尤其他的 Correlation 也只有 -0.17,因此也被棄用。
- 第一版最終挑選出來的 Feature 就只有 **Pclass**, **Sex_Label**, **Fare_5**, **Connected_Survival**, **Age_Minimum**。
- 而我們的模型有試過非常簡單的 DNN 二分類模型以及 Scikit-learn 上的幾個機器學習模型:SVM, Random Forest Tree, Extra Tree, Gradient Boosting, Logistic Regression,下圖是我們 DNN 模型。

- 另外為了表示模型的穩定性,在這邊我使用了 StratifiedKFold 做 Cross Validation,得到結果如下表:
| Model | DNN | SVC | RFC | ETC | GBC | LR |
|:-------:|:------:|:------:|:------:|:------:|:------:|:------:|
| **Acc** | 85.41% | 86.08% | 85.18% | 85.29% | 84.84% | 84.73% |
- 最後我們拿 DNN 結果上傳 Kaggle 得到的結果為 **78.95%**,SVC 的結果為 **78.47%**。
### 2nd. Version
- 由於上一版是參考別人做法做出來的結果,雖然他使用 RandomForestClassifier 效果可達 82%,但在我這邊始終低於 78%,因此我在這裡又加上 **Title**、**Ticket_Trans**、**Family_Size_Trans** 希望讓 Feature 更加完整,在這邊我直接以 Label Encoding 定義。
- Family_Size_Trans:
- Single Person -> 0
- Small Family -> 1
- Median Family -> 2
- Large Family -> 3
- Title:
- Master -> 0
- Miss -> 1
- Mr -> 2
- Mrs -> 3
- Rare -> 4
- Royal -> 5
- Ticket_Trans:
- Ticket_1 -> 0
- Ticket_2_4 -> 1
- Ticket_5_8 -> 2
- Ticket_11 -> 3
- 總共真正選用的特徵有:**Pclass**, **Sex_Label**, **Fare_5**, **Connected_Survival**, **Age_Minimum**, **Title**, **Ticket_Trans**, **Family_Size_Trans**,下方為特徵之間的 Correlation Heatmap:

- 下表是跑完 Cross-Validation 的結果,最終我們選出 **DNN** 與 **SVC** 來當作上傳結果:
| Model | DNN | SVC | RFC | ETC | GBC | LR |
|:-------:|:------:|:------:|:------:|:------:|:------:|:------:|
| **Acc** | 86.30% | 86.53% | 84.62% | 84.28% | 84.73% | 86.08% |
- DNN Model:

- **2nd Version** 我們拿 **DNN** 結果上傳 Kaggle 得到的結果為 **76.55%**,**SVC** 的結果為 **78.47%**,Test Data 的準確率在 **DNN** 反而下降了,推測這三個特徵方式幫助不大還有可能拖後腿,的確從特徵熱力圖可看出他們與 **Survived** 的相關性絕對值不高於 0.2。
### 3rd. Version
- 這一版我做了一些改變,我認為有些特徵內部做 Label Encoding 有點詭異,像是 **Pclass** 票等之間並沒有因為票等 1 跟 2 比較接近有任何關聯性,應該要分開來看,因此我將一些特徵欄位抓出來做 One-Hot Enconding,於是我展開了 **Pclass**、**Ticket_Trans**、**Family_Size_Trans**、**Title**,最後全部特徵暴增到 21 個。

- **DNN** 沿用剛剛模型只改 Input 的維度。
- 下表是跑完 Cross-Validation 的結果,最終我們選出 **DNN** 與 **LR** 來當作上傳結果:
| Model | DNN | SVC | RFC | ETC | GBC | LR |
|:-------:|:------:|:------:|:------:|:------:|:------:|:------:|
| **Acc** | 86.64% | 85.85% | 84.84% | 83.72% | 85.29% | 86.08% |
- 上傳 Kaggle 後 **DNN** 效果只有 **78.95%**, **LR** 只有 **75.60%**。
### 4th. Version
- Ont-Hot 做法效果並沒有提升僅維持到最一開始的準確率,的確當我們看熱力圖有很多 One-Hot 特徵 Correlation 都過低,因此我決定先以 Chi2 來做特徵抽取,並只取出 P-Value < 0.001 的特徵:

- 最後只剩下上面 13 個特徵,並拿去訓練得出以下 Cross-Validation 的結果,最終我們選出 **DNN** 與 **LR** 來當作上傳結果:
| Model | DNN | SVC | RFC | ETC | GBC | LR |
|:-------:|:------:|:------:|:------:|:------:|:------:|:------:|
| **Acc** | 86.64% | 85.85% | 84.51% | 84.06% | 85.29% | 85.74% |
- 上傳之後 **DNN**: **79.19%**, **SVC**: **77.75%**,DNN 效果稍微提升了一些。
---
- 由於特徵 13 個是由 P-Value 算出來的,僅能代表特徵是不是有顯著性可以分出生存或死亡,因此我決定又試了另一種方法 **PCA** 來做降維,只取出 95% 的主成分來做訓練,在這邊由於特徵已被拆解所以就不放上熱力圖,最後可以得出 8 個特徵。
- 將 PCA 的輸出拿去訓練可得出以下 Cross-Validation 的結果,最終我選出 **DNN** 與 **SVC** 來當作上傳結果:
| Model | DNN | SVC | RFC | ETC | GBC | LR |
|:-------:|:------:|:------:|:------:|:------:|:------:|:------:|
| **Acc** | 86.86% | 86.19% | 84.17% | 83.38% | 83.83% | 84.39% |
- 上傳之後 **DNN**: **80.14%**, **SVC**: **79.43%**,兩者都上升了一些,且總算準確率首度突破 8 成!
:::info
:bulb: 在這邊我也嘗試使用不同 Optimizer 來訓練 **DNN**,效果最好的大部分是 adamax 或 nadam 但差距不會太大,也試過把這幾個不同 Optimizer 做 **Ensemble** 加總,效果最好的是 **adamax+nadam+adagrad**,但也僅僅提升到 **80.38%** 並不明顯。
:::
:::success
:sunny: 到了這邊透過 Ensemble 所得到的準確率 **80.38%** 已經可以達到 **Top 2.46%** 的程度!
:::
### 5th. Version
- 到了這邊我已經黔驢技窮沒有其他辦法了XD,因此決定開始著手在模型方面的改進,我試過將模型變深或是將每個 Layer 變寬但效果並不彰,於是我再找了許多資料找到了 **AutoKeras**,它可以幫助你自動選擇最好的模型,Layer 的超參數、Optimizer、Learning rate、⋯⋯等等都可以自動幫你找到最好的組合。
- 這邊放上他的[論文連結](https://arxiv.org/pdf/1806.10282.pdf),執行的寫法很簡單:
```python=
import autokeras as ak
# 專門針對數值型的結構化資料,預設會跑 100 種不同超參數的模型
clf = ak.StructuredDataClassifier(overwrite=True)
# 開始訓練
clf.fit(train_x, train_y)
# 輸出最佳的模型
model = clf.export_model()
```
- 我大部分都使用預設的參數在 AutoKeras 上,除了 Patience 希望他只要 5 次驗證資料的 Loss 沒有下降就會停止。
- 我用了四種上面提到過的特徵組合來做自動訓練:
- Fea. 5:最一開始的基本五個特徵:Pclass, Sex_Label, Fare_5, Connected_Survival, Age_Minimum。
- Fea. 21:One-Hot 後的特徵組合。
- Fea. Chi2:使用 Chi-Squared 做特徵抽取後的結果。
- Fea. PCA:使用 PCA 後的 95% 主成分特徵。
- 下面這是跑完之後每個模型的模型架構 (自動產生),可以看出每個模型的 DNN Layer 都是以 2 的倍數存在,但為何這樣編排跟組織就是比較無法理解的:
| Fea. 5 | Fea. 21 | Fea. Chi2 |
|:------------------------------------:|:------------------------------------:|:------------------------------------:|
|  |  |  |
| **Fea. PCA** | | |
|  | | |
- 下面這是跑完之後放在 Kaggle 上的結果以及整體跑完所花的時間,由此可發現 Fea. PCA 在 AutoML 上也得出了一個最好的結果,甚至直接超越我 4% 的簡單模型,以上就是 **Top 0.56%** 的所有實驗。
| Features | Fea. 5 | Fea. 21 | Fea. Chi2 | Fea. PCA |
|:--------:|:-------:|:---------:|:---------:|:----------:|
| Time | 50m 24s | 1h 6m 19s | 17m 36s | 14m 42s |
| Acc | 83.97% | 83.97% | 84.21% | **84.45%** |
- **AutoKeras** 的體驗上來說遠比當年 NASNet 的資源吃得少且速度快上許多,本文只用了一張 1080Ti 的顯卡即可在 2 小時內訓練出一個比我自己創還準的模型,在論文中提到:
> The goal of AutoML is to enable people with limited machine learning background knowledge to use the machine learning models easily.
的確使用起來簡單又能找出一個好的結果,對於不知如何調參,或不想花費太多時間在找參數的人是非常值得一試的方法!
## 補充
前面之前用到的特徵分析或統計都可以透過某的套件直接秀出,叫做 **ProfileReport**,程式只要打上:
```python=
from pandas_profiling import ProfileReport
report = ProfileReport(train_data)
```
就可以秀出許多資訊,如以下幾張圖
**Overview**

**Variables**

**Interactions**

**Correlations**

**Missing Values**

**Samples**

算是一個非常好用的工具也推薦給大家。