---
# System prepended metadata

title: 410921216賴柏諺-深度學習作業一

---

# 410921216賴柏諺-深度學習作業一

## 零. 程式碼: 
:::spoiler COLAB程式碼(主要)
```PYTHON
import torch
import torch.nn as nn
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from tqdm.auto import tqdm 
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 讀入 train.csv 檔案
train_data = pd.read_csv('train.csv')

# 定義模型的神經網路
class Net(nn.Module):
    def __init__(self):
      super(Net, self).__init__()
      self.Lrelu = nn.LeakyReLU()
      self.relu = nn.ReLU()
      self.elu = nn.ELU()
      self.tanh = nn.Tanh()
      Neurons = [6,180,39,66,77,987,27,25,17,6,89,11,121,23,52]
      self.start = nn.Linear(2, Neurons[0])
      self.h1 = nn.Linear(Neurons[0], Neurons[1])
      self.h2 = nn.Linear(Neurons[1], Neurons[2])
      self.h3 = nn.Linear(Neurons[2], Neurons[3])
      self.h4 = nn.Linear(Neurons[3], Neurons[4])
      self.h5 = nn.Linear(Neurons[4], Neurons[5])
      self.h6 = nn.Linear(Neurons[5], Neurons[6])
      self.h7 = nn.Linear(Neurons[6], Neurons[7])
      self.h8 = nn.Linear(Neurons[7], Neurons[8])
      self.h9 = nn.Linear(Neurons[8], Neurons[9])
      self.h10 = nn.Linear(Neurons[9], Neurons[10])
      self.h11 = nn.Linear(Neurons[10], Neurons[11])
      self.h12 = nn.Linear(Neurons[11], Neurons[12])
      self.h13 = nn.Linear(Neurons[12], Neurons[13])
      self.final = nn.Linear(Neurons[13], 1)
      
    def forward(self, x):
      x = self.start(x)
      x = self.tanh(x) #
      x = self.h1(x)
      x = self.tanh(x) #
      x = self.h2(x)
      x = self.h3(x)
      x = self.tanh(x) #
      x = self.h4(x)
      x = self.h5(x)
      x = self.tanh(x) #
      x = self.h6(x)
      x = self.Lrelu(x) ##
      x = self.h7(x)
      x = self.h8(x)
      x = self.tanh(x) #
      x = self.h9(x)
      x = self.h10(x)
      x = self.tanh(x) #
      x = self.h11(x)
      x = self.h12(x)
      x = self.tanh(x) #
      x = self.elu(x)
      x = self.tanh(x) #
      x = self.h13(x)
      x = self.Lrelu(x) ##
      x = self.final(x)
      return x
      
# 建立模型
model = Net().to(DEVICE)

# 定義損失函數和優化器
criterion = nn.SmoothL1Loss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.000064)

# 繪製趨勢圖用
loss_list = []

# 訓練模型
train_time = 6001 #訓練次數
print_dis = 500 #多少次印一次
for epoch in tqdm(range(train_time)):
  inputs = torch.tensor(train_data[['x1', 'x2']].values, device=DEVICE, dtype=torch.float32)
  labels = torch.tensor(train_data[['y']].values, device=DEVICE, dtype=torch.float32)
  optimizer.zero_grad()
  outputs = model(inputs)
  loss = criterion(outputs, labels)
  loss.backward() 
  optimizer.step() 
  loss_list.append(loss.item()) #繪圖用
  if epoch % print_dis == 0:
      print('epoch {}, loss {}'.format(epoch, loss.item()))
# 讀入 test.csv 檔案 (開始預測)
test_data = pd.read_csv('test.csv')
# 預測 test.csv 的 y 值
with torch.no_grad():
    test_inputs = torch.tensor(test_data[['x1', 'x2']].values, device=DEVICE, dtype=torch.float32)
    predicted_y = model(test_inputs).detach().cpu().numpy()

# 將預測結果存入 ans.csv 檔案中
result = pd.DataFrame({'id': range(1, len(test_data)+1), 'y': predicted_y.flatten()})
result.to_csv('ans.csv', index=False)

#簡單測試
my_best = pd.read_csv('k0_01494.csv')
now_test = pd.read_csv('ans.csv')
my_best = my_best.iloc[:, -1].values
now_test = now_test.iloc[:, -1].values
print("MSE:", np.mean((my_best - now_test)**2))

# 繪製訓練趨勢圖
plt.plot(range(train_time), loss_list)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Trend')
plt.show()

```
:::
## 一. 遇到的問題: 
1. 運行不了 pytorch
2. 跑太慢 
3. kaggle 成績降不下去
4. 要不要用batch
## 二. 怎麼解決問題: 
1. **運行不了 pytorch:** 要在什麼平台上執行是一開始寫作業的大考驗，本來是想要直接裝pytorch，但是裝pytorch後還要對cuda版本，心態炸裂，所以轉戰到colab。
colab的優點就是不用裝那麼多東西，只需要學習怎麼上傳資料就可以了，所以本次實驗基本上都是在colab上執行的。但是colab的缺點就是免費GPU使用依照流量，大概用個2小時就要換帳號，屬實麻煩，於是繼續轉戰kaggle。
Kaggle是我在大約上傳70次後使用的平台，因為它免費給 30hr/week 的GPU使用，較colab穩定(不用跑到一半還要改帳號)，只是在設定檔案地址上會比較麻煩，資料都要改成: /kaggle/input/mytestdata/train.csv (其中mytestdata是自己創的datasets)

