ใน ep ที่แล้ว Neural Network ep.13 ที่เราได้สร้างโมเดล Deep Neural Network ที่ใช้ Linear Layer + ReLU Activation Function เราได้สร้าง Training Loop ที่มีความ Flexible จาก Callback ทำให้เราสามารถ Schedule Hyperparameter ได้ตามต้องการ

แต่ไม่ว่าจะเทรนอย่างไร เราก็จำแนก MNIST ได้ Accuracy สูงสุดแค่ 97% เท่านั้น เนื่องจากข้อจำกัดของ Model Architecture แล้วเราจะแก้ปัญหานี้อย่างไรดี

Convolutional Neural Network คืออะไร

Convolutional Neural Network คือ Neural Network แบบหนึ่งที่มักถูกนำมาใช้ในงาน Computer Vision หรือ วิเคราะห์รูปภาพ เช่น Image Classification จำแนกรูปภาพ, Object Detection ตรวจจับวัตถุ, Face Recognition เรียนรู้จดจำใบหน้า, etc.

Convolutional Neural Network ทำงานอย่างไร

Typical CNN architecture Credit https://en.wikipedia.org/wiki/File:Typical_cnn.png
Typical CNN architecture Credit https://en.wikipedia.org/wiki/File:Typical_cnn.png

Convolutional Neural Network คือ Deep Learning อัลกอริทึม ที่จะรับ Input เป็นรูปภาพ แล้วเรียนรู้ Feature ต่าง ๆ ของรูปภาพเหล่านั้น ในแต่ละ Layer ต่อยอดขึ้นไปเรื่อย ๆ ตั้งแต่ จุด, เส้นแนวตั้ง, เส้นแนวนอน, เส้นแนวทแยง, กากบาท, มุม, เส้นโค้ง, วงกลม, พื้นผิว, ลวดลาย, ดวงตา, ใบหน้า, … ไปจนถึง วัตถุที่เรากำหนด

Input Image

MNIST Data Number 8 in Grayscale Pixel Value 0-1
MNIST Data Number 8 in Grayscale Pixel Value 0-1

รูปภาพที่อยู่ในคอมพิวเตอร์ ในกรณีรูปขาวดำ ก็คือ ข้อมูลตัวเลขความสว่าง 0-255 ที่นำมาจัดเรียงเป็น Matrix กว้าง x ยาว (Width x Height) จากตัวอย่าง MNIST ด้านบน ถ้าเป็นรูปสี ก็จะเป็น 3 Channel RGB กว้าง x ยาว x ลึก (Widgh x Height x Depth)

ConvLayer – Kernel/Filter

Convolution Layer (ConvLayer) คือ Layer ที่อยู่แรก ๆ ของโมเดล CNN, ConvLayer ทำหน้าที่สกัดเอา Feature สำคัญ จากรูปภาพ, ConvLayer มีความพิเศษตรงที่ คงความสัมพันธ์ของ Pixel ที่อยู่บริเวณพื้นที่ใกล้เคียงกันเอาไว้ด้วย

A filter (=kernel, neuron) in a convolutional artificial neural network. The input to the filter is three features thick. The three features come from three separate filters in the previous layer of the deep neural network. Credit https://commons.wikimedia.org/wiki/File:Convolutional_Neural_Network_NeuralNetworkFeatureLayers.gif
A filter (=kernel, neuron) in a convolutional artificial neural network. The input to the filter is three features thick. The three features come from three separate filters in the previous layer of the deep neural network. Credit https://commons.wikimedia.org/wiki/File:Convolutional_Neural_Network_NeuralNetworkFeatureLayers.gif

เปรียบเทียบกับการทำงานของ Neural Network ธรรมดาที่เชื่อมทุก ๆ Neuron ของ Layer ก่อนหน้าเข้าด้วยกัน แต่ ConvLayer เลือกที่จะเชื่อมแค่บริเวณที่ต้องการ เรียกว่า Receptive Field

