- 我們昨天成功拿到資料了,今天就要開始訓練模型了
- 那由於我們當初在介紹 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 上,那要特別注意總共要丟到同一個環境的東西有,
- 所以我們晚點會看到總共有三個部分都會需要加
.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 以外,還有哪些選擇呢?