# [ML] MNIST 手寫數字辨識 ###### tags: `ML` ## 簡介 MNIST手寫數字辨識是一個經典的機器學習項目,可以讓你學習分類器的基本原理。MNIST 是早在 1998 年就釋出的手寫數字辨識 dataset。因為他資料量小、架構簡單就能訓練,因此被視為深度學習界的 hello world 專案。今天我們就來用PyTorch來實作一個辨識手寫數字的模型。 今天我們選用的是最簡單的全連結神經網路,並在每一層加上 ReLU 激勵函數(activation function)。 ## 實作 ### Import package 在最開始我們必須import需要的package: ```python= import torch import torch.nn as nn import torch.optim as optim import torch.utils.data as data import torchvision.datasets as datasets import torchvision.transforms as transforms ``` ### 載入資料 我們使用`torchvision`提供的功能來下載MNIST資料: ```python= # 訓練資料集,用於訓練模型 train_data = datasets.MNIST( root="./mnist", train=True, transform=transforms.ToTensor(), download=True ) # 測試資料集,用於評估模型表現 test_data = datasets.MNIST( root="./mnist", train=False, transform=transforms.ToTensor(), download=True ) ``` 在這邊可以先用`matplotlib`來偷看一下資料的圖片長怎樣: ```python= import matplotlib.pyplot as plt plt.imshow( train_data.train_data[13].numpy(), cmap="gray" ) plt.title(train_data.train_labels[13].item()) ``` 我們進一步將訓練集分成真正的訓練集和驗證集,只有真正的訓練集用於訓練模型,驗證集用於挑選模型: ```python= # 將數據集分成訓練集和驗證集 num_train = len(train_data) indices = list(range(num_train)) split = int(num_train * 0.8) train_idx, valid_idx = indices[:split], indices[split:] train_sampler = data.SubsetRandomSampler(train_idx) valid_sampler = data.SubsetRandomSampler(valid_idx) ``` ### 定義模型 訓練之前必須先定義我們的模型。在這裡包含了三個步驟: * 繼承`nn.Module`類別 * 覆寫`__init__()`方法 * 覆寫`forward()`方法 ```python= class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(28 * 28, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, 10) def forward(self, x): x = x.view(-1, 28 * 28) x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x ``` 這個模型包含3個全連接層,分別為: * 第1層:輸入層,有 28 * 28 個神經元,對應輸入圖像的每個像素。 * 第2層:隱藏層,有 128 個神經元,使用 ReLU activation function。 * 第3層:輸出層,有 10 個神經元,對應 10 個數字類別。 在前向傳播過程中,我們將輸入圖像的像素展平成一個向量,並通過第1層的全連接層,然後使用 ReLU activation fucntion。接下來,我們將輸出傳遞到第2層的全連接層,並再次使用 ReLU activation function。最後,我們將輸出傳遞到第3層的全連接層,並輸出預測的類別機率。 這個模型的架構非常簡單,但卻可以達到不錯的效果。如果你想要更準確的模型,可以考慮使用更複雜的模型架構,例如卷積神經網絡(CNN)。 ### 訓練前的準備 在訓練之前我們先來定義幾個超參數: ```pytho= batch_size = 128 learning_rate = 0.01 num_epochs = 7 ``` 這三個超參數是指在訓練深度學習模型時需要人為設定的參數: * `batch_size`:指每個批次包含的樣本數量。如果`batch_size`設定的太小,那麼每次更新權重時,模型就會基於少量的樣本進行更新,這可能會導致訓練效率降低。反之,如果`batch_size`設定的太大,那麼每次更新權重時,模型就會基於大量的樣本進行更新,這可能會導致訓練過程變得較為不穩定。 * `learning_rate`:指用於更新模型權重的學習率。`learning_rate`設定的適當值對於模型的訓練效果非常重要。如果`learning_rate`設定的太大,那麼模型可能會跳過最佳解,而造成訓練效果更差。反之,如果`learning_rate`設定的太小,那麼訓練過程可能會非常緩慢。有時候我們也會採取一些非固定的 learning rate 調整策略,例如隨著訓練進行使得學習率越來越小等(但在本次專案中我們並沒有這麼做)。 ![](https://hackmd.io/_uploads/r1g0KZgqo.png) * `num_epochs`:指訓練迭代的輪數。通常情況下,模型的訓練效果隨著訓練輪數的增加而提升,但是如果訓練輪數過多,那麼模型就有可能會出現過擬合的現象。因此,你需要在訓練過程中觀察模型的表現,並在必要時調整訓練輪數。 接著我們可以根據剛剛給定的 batch size,利用 PyTorch 的`DataLoader`轉換成批量資料: ```python= # 將數據集轉換成批量資料 train_loader = data.DataLoader( dataset=train_data, batch_size=batch_size, sampler=train_sampler ) valid_loader = data.DataLoader( dataset=train_data, batch_size=batch_size, sampler=valid_sampler ) test_loader = data.DataLoader( dataset=test_data, batch_size=batch_size, shuffle=False ) ``` 然後再建立一個模型的實例、並定義好損失函數和優化器: ```python= # 建立模型實例 model = Net() # 定義損失函數和優化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) ``` 在這個分類任務中,我們使用交叉熵(cross entropy)損失函數來定義模型的損失。交叉熵損失函數是一種常用的**分類**任務損失函數,它可以衡量預測的分佈和真實的分佈之間的差距。較小的交叉熵損失值表示模型的預測結果更準確,反之亦然。在我們的程式碼中使用到PyTorch的`nn.CrossEntropyLoss`函數來定義交叉熵損失。這個函數會自動計算交叉熵損失和每個類別的平均損失,所以你不必自己手動計算。 ### 訓練 ```python= # 訓練模型 total_step = len(train_loader) for epoch in range(num_epochs): for i, (images, labels) in enumerate(train_loader): outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() if (i+1) % 100 == 0: print ("Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}" .format(epoch+1, num_epochs, i+1, total_step, loss.item())) # 評估模型 with torch.no_grad(): correct = 0 total = 0 for images, labels in valid_loader: outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print("Accuracy of the model on the valid images: {} %" .format(100 * correct / total)) ```