การทำ Convolution รูปด้วย Filter ที่แตกต่างกัน จะได้ความหมายที่แตกต่างกันไป เช่น หาขอบรูป, หาความเบลอ, หาความคม เริ่มต้นที่ Layer แรก เส้นตรง เส้นโค้ง ไปถึง Layer หลัง ๆ ก็จะนามธรรม (Abstract) ขึ้นเรื่อย ๆ

A 3x3 convolution of depth 1 performed over a 5x5 input feature map, also of depth 1. Credit https://developers.google.com/machine-learning/practica/image-classification/convolutional-neural-networks
A 3×3 convolution of depth 1 performed over a 5×5 input feature map, also of depth 1. Credit https://developers.google.com/machine-learning/practica/image-classification/convolutional-neural-networks

Filter (สีชมพู) จะมีขนาดเท่าไร (Kernel Size) วิ่งทีละกี่ช่อง (Stride) เป็น Hyperparameter ที่เราต้องกำหนด ในแต่ละ Layer

Filter หน้าตาเป็นอย่างไร เป็น Parameter ที่โมเดลจะต้องเรียนรู้ ส่วน Output ของการ Convolution (สีเขียว) เรียกว่า Activation Map หรือ Feature Map

Receptive Field

Neurons of a convolutional layer (blue), connected to their receptive field (red). Credit https://en.wikipedia.org/wiki/File:Conv_layer.png
Neurons of a convolutional layer (blue), connected to their receptive field (red). Credit https://en.wikipedia.org/wiki/File:Conv_layer.png

สถาปัตยกรรม Model Architecture ของ ConvNet ได้แรงบันดาลใจมาจาก ระบบประสาทการมองเห็น Visual Cortex ในสมองของมนุษย์ แต่ละ Neuron จะรับผิดชอบพื้นที่จำกัดของตัวเอง เรียกว่า Receptive Field โดยที่หลาย ๆ Neuron ใกล้เคียงกันจะช่วยกันดูแลพื้นที่ใกล้เคียงคาบเกี่ยวกัน จนครอบคลุมหมดทั้งพื้นที่

Custom Head

The activations of an example ConvNet architecture. The initial volume stores the raw image pixels (left) and the last volume stores the class scores (right).  Credit http://cs231n.github.io/convolutional-networks/
The activations of an example ConvNet architecture. The initial volume stores the raw image pixels (left) and the last volume stores the class scores (right). Credit http://cs231n.github.io/convolutional-networks/

หลังจากที่ผ่านหลาย ๆ Convolutional Layer มา โมเดลส่วนใหญ่จะจบด้วย Fully Connected Layer เพื่อปรับ Output ของโมเดลให้ออกมาตรงกับงานที่เราต้องการ เช่น Classification 10 Class เราก็จะใช้ Fully Connected Layer ที่มี Input = Output ของ ConvLayer ท้ายสุด Flatten, Output = 10 แล้วต่อด้วย Softmax หรือ Sigmoid Activation Function

เรามาเริ่มกันเลยดีกว่า

ดังนั้นใน ep นี้เราจะปรับ Model Architecture จาก Linear Layer ธรรมดา กลายเป็น Conv2d Layer ทำให้โมเดลของเรากลายเป็น Convolutional Neural Network (CNN) ซึ่งเป็น Neural Network ชนิดหนึ่ง ที่เหมาะงาน Computer Vision

การเลือกว่าจะใช้ Model Architecture ไหน ก็ถือเป็น Hyperparameter เช่นกัน เราจะเริ่มต้นที่หัวข้อ 4. Model

Open In Colab

จากใน ep ก่อน ที่เราได้สร้างโมเดล Deep Neural Network ที่ใช้ Linear Layer + ReLU Activation Function เราได้สร้าง Training Loop ที่มีความ Flexible จาก Callback ทำให้เราสามารถ Schedule Hyperparameter ได้ตามต้องการ

แต่ไม่ว่าจะเทรนอย่างไร เราก็เทรน MNIST ได้ Accuracy สูงสุดแค่ 97% เท่านั้น เนื่องจากข้อจำกัดของ Model Architecture

