為了詳細了解每一段程式碼背後的原理,以下會透過拆解Homework 1: COVID-19 Cases Prediction (Regression)的程式碼 # Reference This notebook uses code written by Heng-Jui Chang @ NTUEE (https://github.com/ga642381/ML2021-Spring/blob/main/HW01/HW01.ipynb) # 導入一些functions ## same_seed ``` def same_seed(seed): '''Fixes random number generator seeds for reproducibility.''' torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) ``` 這段程式碼是一個Python函式,名為`same_seed`,用於設定隨機數生成器的種子,以實現可重複性。以下是程式碼的每個部分的解釋: 1. `torch.backends.cudnn.deterministic = True`: 這行程式碼設定了PyTorch庫的CuDNN(CUDA Deep Neural Network library)模組,使其運行時的算法變得確定性,這樣每次運行程式時,相同的輸入將會得到相同的輸出。這有助於確保在不同的運行之間產生相同的結果。 2. `torch.backends.cudnn.benchmark = False`: 這行程式碼關閉了CuDNN的自動調整功能,以確保每次運行時使用的算法和參數是一致的,從而確保結果的可重複性。儘管自動調整可能提高一些運算的效率,但這會導致不同運行之間的結果可能不同。 3. `np.random.seed(seed)`: 這行程式碼使用NumPy庫設定了隨機數生成器的種子,從而確保在使用NumPy生成隨機數時,不同運行之間的結果是相同的。這對於數值實驗的可重複性非常重要。 4. `torch.manual_seed(seed)`: 這行程式碼設定了PyTorch的隨機數生成器的種子,確保在使用PyTorch生成隨機數時,不同運行之間的結果是相同的。這包括了在CPU上進行的隨機運算。 5. `if torch.cuda.is_available():`: 這是一個條件判斷,檢查是否有可用的CUDA(NVIDIA的並行運算平台)設備。如果有,接下來的程式碼將對CUDA進行相關的種子設定。 6. `torch.cuda.manual_seed_all(seed)`: 這行程式碼設定了所有可用的CUDA設備的隨機數生成器的種子,確保在使用CUDA進行運算時,不同運行之間的結果是相同的。 總之,這段程式碼確保了在使用PyTorch和NumPy進行運算時,無論是在CPU還是GPU上,相同種子的輸入將會產生相同的輸出,從而實現了實驗結果的可重複性。這對於開發、測試和比較不同模型、演算法或參數設定時非常有用。 ## train_valid_split ``` def train_valid_split(data_set, valid_ratio, seed): '''Split provided training data into training set and validation set''' valid_set_size = int(valid_ratio * len(data_set)) train_set_size = len(data_set) - valid_set_size train_set, valid_set = random_split(data_set, [train_set_size, valid_set_size], generator=torch.Generator().manual_seed(seed)) return np.array(train_set), np.array(valid_set) ``` 這段程式碼是一個Python函式,名為`train_valid_split`,用於將提供的訓練資料分割成訓練集和驗證集。以下是程式碼的每個部分的解釋: 1. `valid_ratio`:這是一個參數,表示希望分配給驗證集的資料比例,通常在0到1之間。 2. `data_set`:這是一個資料集的變數,可能是一個包含訓練資料的容器,例如一個Python清單、NumPy數組或其他類似的資料結構。 3. `seed`:這是用於種子生成的整數值,它將在隨機分割資料時用於確保可重複性。 4. `valid_set_size` 和 `train_set_size`:這兩個變數分別表示驗證集和訓練集的大小。根據`valid_ratio`參數,它們通過計算資料集大小的一部分來確定。 5. `random_split` 函式:這是一個用於將資料集進行隨機分割的函式。它接受三個參數:資料集、要分配的大小列表(這裡是 `[train_set_size, valid_set_size]`),以及一個隨機種子生成器,該種子生成器以 `seed` 為種子來確保可重複性。 6. `return` 語句:最後,這個函式返回兩個NumPy數組,分別代表訓練集和驗證集。這樣,調用這個函式後,您將得到分割後的訓練集和驗證集。 總之,這段程式碼是用於將訓練資料分割成訓練集和驗證集的工具。這在機器學習和深度學習中非常常見,因為它可以用來進行模型訓練和驗證,並確保訓練和評估過程的可重複性。 ## predict ``` def predict(test_loader, model, device): model.eval() # Set your model to evaluation mode. preds = [] for x in tqdm(test_loader): x = x.to(device) with torch.no_grad(): pred = model(x) preds.append(pred.detach().cpu()) preds = torch.cat(preds, dim=0).numpy() return preds ``` 這段程式碼是一個Python函式,名為`predict`,用於使用訓練好的模型對測試資料進行預測。以下是程式碼的每個部分的解釋: 1. `test_loader`:這是包含測試資料的資料載入器(data loader),它通常用於批次(batch)地載入資料以進行預測。 2. `model`:這是訓練好的模型,用於進行預測。在此函式中,模型將被設置為評估模式(evaluation mode),這意味著模型的行為將會受到一些影響,例如,一些具有不同行為的層(如Dropout)在評估模式下會被禁用,從而確保預測的穩定性。 3. `device`:這是模型所在的計算設備,可以是CPU或GPU。預測將在指定的設備上進行。 4. `model.eval()`:這行程式碼將模型設置為評估模式。這是因為在預測階段,我們通常不需要訓練模式的功能,而是希望模型在預測時的行為一致且穩定。 5. `preds`:這是一個空列表,用於存儲所有預測結果的tensor。 6. `for x in tqdm(test_loader):`:這是一個迴圈,遍歷`test_loader`中的每個資料批次。`tqdm`用於在迴圈中顯示進度條,讓您知道迴圈運行的進度。 7. `x = x.to(device)`:將資料批次`x`移動到指定的計算設備上(CPU或GPU)。 8. `with torch.no_grad():`:這是一個上下文管理器,它內部的程式碼將在"no_grad"模式下運行,這意味著計算過程中不會記錄梯度。這在預測階段是有用的,因為我們不需要計算梯度,只需進行前向傳遞。 9. `pred = model(x)`:使用模型對資料進行預測,得到預測結果。 10. `preds.append(pred.detach().cpu())`:將預測結果添加到`preds`列表中。`detach()`用於分離計算圖,`cpu()`將數據移到CPU上。 11. `preds = torch.cat(preds, dim=0).numpy()`:將所有預測結果的tensor串聯起來,並轉換為NumPy數組。這將返回包含所有預測結果的NumPy數組。 12. `return preds`:這個函式返回預測結果的NumPy數組,即模型對測試資料的預測值。 ### Q: 為何要定義`.cpu()`後面的段落? A: 在機器學習和深度學習中,通常會使用 GPU 進行計算,因為 GPU 可以加速計算運算。然而,在某些情況下,需要將數據從 GPU 移到 CPU,或者將計算結果從 GPU 移到 CPU。這種移動的原因可能包括: 1. **內存管理**:GPU 的內存有限,如果在訓練過程中產生的數據或計算結果超過 GPU 內存的容量,就需要將數據或結果移動到 CPU 上,以便處理更大量的數據。 2. **可視化和保存**:在訓練過程中,可能需要將中間結果或最終結果保存到文件或數據庫中。通常,這些操作需要在 CPU 上進行,因此需要將 GPU 上的數據轉移到 CPU。 3. **後續分析**:有時需要對模型的預測結果進行後續分析、評估或可視化。這些操作通常需要使用 NumPy 或其他 Python 函數庫,這些庫通常在 CPU 上運行,因此需要將 GPU 上的數據轉移到 CPU。 而 `.detach()` 和 `.cpu()` 方法是 PyTorch 提供的方式,用於實現數據移動和副本的操作。具體來說: - `.detach()` 方法用於創建張量的一個副本,該副本不會與計算圖相關聯,即不會進行梯度計算。這通常在需要將數據轉移或保存時使用,以避免計算圖的佔用。 - `.cpu()` 方法將張量的數據從 GPU 移到 CPU。這在需要進行後續處理或保存時很有用,因為大多數的 Python 函數庫和操作在 CPU 上運行。 而 `torch.cat(preds, dim=0)` 是將 `preds` 列表中的多個張量(來自於每個批次的預測結果)在指定的維度上連接起來,以形成一個大的張量。在這個情境下,我們希望將所有測試樣本的預測結果連接在一起,方便後續的分析和保存。 總之,上述的操作是為了有效地處理在 GPU 和 CPU 之間進行數據移動,並確保模型的預測結果能夠方便地進行後續的處理、分析和保存。 # Dataset ``` class COVID19Dataset(Dataset): ''' x: Features. y: Targets, if none, do prediction. ''' def __init__(self, x, y=None): if y is None: self.y = y else: self.y = torch.FloatTensor(y) self.x = torch.FloatTensor(x) def __getitem__(self, idx): if self.y is None: return self.x[idx] else: return self.x[idx], self.y[idx] def __len__(self): return len(self.x) ``` 這段程式碼定義了一個名為 `COVID19Dataset` 的類別,它用於創建一個自定義的 PyTorch 資料集(Dataset)。這個資料集可以用於訓練、驗證和測試機器學習模型。 以下是這個類別的每個方法的詳細說明: 1. `__init__(self, x, y=None)`: 這是類別的建構子方法(constructor)。它接受兩個參數:`x` 和 `y`。`x` 是資料的特徵(features),`y` 是資料的目標(targets)。如果沒有提供 `y`,則意味著這個資料集用於預測(未標記的)。 在這個方法內,如果 `y` 是 `None`,則 `self.y` 也設置為 `None`,否則 `self.y` 會被轉換為一個 PyTorch 的浮點數張量(`torch.FloatTensor`)。`self.x` 也會被轉換為一個浮點數張量。 這段程式碼是 `COVID19Dataset` 類別的初始化方法 `__init__`,它在創建資料集實例時被呼叫。這個方法用於將特徵數據和目標數據(如果提供)儲存在資料集對象中,以便後續的訪問和操作。 ### 以下是 `__init__` 方法的詳細解釋: (1) `def __init__(self, x, y=None):`: 這是初始化方法的定義,它接收兩個參數:`x` 是特徵數據,`y` 是目標數據(可選)。在創建 `COVID19Dataset` 實例時,我們可以提供特徵數據和相應的目標數據,也可以只提供特徵數據,這取決於資料集的用途。 (2) `if y is None:`: 這個條件語句檢查是否提供了目標數據 `y`。如果 `y` 是 `None`,表示沒有提供目標數據,資料集可能是測試集或者我們只想用模型進行預測。 (3) `self.y = y`: 如果 `y` 是 `None`,則將 `self.y` 設置為 `None`,表示資料集中沒有目標數據。這將在 `__getitem__` 方法中處理,如前面的解釋所示。 (4) `self.y = torch.FloatTensor(y)`: 如果提供了目標數據 `y`,則將 `self.y` 初始化為一個 PyTorch 的浮點張量(`torch.FloatTensor`)。這將使目標數據能夠適應 PyTorch 的計算框架,以便進行後續的操作和計算。 (5) `self.x = torch.FloatTensor(x)`: 將特徵數據 `x` 初始化為一個 PyTorch 的浮點張量。同樣,這將使特徵數據能夠適應 PyTorch 的計算框架,以便進行後續的操作和計算。 總之,`__init__` 方法用於初始化資料集的特徵數據和目標數據,並將它們儲存在資料集對象中,以便於後續的訪問和使用。如果提供了目標數據,它們會被轉換成 PyTorch 張量,以便於與 PyTorch 的計算框架整合。 2. `__getitem__(self, idx)`: 這個方法是用於取得資料集中特定索引 `idx` 對應的資料。如果 `y` 不為 `None`,則返回一個包含特徵 `self.x[idx]` 和目標 `self.y[idx]` 的元組。否則,只返回特徵 `self.x[idx]`。 這段程式碼定義了一個自定義的 PyTorch 資料集類別 `COVID19Dataset`,該類別用於讀取和處理資料集中的樣本。這裡主要涉及了 `__getitem__` 方法,它是 Python 物件的特殊方法,用於取得物件中特定索引對應的資料。 ### 以下是 __getitem__ 方法的詳細解釋: (1) `def __getitem__(self, idx):`: 這是一個特殊方法,當我們使用索引操作符 `[]` 訪問物件時,會調用這個方法。`idx` 是要訪問的索引值。 (2) `if self.y is None:`: 首先,它檢查 `y` 是否為 `None`。如果 `y` 是 `None`,表示這個資料集不包含目標變量(例如,用於測試集或預測時)。 (3) `return self.x[idx]`: 如果 `y` 是 `None`,則只返回特徵張量 `self.x` 中索引 `idx` 對應的部分。這意味著當我們使用 `dataset[idx]` 訪問資料集時,將返回該索引的特徵數據。 (4) `return self.x[idx], self.y[idx]`: 如果 `y` 不是 `None`,則返回一個元組,包含特徵張量 `self.x` 中索引 `idx` 對應的部分以及目標張量 `self.y` 中索引 `idx` 對應的部分。這意味著當我們使用 `dataset[idx]` 訪問資料集時,將返回該索引的特徵數據和對應的目標數據。 總之,`__getitem__` 方法定義了如何根據索引值從資料集中取得特定樣本的數據。根據是否有目標變量,它會返回包含特徵和目標的元組,或只返回特徵。這樣的設計使得我們可以使用索引操作符方便地訪問資料集中的數據。 3. `__len__(self)`: 這個方法返回資料集的大小,即資料集中的樣本數,它是 `self.x` 的長度。 總之,這個 `COVID19Dataset` 類別用於創建一個 PyTorch 資料集,該資料集可以包含特徵和目標。如果目標是 `None`,則表示資料集用於預測。這樣的自定義資料集可用於 PyTorch 中的 DataLoader,以更有效地處理訓練資料的載入、轉換和批次處理。 # Neural Network Model ``` class My_Model(nn.Module): def __init__(self, input_dim): super(My_Model, self).__init__() # TODO: modify model's structure, be aware of dimensions. self.layers = nn.Sequential( nn.Linear(input_dim, 16), nn.ReLU(), nn.Linear(16, 8), nn.ReLU(), nn.Linear(8, 1) ) def forward(self, x): x = self.layers(x) x = x.squeeze(1) # (B, 1) -> (B) return x ``` 這段程式碼定義了一個名為 `My_Model` 的 PyTorch 模型(Neural Network)。這個模型似乎用於執行某種回歸任務,即預測連續數值輸出。以下是這個模型的詳細說明: 1. `__init__(self, input_dim)`: 這是類別的建構子方法(constructor)。它接受一個參數 `input_dim`,表示輸入的特徵數目。在這個方法內,模型的結構被定義。 - `nn.Linear(input_dim, 16)` 創建了一個線性層(全連接層),將輸入特徵維度降至 16 維。 - `nn.ReLU()` 創建了一個 ReLU(Rectified Linear Unit)激活函式,引入非線性。 - 再次使用 `nn.Linear(16, 8)` 創建另一個線性層,將 16 維的輸入降至 8 維。 - 再次使用 `nn.ReLU()` 創建 ReLU 激活函式。 - 最後一個 `nn.Linear(8, 1)` 創建一個輸出層,將 8 維的輸入輸出為 1 維。 所有這些層組成了一個順序(sequential)模型,代表了神經網絡的結構。 ### `nn.Linear()` 和 `nn.ReLU()` 是神經網路模型中的兩種不同類型的層,分別是全連接層和 ReLU 激活層。它們的功能和作用如下: (1) `nn.Linear(input_dim, 16)`: 這是一個全連接層,也稱為線性層或稠密層。它執行線性轉換,將輸入特徵進行線性組合,並生成指定數目的輸出特徵。在這裡,`input_dim` 表示輸入特徵的維度,16 表示輸出特徵的維度。該層的計算方式為 `output = input @ weight + bias`,其中 `@` 表示矩陣相乘,`weight` 是權重矩陣,`bias` 是偏差向量。這一層的作用是將輸入特徵進行線性變換,以便學習特徵之間的線性關係。 (2) `nn.ReLU()`: 這是一個 ReLU 激活層,它對輸入進行非線性的激活操作。ReLU(Rectified Linear Unit)是一種常用的激活函數,其計算方式為 `output = max(0, input)`。它將負值截斷為零,保留正值不變,引入了非線性性質。這是神經網路中的一個重要操作,使得網路能夠學習和模擬更複雜的非線性關係。 總之,`nn.Linear` 層是一個線性變換層,用於對特徵進行線性組合;而 `nn.ReLU` 層是一個非線性激活層,用於引入非線性性質,使神經網路能夠學習更複雜的特徵關係。在深層神經網路中,通常會交替使用這兩種層,以構建多層的網路結構。 2. `forward(self, x)`: 這是模型的前向傳遞方法。在這個方法內,輸入 `x` 通過模型的結構進行順向傳播。首先,`x` 通過 `self.layers`,這是前面定義的 Sequential 結構,從而將輸入特徵進行一系列的線性變換和非線性激活。然後,`x` 使用 `x.squeeze(1)` 被壓縮為一維,從 `(B, 1)` 到 `(B)`,其中 `B` 代表批次大小(batch size)。 最終,經過模型的前向傳遞,輸出被產生並返回。 總之,這個 `My_Model` 類別定義了一個簡單的前向傳遞神經網絡模型,適用於執行回歸任務。它包含了幾個線性層和 ReLU 激活函式,輸出層的數量為 1,通過前向傳遞生成預測結果 # Feature Selection ``` def select_feat(train_data, valid_data, test_data, select_all=True): '''Selects useful features to perform regression''' y_train, y_valid = train_data[:,-1], valid_data[:,-1] raw_x_train, raw_x_valid, raw_x_test = train_data[:,:-1], valid_data[:,:-1], test_data if select_all: feat_idx = list(range(raw_x_train.shape[1])) else: feat_idx = [0,1,2,3,4] # TODO: Select suitable feature columns. return raw_x_train[:,feat_idx], raw_x_valid[:,feat_idx], raw_x_test[:,feat_idx], y_train, y_valid ``` 這段程式碼定義了一個名為 `select_feat` 的函式,用於從給定的訓練資料、驗證資料和測試資料中選擇有用的特徵(features)來進行回歸。以下是程式碼的每個部分的詳細解釋: 1. `train_data`、`valid_data` 和 `test_data`: 這些是提供的訓練資料、驗證資料和測試資料。每個資料集都被假設為包含特徵和目標,其中最後一列是目標,其餘列是特徵。 2. `select_all`: 這是一個布林值參數,控制是否選擇所有特徵。如果設定為 `True`,則將選擇所有特徵;如果設定為 `False`,則將根據 `feat_idx` 選擇指定的特徵。 3. `y_train, y_valid`: 這裡從訓練資料和驗證資料中分別提取目標數據,`y_train` 是訓練目標,`y_valid` 是驗證目標。 4. `raw_x_train, raw_x_valid, raw_x_test`: 這裡分別從訓練資料、驗證資料和測試資料中提取特徵數據,`raw_x_train` 是訓練特徵,`raw_x_valid` 是驗證特徵,`raw_x_test` 是測試特徵。 5. `if select_all:`: 這是一個條件判斷,檢查是否要選擇所有特徵。如果 `select_all` 為 `True`,則進入這個條件。 6. `feat_idx = list(range(raw_x_train.shape[1]))`: 如果要選擇所有特徵,則建立一個包含所有特徵索引的列表 `feat_idx`。`raw_x_train.shape[1]` 返回訓練特徵的列數,也就是特徵數目。 7. `else:`: 如果 `select_all` 為 `False`,則進入這個條件。 8. `feat_idx = [0,1,2,3,4]`: 這個列表 `feat_idx` 包含了手動選擇的特徵索引。這表示只選擇列表中指定的特徵,可以根據需要進行修改。 9. `return raw_x_train[:,feat_idx], raw_x_valid[:,feat_idx], raw_x_test[:,feat_idx], y_train, y_valid`: 這個函式最終返回一組已選擇特徵的訓練特徵、驗證特徵、測試特徵以及相應的目標數據。這樣,在回歸任務中,只會使用選定的特徵進行訓練和預測。 總之,這個 `select_feat` 函式用於從提供的資料中選擇特定的特徵,這在機器學習中是一個常見的步驟,以提高模型的效能和減少過度擬合的風險。該函式返回選擇的特徵和相應的目標,以便進一步進行回歸任務。 # Training Loop ``` def trainer(train_loader, valid_loader, model, config, device): # 定義損失函數,這裡使用均方誤差(MSE),請勿修改此部分。 criterion = nn.MSELoss(reduction='mean') # 定義優化器。 # TODO: 請參考 https://pytorch.org/docs/stable/optim.html 獲取更多可用的優化算法。 # TODO: 可以添加 L2 正則化(優化器的 weight decay)或自行實現。 optimizer = torch.optim.SGD(model.parameters(), lr=config['learning_rate'], momentum=0.9) writer = SummaryWriter() # 用於 TensorBoard 的寫入器。 if not os.path.isdir('./models'): os.mkdir('./models') # 創建保存模型的目錄。 n_epochs, best_loss, step, early_stop_count = config['n_epochs'], math.inf, 0, 0 for epoch in range(n_epochs): model.train() # 設置模型為訓練模式。 loss_record = [] # 用於記錄每個小批次的損失值。 ``` 這段程式碼對 `trainer` 函式進行了初始化,它會進行模型的訓練,以及相關參數和記錄的初始化。在訓練過程中,每個訓練週期(epoch)會進行以下步驟: 1. 將模型切換為訓練模式。 2. 創建一個空的 `loss_record` 列表,用於記錄每個小批次的損失值。 在這段程式碼中,我們開始了模型的訓練迴圈,這個迴圈將在每個訓練週期內執行以下步驟: 1. **模型訓練**:迭代訓練數據集的小批次,計算梯度並更新參數,使模型學習適應訓練數據。每個小批次都會進行以下操作: - 將優化器的梯度歸零,以確保每個小批次的梯度都是獨立計算的。 - 將輸入數據和目標標籤移至指定的設備(例如 GPU)。 - 通過模型進行預測,獲得預測輸出。 - 計算預測輸出和真實標籤之間的損失,這裡使用均方誤差(MSE)作為損失函數。 - 進行反向傳播,計算梯度。 - 使用優化器進行參數更新。 - 將當前小批次的損失值加入 `loss_record` 列表。 這段程式碼確保模型在每個小批次上進行了訓練,並將每個小批次的損失值記錄下來。進度條(`train_pbar`)會在每個小批次上更新,顯示當前的訓練週期和損失值。 請注意,這只是 `trainer` 函式的一部分,它在整個訓練過程中負責模型的訓練和參數更新。 ### 優化器的重要性 在機器學習中,優化器(Optimizer)是一個用於更新模型參數的關鍵工具。模型的目標是最小化損失函數,而優化器的作用就是根據模型的梯度信息,以適當的方式調整參數,使損失函數值逐漸降低,從而提高模型的性能。 以下是使用優化器的重要原因: 1. **參數更新**:在訓練過程中,模型的目標是最小化損失函數。透過計算損失函數對於參數的梯度,優化器能夠將參數調整到能夠降低損失函數的方向,以達到更好的模型性能。 2. **學習率調整**:優化器通常有一個稱為「學習率」的超參數,用於控制參數的更新幅度。學習率的選擇直接影響參數的收斂速度和模型的最終性能。不同的優化器可能有不同的學習率調整策略。 3. **梯度更新策略**:優化器可以根據梯度的大小和方向,采取不同的策略來更新參數。例如,SGD(隨機梯度下降)每次只使用單一的樣本來計算梯度和更新,而Adam等優化器則結合了過去梯度的信息,調整參數更新策略。 4. **避免局部極值**:損失函數通常具有多個極值點,而不一定是全局最小值。優化器通過適當的策略,能夠在參數空間中搜索不同的區域,從而有機會逃離局部極值,找到更好的參數組合。 總之,優化器在訓練過程中對於模型的參數更新至關重要。不同的優化器算法有不同的性能表現,根據問題的特點和需求,選擇適合的優化器可以幫助模型更快地收斂並取得更好的結果。 ``` # tqdm 是一個用於可視化訓練進度的套件。 train_pbar = tqdm(train_loader, position=0, leave=True) for x, y in train_pbar: optimizer.zero_grad() # 將梯度歸零。 x, y = x.to(device), y.to(device) # 將數據移至指定設備(例如 GPU)進行計算。 pred = model(x) loss = criterion(pred, y) loss.backward() # 計算梯度(反向傳播)。 optimizer.step() # 更新模型參數。 step += 1 loss_record.append(loss.detach().item()) # 在 tqdm 進度條上顯示當前的訓練週期和損失值。 train_pbar.set_description(f'Epoch [{epoch+1}/{n_epochs}]') train_pbar.set_postfix({'loss': loss.detach().item()}) mean_train_loss = sum(loss_record)/len(loss_record) writer.add_scalar('Loss/train', mean_train_loss, step) model.eval() # 將模型切換到評估模式。 loss_record = [] for x, y in valid_loader: x, y = x.to(device), y.to(device) with torch.no_grad(): pred = model(x) loss = criterion(pred, y) loss_record.append(loss.item()) mean_valid_loss = sum(loss_record)/len(loss_record) print(f'Epoch [{epoch+1}/{n_epochs}]: 訓練損失: {mean_train_loss:.4f}, 驗證損失: {mean_valid_loss:.4f}') writer.add_scalar('Loss/valid', mean_valid_loss, step) if mean_valid_loss < best_loss: best_loss = mean_valid_loss torch.save(model.state_dict(), config['save_path']) # 儲存最佳模型 print('以損失 {:.3f} 儲存模型...'.format(best_loss)) early_stop_count = 0 else: early_stop_count += 1 if early_stop_count >= config['early_stop']: print('\n模型未顯著改善,因此我們終止訓練過程。') return ``` 這段程式碼的功能如下: 1. 使用 `tqdm` 創建進度條,用於可視化訓練進度。 2. 迭代訓練數據(`train_loader`)的小批次。在每次迭代中: - 使用 `optimizer.zero_grad()` 將模型參數的梯度重置為零。 - 將輸入數據(`x`)和目標標籤(`y`)移至指定設備(例如 GPU)進行計算。 - 模型根據輸入數據進行預測(`pred`)。 - 使用指定的損失標準(`criterion`)計算預測輸出和目標標籤之間的損失。 - 使用 `loss.backward()` 進行反向傳播,計算梯度。 - 使用 `optimizer.step()` 更新模型參數。 3. 進度條顯示當前的訓練週期和損失信息。 4. 計算平均訓練損失並使用 TensorBoard 進行記錄(`writer.add_scalar`)。 5. 使用 `model.eval()` 將模型切換為評估模式,然後迭代驗證數據(`valid_loader`)以評估模型在驗證集上的表現。 - 在評估過程中,不計算梯度,也不進行參數更新。 6. 計算平均驗證損失並輸出。 7. 如果平均驗證損失低於最佳記錄的損失(`best_loss`),則將模型的狀態字典儲存到指定路徑(`config['save_path']`)。 8. 如果驗證損失未改善,則計數器(`early_stop_count`)遞增。如果此計數器超過指定的提前停止標準(`config['early_stop']`),則終止訓練過程。 總之,這段程式碼組織了模型的訓練和驗證過程,包括損失計算、梯度更新和提前停止機制,同時通過進度條提供視覺反饋,並對重要信息進行記錄以供分析。 # Configurations `config` contains hyper-parameters for training and the path to save your model. ``` device = 'cuda' if torch.cuda.is_available() else 'cpu' config = { 'seed': 5201314, # Your seed number, you can pick your lucky number. :) 'select_all': True, # Whether to use all features. 'valid_ratio': 0.2, # validation_size = train_size * valid_ratio 'n_epochs': 3000, # Number of epochs. 'batch_size': 256, 'learning_rate': 1e-5, 'early_stop': 400, # If model has not improved for this many consecutive epochs, stop training. 'save_path': './models/model.ckpt' # Your model will be saved here. } ``` 這段程式碼定義了一個叫做 `config` 的字典,其中包含了用於模型訓練的各種參數和設置。以下是每個參數的詳細解釋: 1. `'seed': 5201314`: 這是隨機數生成器的種子,用於固定隨機數生成的序列。設置這個種子可以確保每次執行時使用相同的隨機序列,以實現可重現的實驗結果。 2. `'select_all': True`: 這個參數指示是否使用所有的特徵。如果設置為 `True`,則將使用所有可用的特徵進行模型訓練;如果設置為 `False`,則可能根據指定的特徵索引篩選特定的特徵。 3. `'valid_ratio': 0.2`: 這個參數表示驗證集在整個訓練集中的比例。例如,如果訓練集有 1000 條數據,並且 `valid_ratio` 設置為 0.2,則驗證集將包含 200 條數據。 4. `'n_epochs': 3000`: 這是模型訓練的總迭代次數,也稱為「訓練的輪數」。模型將在整個訓練集上進行 `n_epochs` 輪的訓練。 5. `'batch_size': 256`: 這個參數表示每個訓練迭代中的批次大小。在每次迭代中,模型將根據這個批次大小從訓練集中選擇一部分數據進行訓練。 6. `'learning_rate': 1e-5`: 這是優化算法的學習率,它決定了模型參數在每次迭代中的更新步長。學習率越大,模型參數更新越快,但可能會導致不穩定性;學習率越小,模型參數更新越穩定,但可能需要更多的迭代次數。 7. `'early_stop': 400`: 如果模型在連續的 `early_stop` 輪中都沒有顯著提升,則停止訓練。這是一種防止過度擬合的策略,以避免在驗證集上表現不佳的模型。 8. `'save_path': './models/model.ckpt'`: 這是保存模型的路徑,訓練過程中最佳的模型將會保存在這個路徑下。 總之,`config` 字典中的這些參數定義了模型訓練的各種設置,包括模型結構、訓練輪數、批次大小、學習率等,以便在後續的程式碼中使用。這樣的設定能夠影響訓練的過程和結果。 # Dataloader ``` # Set seed for reproducibility same_seed(config['seed']) # train_data size: 2699 x 118 (id + 37 states + 16 features x 5 days) # test_data size: 1078 x 117 (without last day's positive rate) train_data, test_data = pd.read_csv('./covid.train.csv').values, pd.read_csv('./covid.test.csv').values train_data, valid_data = train_valid_split(train_data, config['valid_ratio'], config['seed']) # Print out the data size. print(f"""train_data size: {train_data.shape} valid_data size: {valid_data.shape} test_data size: {test_data.shape}""") # Select features x_train, x_valid, x_test, y_train, y_valid = select_feat(train_data, valid_data, test_data, config['select_all']) # Print out the number of features. print(f'number of features: {x_train.shape[1]}') train_dataset, valid_dataset, test_dataset = COVID19Dataset(x_train, y_train), \ COVID19Dataset(x_valid, y_valid), \ COVID19Dataset(x_test) # Pytorch data loader loads pytorch dataset into batches. train_loader = DataLoader(train_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True) valid_loader = DataLoader(valid_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True) test_loader = DataLoader(test_dataset, batch_size=config['batch_size'], shuffle=False, pin_memory=True) ``` 這段程式碼執行了一系列的步驟,將數據準備好以進行模型訓練和驗證。以下是程式碼的詳細解釋: 1. `same_seed(config['seed'])`: 使用之前定義的 `same_seed` 函數來設定隨機數生成器的種子,以實現可重現性。 2. 讀取和切分數據: - 使用 `pd.read_csv` 函數讀取訓練數據和測試數據,並將它們轉換為 NumPy 陣列。 - 使用 `train_valid_split` 函數將訓練數據切分成訓練集和驗證集,並指定了驗證集佔整個數據集的比例。 3. 輸出數據的大小: 使用 `print` 函數輸出訓練集、驗證集和測試集的大小,以檢查數據的維度。 4. 選擇特徵: 使用 `select_feat` 函數從訓練集、驗證集和測試集中選擇相關的特徵,並根據配置的設定來選擇特定的特徵。 5. 輸出特徵的數量: 使用 `print` 函數輸出選擇的特徵的數量。 6. 創建 PyTorch 數據集: - 使用 `COVID19Dataset` 類別創建訓練、驗證和測試的 PyTorch 數據集,並將特徵和目標(對於訓練和驗證集)轉換為 PyTorch 的 `torch.FloatTensor`。 - 此處數據集的目的是將數據準備好,以便後續載入到 PyTorch 的數據加載器(DataLoader)中。 7. 創建數據加載器(DataLoader): - 使用 `DataLoader` 函數創建訓練、驗證和測試的數據加載器,將數據集按照指定的批次大小進行分批處理。 - `batch_size` 是指定每個批次的樣本數量。 - `shuffle` 參數指示是否對數據進行洗牌(隨機排序),對於測試數據不需要洗牌。 - `pin_memory` 參數指示是否將數據轉移到 GPU 的固定內存中,以加速數據載入。 總之,這段程式碼的目標是將數據準備好,並為訓練過程和驗證過程創建相應的數據加載器,以便在模型訓練和驗證時使用。 # Start training ``` model = My_Model(input_dim=x_train.shape[1]).to(device) # put your model and data on the same computation device. trainer(train_loader, valid_loader, model, config, device) ``` 這段程式碼的主要目的是使用訓練數據對指定的模型進行訓練,同時使用驗證數據來監控訓練的過程和效果。以下是程式碼的詳細解釋: 1. `model = My_Model(input_dim=x_train.shape[1]).to(device)`: 創建了一個 `My_Model` 的實例,並根據輸入特徵的維度調整模型的結構。然後,使用 `.to(device)` 將模型移到指定的計算裝置(通常是 GPU)上,以加速計算。 2. `trainer(train_loader, valid_loader, model, config, device)`: 呼叫了名為 `trainer` 的函數,並將以下參數傳遞給它: - `train_loader`:訓練數據的 DataLoader,用於在訓練過程中遞送批次數據。 - `valid_loader`:驗證數據的 DataLoader,用於在訓練過程中進行模型的驗證。 - `model`:剛剛創建的模型實例,將在訓練過程中被訓練。 - `config`:一個包含訓練配置參數的字典,用於控制訓練過程中的各種設置。 - `device`:指定的計算裝置,模型和數據將在同一裝置上進行計算。 在這個函數內部,模型將使用 `train_loader` 的數據進行訓練,同時使用 `valid_loader` 的數據來監控訓練過程中的驗證損失。該函數的內容可能涉及到優化器的初始化、損失函數的定義、模型參數的更新等步驟。 總之,這段程式碼是用於訓練一個指定模型的部分,並在訓練過程中使用驗證數據來監控模型的效果和避免過度擬合。這是一個典型的深度學習訓練過程。 # Testing ``` def save_pred(preds, file): ''' Save predictions to specified file ''' with open(file, 'w') as fp: writer = csv.writer(fp) writer.writerow(['id', 'tested_positive']) for i, p in enumerate(preds): writer.writerow([i, p]) model = My_Model(input_dim=x_train.shape[1]).to(device) model.load_state_dict(torch.load(config['save_path'])) preds = predict(test_loader, model, device) save_pred(preds, 'pred.csv') ``` 這段程式碼的主要目標是使用已經訓練好的模型進行預測,然後將預測結果儲存到一個 CSV 檔案中。 以下是程式碼的解釋: 1. `model = My_Model(input_dim=x_train.shape[1]).to(device)`: 創建了一個 `My_Model` 的實例,並傳入輸入特徵的維度。然後,將模型移到指定的計算裝置(通常是 GPU)上,以加速計算。 2. `model.load_state_dict(torch.load(config['save_path']))`: 載入之前訓練過並保存在指定路徑的模型參數。這將恢復模型的參數,以便進行預測。 3. `preds = predict(test_loader, model, device)`: 使用剛剛載入的模型對測試數據進行預測,獲得預測結果。`predict` 函數可能在您的代碼中定義,通常用於對測試數據進行批次預測。 4. `save_pred(preds, 'pred.csv')`: 使用 `save_pred` 函數將預測結果儲存到一個 CSV 檔案中,檔名為 'pred.csv'。每一行代表一個預測,包括 `id` 和 `tested_positive` 兩個欄位。 總之,這段程式碼主要是將已經訓練好的模型應用於測試數據,獲得預測結果並保存到一個 CSV 檔案中,以便之後進行評估或提交。