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