ดังนั้นใน ep นี้เราจะปรับ Model Architecture เป็น Convolutional Neural Network (CNN) ที่เหมาะงาน Computer Vision การเลือกว่าจะใช้ Model Architecture ไหน ก็ถือเป็น Hyperparameter เช่นกัน เราจะเริ่มต้นที่หัวข้อ 4. Model

0. Magic

In [0]:
%load_ext autoreload
%autoreload 2
%matplotlib inline

1. Import

In [0]:
import torch
from torch import tensor
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import *
import fastai
from fastai import datasets
from fastai.metrics import accuracy
from fastai.basic_data import *
from fastai.basic_train import *
import pickle, gzip, math, torch, re
from IPython.core.debugger import set_trace
import matplotlib.pyplot as plt
from functools import partial

2. Data

In [0]:
class Dataset(Dataset):
    def __init__(self, x, y, c):
        self.x, self.y, self.c = x, y, c
    def __len__(self):
        return len(self.x)
    def __getitem__(self, i):
        return self.x[i], self.y[i]
In [0]:
MNIST_URL='http://deeplearning.net/data/mnist/mnist.pkl'
In [0]:
def get_data():
    path = datasets.download_data(MNIST_URL, ext='.gz')
    with gzip.open(path, 'rb') as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding='latin-1')
    return map(tensor, (x_train, y_train, x_valid, y_valid))
In [0]:
x_train, y_train, x_valid, y_valid = get_data()

3. Data Preprocessing

In [0]:
def normalize(x, m, s): 
    return (x-m)/s
In [0]:
from typing import *

def listify(o):
    if o is None: return []
    if isinstance(o, list): return o
    if isinstance(o, str): return [o]
    if isinstance(o, Iterable): return list(o)
    return [o]
In [0]:
_camel_re1 = re.compile('(.)([A-Z][a-z]+)')
_camel_re2 = re.compile('([a-z0-9])([A-Z])')

def camel2snake(name):
    s1 = re.sub(_camel_re1, r'\1_\2', name)
    return re.sub(_camel_re2, r'\1_\2', s1).lower()   
In [0]:
train_mean, train_std = x_train.mean(), x_train.std()
x_train = normalize(x_train, train_mean, train_std)
x_valid = normalize(x_valid, train_mean, train_std)
In [0]:
bs = 128
n, m = x_train.shape
c = (y_train.max()+1).numpy()
loss_func = F.cross_entropy
In [0]:
train_ds, valid_ds = Dataset(x_train, y_train, c), Dataset(x_valid, y_valid, c)
# train_dl, valid_dl = DataLoader(train_ds, bs), DataLoader(valid_ds, bs)
In [0]:
data = DataBunch.create(train_ds, valid_ds, bs=bs, num_workers=8, device=torch.device('cpu'))

4. Model

เราจะเปลี่ยน Layer ในโมเดลจาก Linear เป็น Convolutional Neural Network (CNN) และปิดหัวท้ายด้วย Lambda Layer สำหรับแปลงมิติข้อมูล

In [0]:
def get_cnn_model(data):
    return nn.Sequential(
        # Lambda(mnist_resize),  # use BatchTransformXCallback instead.
        nn.Conv2d(  1,  8, 5, padding=2, stride=2), nn.ReLU(), #14
        nn.Conv2d(  8, 16, 3, padding=1, stride=2), nn.ReLU(), # 7
        nn.Conv2d( 16, 32, 3, padding=1, stride=2), nn.ReLU(), # 4
        nn.Conv2d( 32, 32, 3, padding=1, stride=2), nn.ReLU(), # 2
        nn.AdaptiveAvgPool2d(1), 
        Lambda(flatten), 
        nn.Linear(32, data.c)
    )

Lambda Layer สำหรับใส่ฟังก์ชันต่าง ๆ เป็น Layer ใน Neural Network

In [0]:
class Lambda(nn.Module):
    def __init__(self, func):
        super().__init__()
        self.func = func
    def forward(self, x): return self.func(x)

ฟังก์ชันแปลง MNIST เป็น 28x28 และ Flatten ให้ Activation ที่ออกจาก Conv2d กลายเป็น Fully-Connected Layer

