# ML2021Spring-hw1 詳細解題紀錄
## Objective
用三天的調查資料,預測第三天確診人數比率([PowerPoint](https://speech.ee.ntu.edu.tw/~hylee/ml/ml2021-course-data/hw/HW01/HW01.pdf)、[Video](https://www.youtube.com/watch?v=Q4yskCCixhw))
## Overview
Repository: [Github](https://github.com/yzu1103309/ml2021spring-hw1)


Private: 0.89116 / Public: 0.86267
Private: 0.89155 / Public: 0.86145
Passed private and public strong baseline
最後的 Hyper Parameters 的設定:
* Optimizer: [Adam](https://bit.ly/3Sttsjj)
* Regularization: [L1](https://bit.ly/3HxgbzY)
* batch_size: 270 或 540(不更改或是調大)
* learning rate: 0.001(未更改)
* epoch: 30000
* early stop: 1000 或 300
* 使用Features: 見 [#最後選定的 Features](#%E6%9C%80%E5%BE%8C%E9%81%B8%E5%AE%9A%E7%9A%84-Features%EF%BC%9A)
* train/dev: 7/1
* seed: 12345(或 3309)
* Network 結構:見 [#最後的 Network 結構](#%E6%9C%80%E5%BE%8C%E7%9A%84-Network-%E7%B5%90%E6%A7%8B%EF%BC%9A)
## 過程
### Stage 1:理解 [Sample Code](https://colab.research.google.com/github/ga642381/ML2021-Spring/blob/main/HW01/HW01.ipynb)
得知以下幾個預設數值:
* Optimizer: [SGD](https://bit.ly/3Sttsjj)
* batch_size: 270
* learning rate: 0.001
* epoch: 3000
* early stop: 200
* 使用全部 Features
* train/dev: 9/1
預設 Network 結構:
```python=
self.net = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
```
### Stage 2: 挑選有用的 Features
Features 排列、類別與意義如下表所示:


先用預設的 Network,依照指示留下 States 和 Tested Positive
再一個一個區段加入,觀察對結果的影響
發現 Covid-like illness 可以讓訓練 Loss 變小
其他區段加入對結果影響不大或是使結果變差
#### 最後選定的 Features:
```python=
feats = (list(range(40)) # states
+ list(range(40, 44)) # Covid-like illness
+ list(range(58, 62)) # Covid-like illness
+ list(range(76, 80)) # Covid-like illness
+ [57, 75]) # Tested Positive (day 1, day 2)
```
> 別人的做法:使用 [sklearn feature selection](https://scikit-learn.org/stable/modules/feature_selection.html) 工具來計算相關程度([參考資料](https://www.kaggle.com/code/lemontreeyc/hw1-public-strong-baseline?scriptVersionId=71762016&cellId=11))
### Stage 3: Regularization
訓練經過很少的 epoch 就會停止
訓練時 loss 降低的很快,而且可以降到很低(平均大約 0.70 ~ 0.80)
但是 Kaggle 測試結果卻不理想(過 Medium baseline,但無法再降低)
推測為過度依賴訓練資料,發生 Overfitting 的情況
於是嘗試加入 Regularization (使用 L1:[參考資料](https://blog.csdn.net/guyuealian/article/details/88426648))
```python=
def cal_loss(self, pred, target):
""" Calculate loss """
# TODO: you may implement L1/L2 regularization here
reg_loss = 0
for param in model.parameters():
reg_loss += torch.sum(torch.abs(param))
# print(reg_loss)
factor = 0.01 # lambda
return self.criterion(pred, target) + factor * reg_loss
```
可以有效改善 Overfitting 的情況,但依舊沒有辦法通過 Strong baseline
### Stage 4: 修改 Network
已知容易 Overfitting,所以不使用過度複雜的 Network
從原本的一層增加成兩層,調整 Neurons 數量,找出最好的數量
#### 最後的 Network 結構:
```python=
self.net = nn.Sequential(
nn.Linear(input_dim, 16),
nn.ReLU(),
nn.Linear(16, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
```
註:後來返回來測試,更簡單的 Network 足以讓 Public 通過 Strong baseline,但 Private 不行;而更複雜的網路不一定會讓結果不好(可見 [branch: testing](https://github.com/yzu1103309/ml2021spring-hw1/commits/testing/))(可能因為有 Regularization 已經不太會 Overfitting 了)
### Stage 5: 嘗試調整 batch size 和 epoch 數
嘗試各式各樣的數值,發現太小的 batch size 會增加 Overfitting 的機會
最後決定使用預設值(270,Data 的十分之一)或是上調(540,Data的五分之一)
調高 epoch 和 early stop 主要讓模型可以盡可能訓練完
### Stage 6: 更改 Optimizer
使用 Adam,一開始效果普通
後來發現這一段 Code:
```python=
# Normalize features (you may remove this part to see what will happen)
self.data[:, 40:] = \
(self.data[:, 40:] - self.data[:, 40:].mean(dim=0, keepdim=True)) \
/ self.data[:, 40:].std(dim=0, keepdim=True)
```
這裡對 Feature 做了一個 z-score normalization
把他註解掉以後,Adam效果明顯提昇,但相對的 SGD 一定要保留此步驟 *(原因待討論)*
**此時改為 Adam 通過 Public baseline(見 [Commit](https://github.com/yzu1103309/ml2021spring-hw1/commit/17943a6621920640c070797d0a5b0812075f5cbb) 紀錄)**
### Stage 7: 嘗試調整其他 Hyper Parameters
為了通過 Private Strong Baseline,經過各種嘗試
最後參考 [討論串](https://www.kaggle.com/competitions/ml2021spring-hw1/discussion/264579#1610204) 中的意見,調整了 train 和 dev 的 data 分配比率 **(見 [Compare of commits](https://github.com/yzu1103309/ml2021spring-hw1/compare/17943a6621920640c070797d0a5b0812075f5cbb...f84b42fb4ac80ff9fe971a26ad0e38da0cf8d328))**
原本的比率分配:
```python=
if mode == 'train':
indices = [i for i in range(len(data)) if i % 10 != 0]
elif mode == 'dev':
indices = [i for i in range(len(data)) if i % 10 == 0]
```
更改為:
```python=
if mode == 'train':
indices = [i for i in range(len(data)) if i % 8 != 0]
elif mode == 'dev':
indices = [i for i in range(len(data)) if i % 8 == 0]
```
**此時通過 Private baseline(見 [Commit](https://github.com/yzu1103309/ml2021spring-hw1/commit/f84b42fb4ac80ff9fe971a26ad0e38da0cf8d328) 紀錄),Public 也得到不錯的分數**
註:最後返回來嘗試,發現把數字改為 16 也有不錯的效果 *(原因待討論)*
另外,在多次嘗試後發現,不同的 Seed 對於結果也有不小的影響
(也許因為隨機起始點的選擇不同會導致最後是否可以更新到最好的參數)
所以遇到貧瘠(loss降不下去)時,也可以試著改 seed 的數值
## 結論和心得
有時改參數真的跟通靈沒兩樣
我們沒辦法一步一步追溯電腦中間運算的詳細過程
只能靠學過的知識去推測影響結果的原因
為了通過 private baseline,嘗試各式各樣數值的排列組合
結果最後都是修改了我意想不到的地方讓結果變好
有點像在玩密室逃脫的感覺
要有耐心的嘗試各種可能性,也許就有機會找到最佳的逃脫路線