2. **跑太慢:** 一開始再跑的時候沒想這麼多，不懂的善加利用資源，只用預設的CPU，但神經個數增加，執行時間就倍增，所以顯然使用GPU去跑是一大重點。使用上就是加上to的指令，只要注意將資料和model都丟上GPU就可以。

3. **kaggle 成績降不下去:** 看講義，看參考資料，邊看邊寫。基本上程式就可以跑了，但一開始測的成績就很不理想，於是問同學，問CHATGPT，增加了不少輔助的東西，像是要用什麼loss fuction，用什麼優化器，加上趨勢圖觀察收斂的情形，還有一個最重要的"**簡單測試**"。
這個簡單測試應該是讓成績降下去的最好方法，原理就是拿自己用kaggle測過最好的成績，跟新的程式跑出來的成績做MSE，直到MSE夠低再傳，高機率下次kaggle上傳的成績會進步，雖然我有想過本來的成績就不是正確答案，這樣測出來的值有意義嗎? 實際測試後是有的，如果連離正確答案的MSE都很大了，那就更高機率離正確答案越遠，個人上傳的標準都是0.0004x ~0.0001x，只要出現0.0001x，基本上就會有自己的新紀錄出現。

4. **要不要用batch:** 成績很難再進步時，上課有聽到其他同學有考慮batch的問題，到底是要切多少%，batch多大等，我根本沒有想過要用batch。一開始因為切分資料訓練要多寫程式碼去切分資料，要查東更多西所以沒弄，但暫時也沒有更進步的方法，於是就試著加看看。 
    :::spoiler 加上batch版本程式碼
    ```python
    import torch
    import torch.nn as nn
    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    from tqdm.auto import tqdm
    from torch.utils.data import TensorDataset, random_split, DataLoader
    DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    # 讀入 train.csv 檔案
    train_data = pd.read_csv('train.csv')
    
    # 定義模型的神經網路
    class Net(nn.Module):
        def __init__(self):
          super(Net, self).__init__()
          self.Lrelu = nn.LeakyReLU()
          self.relu = nn.ReLU()
          self.elu = nn.ELU()
          self.tanh = nn.Tanh()
          Neurons = [6,18,39,66,77,27,17,15,7,6,15,16,18,23,52]
          self.start = nn.Linear(2, Neurons[0])
          self.h1 = nn.Linear(Neurons[0], Neurons[1])
          self.h2 = nn.Linear(Neurons[1], Neurons[2])
          self.h3 = nn.Linear(Neurons[2], Neurons[3])
          self.h4 = nn.Linear(Neurons[3], Neurons[4])
          self.h5 = nn.Linear(Neurons[4], Neurons[5])
          self.h6 = nn.Linear(Neurons[5], Neurons[6])
          self.h7 = nn.Linear(Neurons[6], Neurons[7])
          self.h8 = nn.Linear(Neurons[7], Neurons[8])
          self.h9 = nn.Linear(Neurons[8], Neurons[9])
          self.h10 = nn.Linear(Neurons[9], Neurons[10])
          self.h11 = nn.Linear(Neurons[10], Neurons[11])
          self.h12 = nn.Linear(Neurons[11], Neurons[12])
          self.h13 = nn.Linear(Neurons[12], Neurons[13])
          self.final = nn.Linear(Neurons[13], 1)
        def forward(self, x):
          x = self.start(x)
          x = self.tanh(x) #
          x = self.h1(x)
          x = self.tanh(x) #
          x = self.h2(x)
          x = self.h3(x)
          x = self.tanh(x) #
          x = self.h4(x)
          x = self.h5(x)
          x = self.tanh(x) #
          x = self.h6(x)
          x = self.Lrelu(x) ##
          x = self.h7(x)
          x = self.h8(x)
          x = self.tanh(x) #
          x = self.h9(x)
          x = self.h10(x)
          x = self.tanh(x) #
          x = self.h11(x)
          x = self.h12(x)
          x = self.tanh(x) #
          x = self.elu(x)
          x = self.tanh(x) #
          x = self.h13(x)
          x = self.Lrelu(x) ##
          x = self.final(x)
          return x
    
    # 繪製趨勢圖用
    train_loss_list = []
    train_test_loss_list = []
    
    #分割train
    BATCH_SIZE = 1024
    X = train_data.iloc[:, 1:-1].values
    y = train_data.iloc[:, -1].values
    X = torch.tensor(X, dtype=torch.float32).reshape(-1, 2)
    y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)
    dataset = TensorDataset(X, y)
    train_set_size = int(len(dataset) * 0.75)
    train_set, valid_set = random_split(dataset, [train_set_size, len(dataset) - train_set_size])
    train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True)
    valid_loader = DataLoader(valid_set, batch_size=BATCH_SIZE, shuffle=False)
    
    # 建立模型
    model = Net().to(DEVICE)
    
    # 定義損失函數和優化器
    criterion = nn.SmoothL1Loss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
    
    # 訓練模型
    train_time = 3000 #訓練次數
    for epoch in tqdm(range(train_time)):
      model.train() #訓練mode
      batch_train = 0
      batch_test = 0
      for inputs, labels in train_loader:
        inputs = inputs.to(DEVICE)
        labels = labels.to(DEVICE)
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward() 
        optimizer.step() 
        batch_train += loss.item()
      train_loss_list.append(batch_train / len(train_loader))
      model.eval() #測試mode
      for inputs, labels in valid_loader:
        with torch.no_grad():
          inputs = inputs.to(DEVICE)
          labels = labels.to(DEVICE)
          outputs = model(inputs)
          loss = criterion(outputs, labels)
          batch_test += loss.item()
      train_test_loss_list.append(batch_test / len(valid_loader))
    
    # 讀入 test.csv 檔案 
    test_data = pd.read_csv('test.csv')
    
    # 預測 test.csv 的 y 值
    with torch.no_grad():
        test_inputs = torch.tensor(test_data[['x1', 'x2']].values, device=DEVICE, dtype=torch.float32)
        predicted_y = model(test_inputs).detach().cpu().numpy()
        
    # 將預測結果存入 ans.csv 檔案中
    result = pd.DataFrame({'id': range(1, len(test_data)+1), 'y': predicted_y.flatten()})
    result.to_csv('ans.csv', index=False)
    
    #簡單測試
    my_best = pd.read_csv('k0_01494.csv')
    now_test = pd.read_csv('ans.csv')
    my_best = my_best.iloc[:, -1].values
    now_test = now_test.iloc[:, -1].values
    print("MSE:", np.mean((my_best - now_test)**2))
    
    # 繪製訓練趨勢圖
    plt.plot(range(train_time), train_loss_list, label="train_loss")
    plt.plot(range(train_time), train_test_loss_list, label="test_loss")
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('Training Loss Trend')
    plt.legend()
    plt.show()
    ```
    ::: 
    加上batch後本來希望可以打破瓶頸，但成績慘不忍睹。因為有切割訓練資料，所以經過無數次訓練後找到的模型參數都不可以套著使用，所以又要重新找，神經層數，神經元個數，訓練率，訓練次數等，加上切割後訓練時間從1min增加到5min，很沒效率，所以就繼續用不切割版本。