In [0]:
def flatten(x): return x.view(x.shape[0], -1)
# def mnist_resize(x): return x.view(-1, 1, 28, 28) # use BatchTransformXCallback instead.

5. Training Loop

แล้วเราจะเทรนตามปกติ

In [0]:
class Runner():
    def __init__(self, cbs=None, cb_funcs=None):
        cbs = listify(cbs)
        for cbf in listify(cb_funcs):
            cb = cbf()
            setattr(self, cb.name, cb)
            cbs.append(cb)
        self.stop, self.cbs = False, [TrainEvalCallback()]+cbs

    @property
    def opt_func(self):     return self.learn.opt_func
    @property
    def model(self):        return self.learn.model
    @property
    def loss_func(self):    return self.learn.loss_func
    @property
    def data(self):         return self.learn.data

    def one_batch(self, xb, yb):
        try: 
            self.xb, self.yb = xb, yb
            self('begin_batch')
            self.pred = self.model(self.xb)
            self('after_pred')
            self.loss = self.loss_func(self.pred, self.yb)
            self('after_loss')
            if not self.in_train: return
            self.loss.backward()
            self('after_backward')
            self.opt_func.step()
            self('after_step')
            self.opt_func.zero_grad()
        except CancelBatchException: self('after_cancel_batch')
        finally: self('after_batch')
    
    def all_batches(self, dl):
        self.iters = len(dl)
        try:
            for xb, yb in dl:
                self.one_batch(xb, yb)
        except CancelEpochException: self('after_cancel_epoch')
    
    def fit(self, epochs, learn):
        self.epochs, self.learn, self.loss = epochs, learn, tensor(0.)

        try:
            for cb in self.cbs: cb.set_runner(self)
            self('begin_fit')
            for epoch in range(epochs):
                self.epoch = epoch
                if not self('begin_epoch'): self.all_batches(self.data.train_dl)

                with torch.no_grad():
                    if not self('begin_validate'): self.all_batches(self.data.valid_dl)
                self('after_epoch')
        except CancelTrainException: self('after_cancel_train')
        finally: 
            self('after_fit')
            self.train = None

    def __call__(self, cb_name):
        # return True = Cancel, return False = Continue (Default)
        res = False
        # check if at least one True return True
        for cb in sorted(self.cbs, key=lambda x: x._order): res = res or cb(cb_name)
        return res        

6. Callbacks

In [0]:
class Callback():
    _order = 0
    def set_runner(self, run): self.run = run
    def __getattr__(self, k): return getattr(self.run, k)

    @property
    def name(self):
        name = re.sub(r'Callback$', '', self.__class__.__name__)
        return camel2snake(name or 'callback')
    
    def __call__(self, cb_name):
        f = getattr(self, cb_name, None)
        if f and f(): return True
        return False

class TrainEvalCallback(Callback):
    def begin_fit(self):
        self.run.n_epochs = 0.
        self.run.n_iter = 0
    
    def begin_epoch(self):
        self.run.n_epochs = self.epoch  
        self.model.train()
        self.run.in_train=True

    def after_batch(self):
        if not self.in_train: return
        self.run.n_epochs += 1./self.iters
        self.run.n_iter += 1

    def begin_validate(self):
        self.model.eval()
        self.run.in_train=False    
           
class CancelTrainException(Exception): pass
class CancelEpochException(Exception): pass
class CancelBatchException(Exception): pass
In [0]:
class Recorder(Callback):
    def begin_fit(self): 
        self.lrs = [[] for _ in self.opt_func.param_groups]
        self.losses = []

    def after_batch(self):
        if not self.in_train: return
        for pg, lr in zip(self.opt_func.param_groups, self.lrs): lr.append(pg['lr'])
        self.losses.append(self.loss.detach().cpu())
    
    def plot_lr(self, pgid=-1): plt.plot(self.lrs[pgid])
    def plot_loss(self, skip_last=0): plt.plot(self.losses[:len(self.losses)-skip_last])
    def plot(self, skip_last=0, pgid=-1):
        losses = [o.item() for o in self.losses]
        lrs = self.lrs[pgid]
        n = len(losses)-skip_last
        plt.xscale('log')
        plt.plot(lrs[:n], losses[:n])
