<h1> 1. 實驗步驟與觀察 </h1> <h2> code explain </h2> 首先題目給的資料有兩份,分別是 train.csv 和 test.csv,目標是的到一個model,藉由輸入x1, x2預測最終的y值。 由於input的維度是1,所以主要使用nn.Linear()來架構model,其次因為因為目標是預測y值,所以model的output只有一個值。 我選擇使用pytorch來完成作業,由於之前就已安裝過,不再多做贅述。 <h3> import </h3> ```python import os from pathlib import Path import numpy as np import pandas as pd import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset from torch.utils.data import DataLoader, random_split from tqdm.notebook import tqdm as tqdm from sklearn.preprocessing import StandardScaler from pathlib import Path from datetime import datetime from matplotlib import pyplot as plt from sklearn.model_selection import KFold ``` <h3> setting device to cuda if available </h3> ```python if torch.cuda.is_available(): device = torch.device('cuda') torch.backends.cudnn.benchmark = True else: device = torch.device('cpu') print(device) ``` <h3> setting dir </h3> ```python data_dir = str(Path(fr"./data/train.csv")) testdata_dir = str(Path(fr"./data/test.csv")) save_dir = str(Path(fr"./runs/exp")) ``` <h3> setting hyperparameter </h3> ```python # number of subprocesses to use for data loading num_workers = 0 # how many samples per batch to load batch_size = 32 epoch_num = 500 dtype = torch.float32 # Learning Rate LR = 1e-3 EPS = 1e-7 ``` <h3> define CustomDataset class </h3> 資料處理部分,首先要將csv檔讀進來,並轉成pytorch框架能接受的datatype,這裡選擇numpy,雖然目前沒有加入transform的打算,但還是有準備,這裡設計getitem時會直接將型別轉為tensor,至於dtype則是作為一個參數。 ```python class CustomDataset(Dataset): def __init__(self, data_dir, dtype=torch.float, transform=None): data = pd.read_csv(data_dir) self.inputs = data[['x1', 'x2']].to_numpy() self.outputs = data['y'].to_numpy() self.dtype = dtype self.transform = transform def __len__(self): return len(self.inputs) def __getitem__(self, idx): input_data = torch.tensor(self.inputs[idx], dtype=dtype) output_data = torch.tensor(self.outputs[idx], dtype=dtype) if self.transform: input_data = self.transform(input_data) return input_data, output_data ``` <h3> Data Preprocessing </h3> 1. 首先利用上一個 block 定義的 class 來讀取資料,建立 dataset 實例 2. 計算 dataset 大小,並切成 train set 跟 validation set 3. 藉由DataLoader class 將兩份資料依照定義的 batch size 做成 dataloader 實例,其中我有將 train set 打亂 ```python dataset = CustomDataset(data_dir) train_size = int(0.8 * len(dataset)) val_size = len(dataset) - train_size train_dataset, valid_dataset = random_split(dataset, [train_size, val_size]) dataloaders = {x: DataLoader(y, batch_size=batch_size, shuffle=z) for x, y, z in zip(['train', 'valid'], [train_dataset, valid_dataset], [True, False])} ``` <h3> define model structure </h3> activation function 使用現代model常用的 ReLU,並且加上Batch Normalization,希望以此來減緩梯度消失的問題。 ```python class DeepModelWithBN(nn.Module): def __init__( self, layer_sizes): super().__init__() self.layers = nn.ModuleList() for i in range(len(layer_sizes) - 1): layer = nn.Linear(layer_sizes[i], layer_sizes[i + 1]).to(dtype) self.layers.append(layer) if i != len(layer_sizes) - 2: bn = nn.BatchNorm1d(layer_sizes[i + 1]).to(dtype) self.layers.append(bn) self.activation = nn.ReLU() def forward(self, x): for i, layer in enumerate(self.layers[:-1]): x = layer(x) if isinstance(layer, nn.Linear): x = self.activation(x) x = self.layers[-1](x) return x ``` <h3> define model, optimizer, and criterion </h3> 定義完 model 架構後,簡單的依照2的指數,創建了一個model的實例。此時選用 adam 作為 optimizer,提高 model 訓練時參數更新時的穩定性,收斂的速度也較快。criterion 則是依照題目定義的分數檢測方式,期望能最小化 MSE Loss。 ```python model_name = 'DeepModelWithBN' model_args = [[2, 100, 1]] model = DeepModelWithBN(*model_args) optimizer = torch.optim.Adam(model.parameters(), lr=LR, eps=EPS) criterion = nn.MSELoss() ``` <h3> training </h3> 為了避免在訓練期間因為其他因素造成沒有存到檔的情形,會在每一個 epoch 時儲存,另外還會儲存目前最佳的 model。 會依照 epoch 數量是否為零判斷是否要沿用目前的儲存路徑,或是避免覆蓋上一個 model 的結果。 ```python def train(ckpt, save_dir, model, dataloaders, optimizer, num_epochs=300, dtype=torch.float32, device='cpu'): save_dir = Path(save_dir) save_dir = increment_path( Path(save_dir), exist_ok=(False if ckpt['epoch'] == 0 else True), mkdir=True) model = model.to(dtype=dtype).to(device) # initial model.load_state_dict(ckpt['model_state_dict']) optimizer.load_state_dict(ckpt['optimizer_state_dict']) criterion = ckpt['criterion'] for epoch in range(1, num_epochs + 1): # keep track of training and validation loss train_loss = 0.0 valid_loss = 0.0 print(f"running epoch: {ckpt['epoch'] + 1}") # Training loop model.train() for inputs, outputs in dataloaders['train']: optimizer.zero_grad() inputs = inputs.to(dtype).to(device) outputs = outputs.to(dtype).to(device) predictions = model(inputs) loss = criterion(predictions.squeeze(1), outputs) loss.backward() optimizer.step() # update training loss train_loss += loss.item() * inputs.size(0) # Validation loop model.eval() with torch.no_grad(): for inputs, outputs in dataloaders['valid']: inputs = inputs.to(dtype).to(device) outputs = outputs.to(dtype).to(device) predictions = model(inputs) loss = criterion(predictions.squeeze(1), outputs) valid_loss += loss.item() * inputs.size(0) train_loss = train_loss / len(dataloaders['train'].dataset) valid_loss = valid_loss / len(dataloaders['valid'].dataset) ckpt['history']['train_loss'].append(train_loss) ckpt['history']['valid_loss'].append(valid_loss) print( f'Train loss -> {train_loss:.6f} \ Validation loss -> {valid_loss:.6f}') # update and save check point information ckpt['epoch'] += 1 ckpt['model_state_dict'] = model.state_dict() ckpt['optimizer_state_dict'] = optimizer.state_dict() ckpt['date'] = datetime.now().isoformat() # save model if validation loss has decreased if valid_loss <= ckpt['history']['valid_loss_min']: print( f"Validation loss decreased ({ckpt['history']['valid_loss_min']:.6f} \ --> {valid_loss:.6f}). Saving model ...") ckpt['history']['valid_loss_min'] = valid_loss torch.save(ckpt, str(Path(save_dir) / Path('valid_best.pth'))) torch.save(ckpt, str(Path(save_dir) / Path('last.pth'))) return ckpt ``` <h3> 初始化 ckpt </h3> ```python ckpt = { 'epoch': 0, 'model_name': model_name, 'model_args': model_args, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'criterion': criterion, 'history': { 'train_loss': [], 'valid_loss': [], 'valid_loss_min': np.Inf, 'best_acc': 0.0, }, 'date': datetime.now().isoformat(), } ``` <h3> 預測 test data 的 y </h3> ```python model.to(device) model = model.to(dtype=dtype) data = pd.read_csv(testdata_dir) x = data[['x1', 'x2']].to_numpy() x = torch.tensor(x, dtype=dtype) model.eval() with torch.no_grad(): y = model(x.to(device)) y = y.squeeze().cpu().numpy() ids = np.arange(1, 2001) df = pd.DataFrame({'id': ids, 'y': y}) df.to_csv('output_last.csv', index=False) ``` <h3> 可視化結果 </h3> 利用matplotlib 來繪圖,為了更好的觀察 model 是否過擬合,以及 loss 收斂的數值,為了可以看得更清楚,這裡直接將 y 座標限制在 0.1 內。 ```python # loss plt.plot(ckpt['history']['train_loss'],label='train') plt.plot(ckpt['history']['valid_loss'],label='valid') plt.legend() plt.ylim(0,0.1) plt.title('loss') plt.xlabel('epoch') plt.ylabel('loss') ``` 為了更了解資料的特性,我將訓練集以matplotlib畫成3d可視圖。經過觀察,我將資料想成一個三維空間中的曲面。我將 x1, x2 想成三維空間的 x, z,y 則對應空間中的 y。 ```python import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Slider from mpl_toolkits.mplot3d import Axes3D data = pd.read_csv(data_dir) # 創建一個3D圖形 fig = plt.figure() fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection='3d') # 繪製散點圖 ax.scatter(data['x1'].to_numpy(), data['x2'].to_numpy(), data['y'].to_numpy(), c=data['y'].to_numpy(), cmap='viridis', marker='o') # 設置標籤 ax.set_xlabel('X1') ax.set_ylabel('X2') ax.set_zlabel('Y') # 顯示圖形 plt.show() ``` ![](https://i.imgur.com/jLN8Dwr.png) 為了可以用另一種角度來查看 model 是否過擬合,完不完美,我利用 numpy 在 x1, x2 為 -1 ~ 1 的範圍生成10000個資料點,並丟入 model 預測。 ```python import numpy as np import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Slider from mpl_toolkits.mplot3d import Axes3D # 設定網格點的數量 grid_size = 100 # 使用linspace在-1和1之間創建均勻間隔的值 x_values = np.linspace(-1, 1, grid_size) y_values = np.linspace(-1, 1, grid_size) # 使用meshgrid創建網格座標 x_grid, y_grid = np.meshgrid(x_values, y_values) # 將網格座標陣列整形為2D點陣列 x = np.column_stack((x_grid.flatten(), y_grid.flatten())) x_t = torch.tensor(x, dtype=dtype) model = model.to(device) model.eval() with torch.no_grad(): y = model(x_t.to(device)) y = y.squeeze().cpu().numpy() data = pd.DataFrame({'x1': x[:, 0], 'x2': x[:, 1], 'y': y}) # 創建一個3D圖形 fig = plt.figure() fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection='3d') # 繪製散點圖 ax.scatter(data['x1'].to_numpy(), data['x2'].to_numpy(), data['y'].to_numpy(), c=data['y'].to_numpy(), cmap='viridis', marker='o') # 設置標籤 ax.set_xlabel('X1') ax.set_ylabel('X2') ax.set_zlabel('Y') # 顯示圖形 plt.show() ``` ![](https://i.imgur.com/ybyWms0.png) ![](https://i.imgur.com/LTefgph.png) <h3> Bug </h3> 在實驗前期,我有發現 loss 的結果有些問題,後來發現兩個 bug 。 第一個是 model 的 output shape 為 (B, 1) 而 output shape 則是 (B, ),一個為二維向量,一個是一維向量,雖然還是可以算出loss值,但結果有些不同。 第二個是在計算loss的總和,並印出來時,將 len(dataloader.dataset) 寫成了 len(dataloader) 一個是回傳有多少個batch,一個是回傳有多少筆資料。 ```python k = iter(dataloaders['train']) x, y = next(k) predictions = model(x) print(predictions.shape) print(y.shape) print(y.reshape(batch_size, 1).shape) print(predictions.squeeze(1).shape) print(criterion(predictions.squeeze(1), y)) print(criterion(predictions, y)) print(criterion(predictions, y.reshape(batch_size, 1))) ``` 執行結果 torch.Size([64, 1]) torch.Size([64]) torch.Size([64, 1]) torch.Size([64]) tensor(1.2346, dtype=torch.float64, grad_fn=<MseLossBackward0>) tensor(1.2805, dtype=torch.float64, grad_fn=<MseLossBackward0>) tensor(1.2346, dtype=torch.float64, grad_fn=<MseLossBackward0>) <h2> Exp 1 </h2> 到此處成功 train 了幾個 model,可以看出 valid loss 的數值較train震盪的更嚴重。我在想是否有可能是batch過小,model 參數更新時受極端值影響較大。為了避免過度跳動,我嘗試加大batch size,我比較32, 64, 128, 256,發現當 batch size 較大時,跳動的問題有得到緩解,而且訓練時間也有效的減短了,但缺點是 loss 有點降不下去。 | **32** | **64** | | -------- | -------- | | ![](https://i.imgur.com/puCbKBG.png) | ![](https://i.imgur.com/5Nq5Nxn.png) | | **128** | **256** | | ![](https://i.imgur.com/nrGJsyT.png) | ![](https://i.imgur.com/4QfD3Tc.png) | <h2> Exp 2 </h2> 進一步對資料可視化後,發現雜訊不少,所以嘗試加深加廣網路。深度增加後,loss 值下降了不少,而且 model 的收斂更快了,loss 跳動的問題也更不明顯了。此時也發現不管再怎麼加深、加寬網路,loss 值大致上只能降至 0.025,此時我也開始將結果繳交至 kaggle 上。 | **model_args = [[2, 100, 100, 1]]** | **model_args = [[2, 100, 100, 100, 100, 1]]** | | -------- | -------- | | ![](https://i.imgur.com/sZkZ7x6.png) | ![](https://i.imgur.com/8tOAEmP.png) | | **model_args = [[2, 64, 128, 128, 64, 1]]** | **model_args = [[2, 64, 128, 256, 256, 128, 64, 1]]** | | ![](https://i.imgur.com/x9ORQzL.png) | ![](https://i.imgur.com/L3D7Wrs.png) | <h2> Exp 3 </h2> 繳交後發現結果不是很理想,再自己的資料集上,原本可以降到0.03以下的,上傳後全部都是高於0.05。再此問題中,我做的嘗試有: 1. 換一個dataloader,我怕某次資料集分割剛好訓練效果不好 2. 嘗試將 activation fuction 改為 LeakyReLU、ELU、tanh 3. 使用不同的 optimizer 像是 SGD、Adadelta、AdaGrad、RMSprop、AdamW 經過多次嘗試的結果,只有使用tanh比較成功,雖然從訓練的loss變化來看,沒什麼變化,但上傳後的結果竟然比較好,這時我的最佳結果來到0.045。其他的更動都沒有明顯差異,有些 optimizer 反而會使 model 更難收斂,所以後面主要還是使用 Adam。 <h2> Exp 4 </h2> 簡單觀察不同的 result 後,我發現每個 model 就算最終 loss 值不相上下,其中 y 值都沒有甚麼規律,所以我想到將多個 model 的 result 取平均,是否會更好。 結果是有些資料有變好,但有些資料可能會變更差,後來就放棄直接從不同的 output 找規律了,且就算有進步,也是少個 0.001。 ```python files_list = ["runs/exp61/output_best.csv", "runs/exp60/output_best.csv", ] # 初始化一個空的DataFrame用於存儲結果 merged_data = pd.DataFrame() for index, file in enumerate(files_list): # 讀取CSV檔案 data = pd.read_csv(file) # 使用檔案名稱或索引作為新列名 new_column_name = f'y_{index}' # 將y值重命名為新列名 data = data.rename(columns={'y': new_column_name}) # 合併檔案 if merged_data.empty: merged_data = data else: merged_data = merged_data.merge(data, on='id', how='outer') # 計算y值的平均 merged_data['y'] = merged_data.iloc[:, 1:].mean(axis=1) # 將結果輸出到CSV檔案 merged_data.to_csv('merged_data.csv', index=False) ``` <h2> Exp 5 </h2> 後面決定改 model 架構,當時想到的改法有幾個: 1. 是否加入 BatchNorm1d,我想說為了防止梯度消失,所以還是有留。 2. 使用不同的 weight initial 方式。 3. 加入 Dropout,但是我又看了一下我們 model 的收斂,我認為這只會讓 model 的能力下降而以,所以沒採用。 4. activation function,我只知道 tanh 效果會好一些,但我堆這些函數是否適合用在此處感到疑惑,所以我決定將一個layer的 output 套用不同的 activation function後再concate 在一起。 5. 為了能進一步的加大加深我的 model,我想使用類似ResNet、DenseNet的技術,在不同層之間加入 shortcut,我最後決定將網路設計的像 DenseNet 一樣。 _Block 會將 input 先送進一個全連階層,其 output 經過 batchnorm 之後複製 N 份進入 N 個 activation function 中,最後將這些不同的 features 再 concate 在一起。 ![](https://i.imgur.com/uAErhKm.png) DeepMultiAFwithBNmodel 則是以 _Block 為基本模塊,將每一層 layer 的 input 跟 output concate 在一起,就會達到如容下面圖示的效果。 ![](https://i.imgur.com/MNm5oF3.png) ```python class _Block(nn.Module): def __init__( self, num_input_features, num_output_features, activation_list, drop_rate: float ) -> None: super().__init__() self.add_module("linear", nn.Linear(num_input_features, num_output_features)) self.add_module("norm", nn.BatchNorm1d(num_output_features)) self.activation_list = activation_list self.drop_rate = float(drop_rate) def forward(self, input): if isinstance(input, torch.Tensor): prev_features = [input] else: prev_features = input x = torch.cat(prev_features, 1) x = self.linear(x) x = self.norm(x) features = [] for acivate in self.activation_list: features.append(acivate(x)) return torch.cat(features, 1) class DeepMultiAFwithBNmodel(nn.Module): def __init__( self, num_init_features: int, num_classes, block_config, activation_list, drop_rate: float = 0 ): super().__init__() self.features = nn.ModuleDict() num_features = num_init_features for i, num_layers in enumerate(block_config): block = _Block(num_features, num_layers, activation_list, drop_rate) self.features.add_module(f"block{i}", block) num_features = num_features + num_layers * len(activation_list) self.classifier = nn.Linear(num_features, num_classes) # 参数初始化 for m in self.modules(): if isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) # 使用Xavier初始化 nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm1d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) def forward(self, init_features): features = [init_features] for name, layer in self.features.items(): new_features = layer(features) features.append(new_features) x = torch.cat(features, 1) x = self.classifier(x) return x ``` 經過簡單的嘗試幾個參數,train 出來的結果如下,收斂的速度稍微變慢,且 loss 的浮動有點大,有點不穩定,但最終的收斂數字約莫是0.22,有進步。 參數部分可以發現上面那個的浮動較大,下面的較小,我實驗得到的結果是,activation function 不怎麼影響結果,而是神經網路太寬時就會浮動特別大。 model_args = [2, 1, [16, 64, 128, 512, 1024, 512, 128, 64, 16], [nn.Tanh(), nn.ReLU(), nn.LeakyReLU()]] ![](https://i.imgur.com/MQOt5ZH.png) model_args = [2, 1, [32, 32, 64, 64, 64, 128, 64, 64, 64, 32, 32], [nn.Tanh(), nn.ReLU(), nn.ELU()]] ![](https://i.imgur.com/2jea62B.png) <h2> Exp 6 </h2> 我嘗試著將 LR 調整為 1e-4 ,但結果是 model 收斂太慢,所以我嘗試加入 lr scheduler,這裡測試兩種方式: 1. StepLR,定義是每過step_size個epoch之後,LR = LR * LR_GAMMA。 2. ReduceLROnPlateau,不是依照epoch num,而是看 loss 有沒有持續往下降,或是 acc 有沒有往上升,會有一個 patience 值,如果連續 patience 次還是沒有打破目前最好的,就會更新一次,還可以設定 cooldown 值。 ```python LR_STEP = 100 LR_GAMMA = 0.5 lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=LR_STEP, gamma=LR_GAMMA lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=30, verbose=True, threshold=0.0001, threshold_mode='rel', cooldown=50, min_lr=0, eps=EPS) ``` 經過幾次測試後,效果都不錯,但是 ReduceLROnPlateau 對參數較不敏感,而 StepLR 要針對不同的 model 參數給出 LR_STEP 和 LR_GAMMA,所以後面選用前者。 上傳到 kaggle 後,最好結果為 0.035。 ![](https://i.imgur.com/2UWr1qp.png) ![](https://i.imgur.com/5Wx218A.png) <h2> Exp 7 </h2> 後來我有問其他同學討論了下結果,他們只有單純使用 Linear,然後深度超過十層,所以我又寫了一個 model。 ```python class DeepModel(nn.Module): def __init__( self, num_init_features: int, num_classes, block_config, # activation_list, drop_rate: float = 0 ): super().__init__() self.features = nn.Sequential() num_features = num_init_features for i, num_layers in enumerate(block_config): layer = nn.Linear(num_features, num_layers) self.features.add_module(f"linear{i}", layer) if i % 2: self.features.add_module(f"ELU{i}", nn.ELU()) else: self.features.add_module(f"Tanh{i}", nn.Tanh()) num_features = num_layers self.classifier = nn.Linear(num_features, num_classes) for m in self.modules(): if isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) nn.init.constant_(m.bias, 0) def forward(self, x): features = self.features(x) out = self.classifier(features) return out ``` 經過幾次測試,調整後,大概可以收斂至 train loss 跟 valid loss 可以收斂至 0.022,有些甚至能收斂至0.02,上傳後結果都小於0.03。 model_args = [2, 1, [8, 16, 32, 64, 128, 64, 32, 16, 8]] ![](https://i.imgur.com/kPzvhbt.png) model_args = [2, 1, [8, 16, 16, 32, 32, 64, 64, 128, 128, 64, 64, 32, 32, 16, 16, 8]] ![](https://i.imgur.com/MmoZE59.png) <h2> Exp 8 </h2> 目前為止都是只有使用到部分資料,有一部分作為 valid,我認為trainset 更大,可能會有更好的效果,但是此時我們會無法評估是否過擬和,雖然經過簡單的測試可以發現運行1000個epoch也不會使 model 過擬和,但這裡還是使用 K-Fold 來觀察,經由觀察來決定執行的 epoch 數。 經過多次嘗試,最後達到 0.01804 的成績。 ```python def plot_k_res(list): # loss for i in list: plt.plot(i['history']['train_loss'],label='train') plt.plot(i['history']['valid_loss'],label='valid') plt.legend() plt.ylim(0,0.1) plt.title('loss') plt.xlabel('epoch') plt.ylabel('loss') plt.show() ``` ```python k_folds = 5 kfold = KFold(n_splits=k_folds, shuffle=True) train_losses_k = [] valid_losses_k = [] ckpt_list = [] for fold, (train_indices, val_indices) in enumerate(kfold.split(dataset)): print(f"Fold {fold + 1}") train_subset = torch.utils.data.Subset(dataset, train_indices) val_subset = torch.utils.data.Subset(dataset, val_indices) dataloaders = {x: DataLoader(y, batch_size=batch_size, shuffle=z) for x, y, z in zip(['train', 'valid'], [train_subset, val_subset], [True, False])} model_name = 'DeepModel' model_args = [2, 1, [8, 16, 32, 64, 128, 64, 32, 16, 8]] model = DeepModel(*model_args) optimizer = torch.optim.Adam(model.parameters(), lr=LR, eps=EPS) lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=30, verbose=True, threshold=0.0001, threshold_mode='rel', cooldown=50, min_lr=0, eps=EPS) criterion = nn.MSELoss() ckpt = { 'epoch': 0, 'model_name': model_name, 'model_args': model_args, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'lr_scheduler_state_dict': lr_scheduler.state_dict(), 'criterion': criterion, 'history': { 'train_loss': [], 'valid_loss': [], 'valid_loss_min': np.Inf, 'train_loss_min': np.Inf, 'best_acc': 0.0, }, 'readme': "", 'date': datetime.now().isoformat(), } ckpt = train(ckpt, save_dir, model, dataloaders, optimizer, lr_scheduler=lr_scheduler, num_epochs=500, device=device) train_losses_k.append(ckpt['history']['train_loss_min']) valid_losses_k.append(ckpt['history']['valid_loss_min']) ckpt_list.append(ckpt) print(f"Average Train Loss: {np.mean(train_losses_k):.4f}, Average Val Loss: {np.mean(valid_losses_k):.4f}") plot_k_res(ckpt_list) ``` <h1> 2. Discussions on the results </h1> 大致上可以分為三種 model 架構,DeepModel、DeepModelWithBN、DeepMultiAFwithBNmodel。 1. DeepModel: 最晚嘗試的一個,我完全沒有料到 model 這麼單純竟然結果是最好的。內部只有Linear layer 跟 activation function,這裡設計使用 RLU、tanh 兩種穿插使用,最後得到最好的結果,至少我的實驗結果是如此,而且訓練十收斂的 loss 也跟 test result 的成績差不多。 2. DeepModelWithBN: 第一個嘗試的,相對第一個加了 batchnorm,只有用一種 activation function,雖然在 train set、 valid set 中的收斂效果不錯,但不知為何test的result上船成績不理想,差異很大。 3. DeepMultiAFwithBNmodel: 我自己大改的 model 架構,嘗試將 CNN model DenseNet 中的部分技術在全連階層中重現,整體效果不錯,但不是最好的。 經過比較,activation function 使用tanh的效果不錯。 然後 model 參數上,使用十層以上,但寬度最寬只有 128 的會比寬度到 1024 的效果更好。 最後是將多個 model 的結果去取平均,由於是預測,所以我想多個model 之間可能一個預測太高,一個預測太低,所以多個去平均會有較好的結果,且概念向是將多個分離的model output,在接一層全連階層,整體來說是可行的,但是只能說效果不是很顯著,其進步量也有限,且很吃運氣。 <h1> 3. Conclution </h1> 之前大部分都是去網路上下載別人的 model、別人的資料處理 ,這次所有的東西都是自己打的,所以對一些細節有更深的理解。再來是 K-Fold,之前有聽說過,但藉由這次作業才理解到底在幹麼,並實作出來。最後是操參數的調整,以前我都直接用 Adam 作為 optimizer,趁這次機會多學了好幾個 optimizer 了解其差異、優缺點,還有以前想說 Adam 本身好像就會去調整LR了,那使用 lr scheduler 還有用嗎?由於兩者調整的理論不同,所以 lr scheduler 還是有用。在 activation function 方面,也多認識了好幾個,我之前都無腦用ReLU。