## 三. 模型設計思路(個人創意): 
**以下為前期的實驗數據:**
第幾次測試,kaggle, 學習率, 訓練次數, 網路
1, 0.34644, 0.01, 1000, 2->10->1
5, 0.20375, 0.01, 5000, 2->20+relu ->1
7, 0.19147, 0.01, 10000, 2->30+relu ->1
8, 0.17556, 0.01, 10000, 2->30+relu ->20->1
9, 0.08976, 0,01, 10000, 2->30+leakyrelu ->20->1
15, 0.08239, 0.01, 15000, 2->10+leakyrelu ->50 ->10->1
18, 0.04607, 0.01, 2001, 2->20+Lrelu ->50+Lrelu ->10+Lrelu ->5+Lrelu ->1
21, 0.02798, 0.00075, 10001, 2->300+Lrelu->500+tanh->375+Lrelu->250+Lrelu->1
25, 0.02574, 0.00075, 1501, 2->325+elu->600+Lrelu->375+tanh->150+Lrelu->1

一開始的想法是神經個數漸漸變大，在漸漸縮小，前25次都是依照這個邏輯在調整各項參數，lossfuction跟優化器在一開始測試數次後選用smoothL1loss和adam優化，看到訓練率不行，就改改神經大小，改改訓練率，看到有mse起色，就送kaggle看看，到了25次以後，就有了新的嘗試。
到了25次後的測試，我發現要在更進步的話就需要調整網路的層數和神經元的分布。先是喪心病狂的將層數加到20層(之後慢慢調整成現在的樣子)，網路的外觀就是一個個的山峰(從小到大再到小，重複數次)，這方法有成功出現0.01x的成績，但是極為不穩定，所以試著刪除幾層網路，邊刪除邊修改，觀察每次收斂的結果對應的mse值，反覆測個5~6次，找出得到mse最高時的學習率與訓練次數，然後再改神經個數。