In [0]:
class AvgStatsCallback(Callback):
    def __init__(self, metrics):
        self.train_stats, self.valid_stats = AvgStats(metrics, True), AvgStats(metrics, False)
    def begin_epoch(self):
        self.train_stats.reset()
        self.valid_stats.reset()
    
    def after_loss(self):
        stats = self.train_stats if self.in_train else self.valid_stats
        with torch.no_grad(): stats.accumulate(self.run)

    def after_epoch(self):
        print(self.train_stats)
        print(self.valid_stats)

class AvgStats():
    def __init__(self, metrics, in_train): 
        self.metrics, self.in_train = listify(metrics), in_train
    
    def reset(self):
        self.tot_loss, self.count = 0., 0
        self.tot_mets = [0.] * len(self.metrics)

    @property
    def all_stats(self): return [self.tot_loss.item()] + self.tot_mets
    @property
    def avg_stats(self): return [o/self.count for o in self.all_stats]

    def __repr__(self):
        if not self.count: return ""
        return f"{'train' if self.in_train else 'valid'}: {self.avg_stats}"

    def accumulate(self, run):
        bn = run.xb.shape[0]
        self.tot_loss += run.loss * bn
        self.count += bn
        
        for i, m in enumerate(self.metrics):
            self.tot_mets[i] += m(run.pred, run.yb) * bn
In [0]:
class ParamScheduler(Callback):
    _order = 1
    def __init__(self, pname, sched_funcs): self.pname, self.sched_funcs = pname, sched_funcs

    def begin_fit(self): 
        if not isinstance(self.sched_funcs, (list, tuple)): 
            self.sched_funcs = [self.sched_funcs] * len(self.opt_func.param_groups)

    def set_param(self):
        assert len(self.opt_func.param_groups) == len(self.sched_funcs)
        for pg, f in zip(self.opt_func.param_groups, self.sched_funcs):
            pg[self.pname] = f(self.n_epochs/self.epochs)

    def begin_batch(self):
        if self.in_train: self.set_param()
In [0]:
def annealer(f):
    def _inner(start, end): return partial(f, start, end)
    return _inner 
In [0]:
@annealer
def sched_lin(start, end, pos): return start + pos * (end - start)
@annealer
def sched_cos(start, end, pos): return start + (1 + math.cos(math.pi*(1-pos))) * (end-start) / 2
In [0]:
torch.Tensor.ndim = property(lambda x: len(x.shape))
In [0]:
def combine_scheds(pcts, scheds):
    assert sum(pcts) == 1.
    pcts = tensor([0] + listify(pcts))
    assert torch.all(pcts >= 0)
    pcts = torch.cumsum(pcts, 0)

    def _inner(pos):
        idx = (pos >= pcts).nonzero().max()
        actual_pos = (pos-pcts[idx]) / (pcts[idx+1]-pcts[idx])
        return scheds[idx](actual_pos)
    return _inner    
In [0]:
max_lr = 3e-1
sched = combine_scheds([0.3, 0.7], [sched_cos(3e-3, max_lr), sched_cos(max_lr, 3e-4)])

เพื่อความยืดหยุ่น เราจะใช้ Callback ในการแปลง MNIST ที่มาในรูปแบบ Matrix 2 มิติ ให้เป็น 4 มิติตามที่โมเดลต้องการ แทนที่จะ Fix เป็น Layer ภายในโมเดล

In [0]:
class BatchTransformXCallback(Callback):
    _order = 2
    def __init__(self, tfm): self.tfm = tfm
    def begin_batch(self): 
        # set_trace()
        self.run.xb = self.tfm(self.xb)

def view_tfm(*size):
    # set_trace()
    def _inner(x): return x.view(*(-1, )+size)
    return _inner
In [0]:
mnist_view = view_tfm(1, 28, 28)
In [0]:
cbfs = [Recorder, partial(ParamScheduler, 'lr', sched), partial(BatchTransformXCallback, mnist_view), partial(AvgStatsCallback, accuracy)]

