# 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值需要調整網路的次數多,時間成本高。