iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
AI & Data

Deep Learning 從零開始到放棄的 30 天 PyTorch 數字辨識模型系列 第 28

Day-27 手把手的手寫面是模型 0x2:資料訓練和結果輸出

  • 我們昨天成功拿到資料了,今天就要開始訓練模型了
  • 那由於我們當初在介紹 CNN 結構時的 code 就已經是基於手寫辨識資料集做撰寫的,因此我們就直接拿來用吧~
  • 那今天就會針對資料集的狀況還有 CNN 的狀況去做比較解釋,應該有助於理解當初為啥是這樣寫 CNN

回顧資料集

# 0) data import and set as pytorch dataset
import torchvision
import torchvision.transforms as transforms

# MNIST
train_dataset = torchvision.datasets.MNIST(root='./data', train=True,
    transform=transforms.ToTensor(), download=False)

test_dataset = torchvision.datasets.MNIST(root='./data', train=False,
    transform=transforms.ToTensor(), download=False)

train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,
    shuffle=True)

test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size,
    shuffle=True)
  • 我們的資料集是一個灰階色系(1 channel)的 28 * 28 的手寫數字圖片,那我們的目標是去撰寫一個可以分類 0~9 十個數字的分類
  • 那我們的 CNN 是如何對應去做撰寫的呢?

CNN

# 1) model build
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
        # image shape is 1 * 28 * 28, where 1 is one color channel
        # 28 * 28 is the image size
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=3, kernel_size=5)    # output shape = 3 * 24 * 24
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)                       # output shape = 3 * 12 * 12
        # intput shape is 3 * 12 * 12
        self.conv2 = nn.Conv2d(in_channels=3, out_channels=9, kernel_size=5)    # output shape = 9 * 8 * 8
        # add another max pooling, output shape = 9 * 4 * 4
        self.fc1 = nn.Linear(9*4*4, 100)
        self.fc2 = nn.Linear(100, 50)
        # last fully connected layer output should be same as classes
        self.fc3 = nn.Linear(50, 10)

        
    def forward(self, x):
        # first conv
        x = self.pool(F.relu(self.conv1(x)))
        # second conv
        x = self.pool(F.relu(self.conv2(x)))
        # flatten all dimensions except batch
        x = torch.flatten(x, 1)
        # fully connected layers
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)

        return x


model = ConvNet()
  • 我們回顧一下當初解釋 CNN 結構的部分,首先是最前面的兩層卷積+池化層,他們會對圖片做特徵的擷取,也會縮小圖片資料大小
  • 那這邊要注意幾個部分就是我們有提到的參數,
    • in_channels 在最前面一定要符合圖片的特徵,也就是有幾通道的圖片顏色
    • 在接到 fully connected 的 NN 時,要注意已經縮放的 image size,否則神經元數量錯誤,會無法訓練
    • 最後一個注意的部分就是輸出了,輸出的神經元一定要跟目標數量相同
  • 上述注意事項也是為甚麼我們會將 CNN 撰寫成這樣結構的原因
  • 那我們有資料了,也有模型了,就讓我們來實際訓練吧~

正式訓練開始~

  • 我們一樣會從第一步開始,一路操作到對後一步,那今天我們會盡量地去融合所有學習過的操作
  • 那基本的 import 就不特別說了,我們這邊就飛過所有會用到的 import
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
import torchvision
import torchvision.transforms as transforms

cuda usage

  • 那首先我們要善用我們的電腦資源,因此我們來看看電腦是否可以使用 cuda 吧~
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  • 這樣短短的一句話就可以麻煩程式幫我們看看有沒有辦法使用到 cuda 來加速計算啦~

hyper-parameters

  • 我們在訓練過程會用到一些基本參數,例如我們的訓練 epochs、batch_size 等等,就讓我們宣告一下常用的參數吧~
# Hyper-parameters
num_epochs = 4
batch_size = 10
learning_rate = 0.001

資料讀取

  • 這邊我們就直接使用 Pytorch 精心包裝的資料了,我們前面解釋過很多次了,這邊就直接上 code
# 0) data import and set as pytorch dataset
# MNIST
train_dataset = torchvision.datasets.MNIST(root='./data', train=True,
    transform=transforms.ToTensor(), download=False)

test_dataset = torchvision.datasets.MNIST(root='./data', train=False,
    transform=transforms.ToTensor(), download=False)

train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,
    shuffle=True)

test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size,
    shuffle=True)

Model Build

  • 我們有資料了,開始讓我們建立模型吧~