7. Train

เริ่มต้นเทรน ด้วย SGD

In [0]:
fastai.torch_core.defaults.device = 'cpu'
In [0]:
model = get_cnn_model(data)

opt = torch.optim.SGD(model.parameters(), lr=max_lr)
learn = Learner(data, model, opt, loss_func=loss_func)

run = Runner(cb_funcs=cbfs)

เทรนไป 8 Epoch

In [32]:
%time run.fit(8, learn)
train: [2.284226481119792, tensor(0.1432)]
valid: [1.653177734375, tensor(0.5677)]
train: [0.4726697872846555, tensor(0.8556)]
valid: [0.1794595703125, tensor(0.9435)]
train: [0.14680708860739683, tensor(0.9555)]
valid: [0.1167708251953125, tensor(0.9641)]
train: [0.092237795316256, tensor(0.9712)]
valid: [0.09356160888671874, tensor(0.9730)]
train: [0.06738131596491886, tensor(0.9791)]
valid: [0.07083306884765625, tensor(0.9788)]
train: [0.05029083154140375, tensor(0.9847)]
valid: [0.06807051391601562, tensor(0.9810)]
train: [0.0395234743754069, tensor(0.9882)]
valid: [0.061148431396484376, tensor(0.9823)]
train: [0.03355224805000501, tensor(0.9904)]
valid: [0.06052144775390625, tensor(0.9826)]
CPU times: user 1min 3s, sys: 8.1 s, total: 1min 11s
Wall time: 1min 8s

เทรนเสร็จเรียบร้อย ได้ Accuracy 98-99% เปรียบเทียบกับโมเดลที่ใช้ Linear Layer อย่างเดียว ที่ได้ 97% แต่ใช้เวลาค่อนข้างนาน คือประมาณ 14 นาที

เราจะใช้ GPU มาเร่งความเร็ว ในการเทรนโมเดล เพื่อลดเวลาที่ใช้ตรงนี้ลง

7/2. Train with CUDA GPU

Cuda คือ API สำหรับการประมวลผลแบบขนาน บน GPU ยี่ห้อ Nvidia

ใน event begin_fit เราจะย้าย Model (Parameter) ไปยัง GPU Memory และ begin_batch ก็จะย้าย Data Batch นั้น ๆ ขึ้น GPU Memory

In [0]:
device = torch.device('cuda',0)
torch.cuda.set_device(device)
fastai.torch_core.defaults.device = device

class CudaCallback(Callback):
    def begin_fit(self): self.model.cuda()
    def begin_batch(self): self.run.xb, self.run.yb = self.xb.cuda(), self.yb.cuda()

เพิ่ม CudaCallback เข้าไปให้โมเดล

In [0]:
cbfs.append(CudaCallback)
In [0]:
data = DataBunch.create(train_ds, valid_ds, bs=bs, num_workers=8, device=device)

model = get_cnn_model(data)

opt = torch.optim.SGD(model.parameters(), lr=max_lr)
learn = Learner(data, model, opt, loss_func=loss_func)

