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 29:K-近邻演算法(k-nearest neighbors)

>>:  Day 27:语系包在 i 身上-Vue I18n 前置作业

[GWS] 服务简单做-RESTful的开发方式

在Genero FGL上也可以做出 RESTful 的 WEB Service。 先将回应WEB R...

30天零负担轻松学会制作APP介面及设计【DAY 01】

大家好,我是YIYI,今天是我开始铁人赛的第一天。请大家多多指教! 为什麽会想自己制作APP? 会想...

GitHub 上讨论议题 - 建立第一个 Issue 与自订 Labels

GitHub Issue 有点像是专案管理系统内管理工作事项的功能,但它能达到功能更多:无论是个人或...

[Day01] 写给现在与将来的主管

我相信,很少人是做好准备才当上主管。通常是凭自己的技术过硬、绩效超群而被赋予领导职,然後开始学管理。...

如果我是主力,我会怎样割韭菜

所谓的主力,就是有绝对多的资金,或是大量持有某一档股票,最大的优势就是容易操控股票价格。 今年一堆散...