# 1) model build
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
        # image shape is 1 * 28 * 28, where 1 is one color channel
        # 28 * 28 is the image size
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=3, kernel_size=5)    # output shape = 3 * 24 * 24
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)                       # output shape = 3 * 12 * 12
        # intput shape is 3 * 12 * 12
        self.conv2 = nn.Conv2d(in_channels=3, out_channels=9, kernel_size=5)    # output shape = 9 * 8 * 8
        # add another max pooling, output shape = 9 * 4 * 4
        self.fc1 = nn.Linear(9*4*4, 100)
        self.fc2 = nn.Linear(100, 50)
        # last fully connected layer output should be same as classes
        self.fc3 = nn.Linear(50, 10)

        
    def forward(self, x):
        # first conv
        x = self.pool(F.relu(self.conv1(x)))
        # second conv
        x = self.pool(F.relu(self.conv2(x)))
        # flatten all dimensions except batch
        x = torch.flatten(x, 1)
        # fully connected layers
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)

        return x


model = ConvNet().to(device)
  • 那這邊要特別注意,我們如果需要 Model 特別使用哪一種硬體做運算,我們必須將模型傳遞到硬體上面,例如說我們要利用 cuda,就必須將 model 丟到 gpu 上,那要特別注意總共要丟到同一個環境的東西有,
    • model
    • features
    • labels
  • 所以我們晚點會看到總共有三個部分都會需要加 .to(device)

Loss and Optimizer

  • 這邊大家也看很多次了,就宣告適合的 Loss function 跟 Optimizer
# 2) loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

Start our training

  • 這邊也跟前面沒太多差別,唯一要注意就是提到的要將資料送正確的設備上
# 3) Training loop
n_total_steps = len(train_loader)
for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        images = images.to(device)
        labels = labels.to(device)
        
        # init optimizer
        optimizer.zero_grad()
        
        # forward -> backward -> update
        outputs = model(images)
        loss = criterion(outputs, labels)
        
        loss.backward()

        optimizer.step()

        if (i + 1) % 1000 == 0:
            print(f'epoch {epoch+1}/{num_epochs}, step {i+1}/{n_total_steps}, loss = {loss.item():.4f}')

print('Finished Training')

Testing

  • 這邊我們稍微更加細分我們的資料判斷狀況,所以寫的比較複雜
# 4) Testing loop
with torch.no_grad():
    n_correct = 0
    n_samples = 0
    n_class_correct = [0 for i in range(10)]
    n_class_samples = [0 for i in range(10)]
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)
        # max returns (value, index)
        _, predicted = torch.max(outputs, 1)
        n_samples += labels.size(0)
        n_correct += (predicted == labels).sum().item()

        for i in range(batch_size):
            label = labels[i]
            pred = predicted[i]
            if (label == pred):
                n_class_correct[label] += 1
            n_class_samples[label] += 1

    acc = 100.0 * n_correct / n_samples
    print(f'Accuracy of the network: {acc} %')

    for i in range(10):
        acc = 100.0 * n_class_correct[i] / n_class_samples[i]
        print(f'Accuracy of {i}: {acc} %')
  • 完成收工~

每日小結

  • 我們成功了~ 我們成功撰寫屬於自己的第一個 Pytorch 手寫辨識模型了~ 歷經了整整 27 天我們總算利用所有學到的概念和知識,撰寫出屬於我們自己的手寫辨識 CNN 模型~
  • 那這邊筆者就留個小問題給大家,這個模型是否有辦法更加精準呢?或是整個撰寫有沒有任何能優化的部分?這些就留給讀者們自己去鑽研了~筆者到這裡也算是功成身退了
  • 那到今天我們已經成功成為一個 Pytorch Framework 的使用者了,如何更靈活的運用他們,就是需要讀者們努力的部分,但最後的最後,我們還是要準備個完美收尾,所以明天我們來聊聊除了 CNN 以外,還有哪些選擇呢?

上一篇
Day-26 手把手的手寫辨識模型 0x1:資料集整理
下一篇
Day-28 手把手的手寫辨識模型 0x3:CNN is the end?模型大哉問
系列文
Deep Learning 從零開始到放棄的 30 天 PyTorch 數字辨識模型31

1 則留言

0
juck30808
iT邦新手 3 級 ‧ 2021-10-12 18:36:25

恭喜大大即將完賽XD !!!

CrazyFire iT邦新手 5 級 ‧ 2021-10-14 00:53:45 檢舉

嗚嗚嗚,結果我忘記發了 QQ

我要留言

立即登入留言