run = Runner(cb_funcs=cbfs, cbs=AvgStatsCallback([accuracy]))
In [36]:
%time run.fit(8, learn)
train: [2.223411051432292, tensor(0.1865, device='cuda:0')]
valid: [0.96980546875, tensor(0.7074, device='cuda:0')]
train: [2.223411051432292, tensor(0.1865, device='cuda:0')]
valid: [0.96980546875, tensor(0.7074, device='cuda:0')]
train: [0.4088633610652043, tensor(0.8762, device='cuda:0')]
valid: [0.1636568115234375, tensor(0.9528, device='cuda:0')]
train: [0.4088633610652043, tensor(0.8762, device='cuda:0')]
valid: [0.1636568115234375, tensor(0.9528, device='cuda:0')]
train: [0.1448400155091897, tensor(0.9563, device='cuda:0')]
valid: [0.10071043701171875, tensor(0.9695, device='cuda:0')]
train: [0.1448400155091897, tensor(0.9563, device='cuda:0')]
valid: [0.10071043701171875, tensor(0.9695, device='cuda:0')]
train: [0.08943829658703926, tensor(0.9721, device='cuda:0')]
valid: [0.083989990234375, tensor(0.9757, device='cuda:0')]
train: [0.08943829658703926, tensor(0.9721, device='cuda:0')]
valid: [0.083989990234375, tensor(0.9757, device='cuda:0')]
train: [0.06471950335380358, tensor(0.9803, device='cuda:0')]
valid: [0.06918075561523437, tensor(0.9797, device='cuda:0')]
train: [0.06471950335380358, tensor(0.9803, device='cuda:0')]
valid: [0.06918075561523437, tensor(0.9797, device='cuda:0')]
train: [0.047968482971191405, tensor(0.9848, device='cuda:0')]
valid: [0.06335953979492187, tensor(0.9824, device='cuda:0')]
train: [0.047968482971191405, tensor(0.9848, device='cuda:0')]
valid: [0.06335953979492187, tensor(0.9824, device='cuda:0')]
train: [0.036812816522060296, tensor(0.9887, device='cuda:0')]
valid: [0.059902166748046874, tensor(0.9828, device='cuda:0')]
train: [0.036812816522060296, tensor(0.9887, device='cuda:0')]
valid: [0.059902166748046874, tensor(0.9828, device='cuda:0')]
train: [0.031753831031994945, tensor(0.9906, device='cuda:0')]
valid: [0.0595421142578125, tensor(0.9837, device='cuda:0')]
train: [0.031753831031994945, tensor(0.9906, device='cuda:0')]
valid: [0.0595421142578125, tensor(0.9837, device='cuda:0')]
CPU times: user 32.3 s, sys: 7.6 s, total: 39.9 s
Wall time: 36 s

ใช้เวลาเหลือแค่ 3 นาทีเท่านั้น ถ้ายิ่งโมเดลใหญ่ขึ้น ซับซ้อนยิ่งขึ้น เวลาที่ใช้ก็จะยิ่งแตกต่างกันเข้าไปอีก ดังตัวอย่าง ทำไม GPU ถึงจำเป็นต่อ Deep Learning เปรียบเทียบ CPU vs GPU เทรน Deep Neural Network - Hardware ep.2

8. Interpret

เราลองพล็อตกราฟ Learning Rate จะเห็นว่ามีการเพิ่มลด แบบ One Cycle Scheduled Learning Rate

In [37]:
run.recorder.plot_lr()

พล็อตกราฟ Cross Entrophy Loss จะเห็นว่าลดลงอย่างรวดเร็ว

In [38]:
run.recorder.plot_loss()

9. สรุป

  • การเลือก Model Architecture ที่เหมาะสม จะมีผลอย่างมาก ต่อประสิทธิภาพการทำงานของโมเดลโดยรวม เป็น 1 ใน Hyperparameter ที่สำคัญที่สุด
  • โมเดลยิ่งซับซ้อน ก็ต้องใช้เวลา ใช้ทรัพยากร ในการเทรนมากยิ่งขึ้น ทำให้จำเป็นต้องใช้ GPU จะเข้ามาช่วยแก้ปัญหานี้ ดังตัวอย่าง ทำไม GPU ถึงจำเป็นต่อ Deep Learning เปรียบเทียบ CPU vs GPU เทรน Deep Neural Network - Hardware ep.2
  • ในการใช้งาน CNN เราต้องเลือก Hyperparameter อีกหลายตัว เช่น จำนวน Layer, จำนวน Channel, ขนาด Kernel, Stride, Padding, etc. จะอธิบายต่อไป

Credit

In [0]:
 

แชร์ให้เพื่อน:

Keng Surapong on FacebookKeng Surapong on GithubKeng Surapong on Linkedin
Keng Surapong
Data Science Manager at Bua Labs
The ultimate test of your knowledge is your capacity to convey it to another.

Published by Keng Surapong

The ultimate test of your knowledge is your capacity to convey it to another.