以下是我對這次作業所使用的調整核心想法:

**改變網路的方法:** 主要是更改***神經元顆數最多***的那層，實驗看看調高或調低的結果，如果都沒有甚麼大進步的話，就調整其他的層數，照著起伏，嘗試將起伏增大/變小，看看結果，如果都沒有進步，那就去增加層數或減少層數。

**激活函數的配置:** 主要使用elu(), leakyrelu(), tanh()，relu會讓負的值變0，所以找了相對會維持負值的激活函數elu()和leakyrelu() (Lrelu)取代，tanh是在查找有甚麼激活函數可以加時意外得到不錯的結果，訓練後期主要使用tanh跟Lrelu。
配置激活函數的位置也會有所調整，方法是在網路配置完後加入或刪除激活函數，試試結果，再繼續下一次調整。

**學習率&訓練次數的調整:** 這個調整就是看mse的結果，比方說收斂loss = 0.0108,mse = 0.0004 ; loss = 0.0109,mse = 0.0005 ; loss = 0.0107,mse = 0.0003。像這三次測試可以簡單的估算出loss變低，mse會減少，所以可以增加訓練次數，或是小幅增加訓練率，如果是結果開始浮動，就代表要調整網路配置了。 

## 四. 學到了什麼: 
這次的作業令我收穫最大的是實作訓練模型，從根本不知道要怎麼寫，到慢慢建出自己的模型，那是真的很有成就感。

在實作過程中，遇到很多的問題，透過問gpt，問同學，問室友，找到很多實用的模組。import tqdm可以看到進度條，pandas讀資料讀表格，plt繪圖，torch裡切分資料的模組等...... 為我解決了很多問題或提供大大的方便性。

**訓練次數大** --> 過擬合機率高
**訓練率大** --> 容易跳出函數的相對低點，但浮動大
這兩條看似常識的話，是我經過無數次的實驗得出的結果。有時候看到收斂有變低，將訓練次數調高，收斂更低了，但再低下去反而mse暴增 ; 訓練率調高，收斂的速度會變得不穩定，有時候飆高，有時候會爆低，調低了，收斂穩穩下降，但有時候會卡在某個值(0.072)，然後mse也是爆炸。

**層數多的模型不一定好**，層數多雖然在數次測試中有機會訓練出極低的mse值(這邊的mse是與自己的最佳結果相比，夠低有較高機會刷新最高紀錄)，但同時也容易使每次訓練出的mse值相差巨大。層數少的模型結構在穩定性上表現很好，幾乎每次測出來的mse都差不多，但是mse偏高，要測出極低的mse值需要調整網路的次數多，時間成本高。
