มาสร้าง AlexNet โดยใช้ PyTorch กัน
บทความนี้เป็นบทความต่อเนื่อง ในซีรีย์ “สร้างโมเดล โดย PyTorch” ซึ่งบทความก่อนหน้านี้ ก็ได้ทดลองสร้าง LeNet5 กันไป สำหรับในบทความนี้เราจะสร้าง AlexNet ซึ่งเป็นหนึ่งในโมเดลที่สร้างความฮือฮาในวงการอย่างมาก ตอนที่เปิดตัวโมเดลนี้ออกมา AlexNet ถือได้ว่าเป็นโมเดลที่ “ก้าวหน้า” ที่นำไปสู่ยุคใหม่ของ deep learning
เราจะเริ่มด้วยการสำรวจและทำความเข้าใจกับสถาปัตยกรรมของ AlexNet จากนั้นก็จะเริ่มเขียนโปรแกรมกัน โดยบทความนี้จะยังคงใช้ dataset CIFAR10 เพื่อจะได้เปรียบเทียบความสามารถเมื่อเทียบกับ LeNet5 ในบทความที่ผ่านมา แต่ในครั้งนี้เราจะมีการทำ pre-processing กับข้อมูลเพิ่มเติมด้วย
AlexNet
AlexNet เป็น deep convolutional neural network ซึ่งมีจุดเริ่มต้นจากการพัฒนาของ Alex Krizhevsky และคณะ ในปี 2012 โดยออกแบบมาเพื่อใช้ในการแยกภาพใน dataset ที่ชื่อว่า ImageNet ซึ่งเป็น dataset ที่มีชื่อเสียงมาก เนื่องจากมีภาพมากถึง 1.4 ล้านภาพ โดยแบ่งออกเป็น class มากถึง 1000 class และมีการจัดการแข่งขัน ImageNet LSVRC-2010 competition ซึ่งจัดขึ้นระหว่างปี 2010–2017 โดย AlexNet เป็นผู้ชนะในปี 2012 หากสนใจจะอ่าน paper รายละเอียดของ AlexNet สามารถอ่านได้จาก ที่นี่
รายละเอียดของสถาปัตยกรรมดั้งเดิมของ AlexNet มีดังภาพข้างต้น โดยจะรับภาพที่มีขนาด 227x227 จำนวน 3 channel เข้ามาและมีการประมวลผลดังนี้
- ชั้นที่ 1.1 ทำ Convolution ด้วย filter ขนาด 11x11 โดยใช้ stride = 4 จำนวน 96 แผ่น ซึ่งเป็นผลให้ output มีขนาด 55x55x96 ((227–11)/4+1)
- ชั้นที่ 1.2 ทำ max pooling แบบ overlap ขนาด 3x3 โดยใช้ stride 2 จะลดขนาดจาก 55x55 เหลือ 27x27 โดยมี feature map จำนวน 96 แผ่นเท่าเดิม
- ชั้นที่ 2.1 ทำ Convolution ด้วย filter ขนาด 5x5 จำนวน 256 แผ่น โดยมี padding=2 และ stride=1 จึงได้ feature map ขนาด 27x27 เท่าเดิม แต่มีจำนวนแผ่นเพิ่มเป็น 256
- ชั้นที่ 2.2 ทำ max pooling แบบ overlap ขนาด 3x3 โดยใช้ stride 2 จะลดขนาดจาก 27x27 เหลือ 13x13 โดยมีจำนวนแผ่นเป็น 256 เท่าเดิม
- ชั้นที่ 3 ทำ Convolution ด้วย filter ขนาด 3x3 จำนวน 384 แผ่น โดยมี padding=1 และ stride=1 จึงได้ feature map ขนาด 13x13 เท่าเดิม แต่มีจำนวนแผ่นเพิ่มเป็น 384
- ชั้นที่ 4 ทำ Convolution ด้วย filter ขนาด 3x3 จำนวน 384 แผ่น โดยมี padding=1 และ stride=1 จึงได้ feature map ขนาด 13x13 เท่าเดิม และมีจำนวนแผ่น 384 เท่าเดิม
- ชั้นที่ 5.1 ทำ Convolution ด้วย filter ขนาด 3x3 จำนวน 256 แผ่น โดยมี padding=1 และ stride=1 จึงได้ feature map ขนาด 13x13 เท่าเดิม และมีจำนวนแผ่นลดลงเหลือ 256 แผ่น
- ชั้นที่ 5.2 ทำ max pooling แบบ overlap ขนาด 3x3 โดยใช้ stride 2 จะลดขนาดจาก 13x13 เหลือ 6x6 โดยมีจำนวนแผ่นเป็น 256 เท่าเดิม
- ชั้นที่ 6 นำข้อมูล 6x6x256 = 9216 มา reshape เป็นมิติเดียว และ นำเข้าสู่ fully connected layer ขนาด 4096
- ชั้นที่ 7 นำมาผ่าน fully connected layer ขนาด 4096 อีกครั้ง
- ชั้นที่ 8 นำมาเข้า output layer ซึ่งจะใช้ cross entropy แบ่งเป็น 1,000 class ด้วย softmax
จาก LeNet5 ที่มีจำนวน parameter เพียง 61,000 ตัว แต่ใน AlexNet จำนวน parameter ของโมเดลนี้เพิ่มขึ้นเป็น 60 ล้านตัว โดยมีจำนวน neuron ประมาณ 650,000 ตัว
หากเทียบระหว่าง AlexNet กับ LeNet5 จะเห็นว่า AlexNet มีการใช้ Deep Learning ที่ลึกมากขึ้น (ในสมัยนั้นไม่ค่อยมีคนทำโมเดลที่ใหญ่เพราะทำงานนาน AlexNet ในขณะนั้นใช้ GTX 580 จำนวน 2 การ์ดทำงานถึง 6 วันจึงจะได้ผลลัพธ์) คือ ลึกถึง 8 ชั้น และใช้ neuron มากในแต่ละชั้น เช่น มีการใช้ filter มากที่สุดถึง 384 ชั้น สำหรับจุดเด่นๆ ของโมเดลนี้ได้แก่
- นำ ReLU มาใช้งาน จริงๆ แล้ว Activation Function LeRU มีมานานแล้ว แต่ไม่ค่อยนิยมใช้งาน แต่ใน AlexNet มีการนำมาใช้งานอย่างจริงจัง และทำให้การประมวลผลเร็วขึ้นมาก การเข้าสู่จุดต่ำสุด (convergence) ก็เร็วขึ้นด้วย ทำให้จากนั้นมา ReLU ได้รับความนิยมขึ้นเรื่อยๆ จนกลายเป็น Activation Function หลักในยุคหลังๆ
- มีการนำ Dropout Layer มาใช้เพื่อลด Over-fitting โดยนำมาใช้ในส่วนของ Fully Connected Layer
- มีการนำ Data Augmentation มาช่วยในการลด Over-fitting
- มีการทำ Local Response Normalization (ต่อมามักนิยมใช้เป็น Batch Normalization มากกว่า)
- ทำงานบน Multi GPU
ในโมเดล AlexNet นี้ให้สังเกตุว่าการออกแบบ เขาได้ใช้ Convolution หลากหลายขนาดโดยลดลงเรื่อยๆ จาก 11x11 ลงมาเป็น 5x5 และ 3x3 ตามลำดับ และได้เพิ่มจำนวน filter ใน Convolution Layer มากขึ้น จาก 96 => 256 => 384 => 256 จากนั้นจึงเข้าสู่ Fully Connected Layer ขนาด 4096 จำนวน 2 ชั้น ที่นำมากล่าวถึงเพื่อให้สังเกตเทียบกับสถาปัตยกรรมหลังๆ ว่าแตกต่างกันอย่างไร
Data Loading
Dataset
เราจะเริ่มจากการโหลดข้อมูลภาพ โดยบทความนี้จะใช้ชุดข้อมูล CIFAR-10 ซึ่งประกอบด้วยข้อมูลภาพสี 60,000 ภาพ (RGB) ที่มีขนาด 32x32 จุด โดยแบ่งออกเป็น 10 ประเภท (class) ประเภทละ 6,000 ภาพ โดยจะแบ่งข้อมูลเป็น 2 ส่วน คือ ข้อมูลสำหรับสอนโมเดล จำนวน 50,000 ภาพ และ ข้อมูลสำหรับทดสอบ จำนวน 10,000 ภาพ
ตัวอย่างของภาพ เป็นไปตามภาพด้านล่างนี้
Importing the Libraries
เราจะเริ่มต้นด้วยการ import library ที่จำเป็น ดังต่อไปนี้
import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
from torch.utils.data.sampler import SubsetRandomSampler
# Define relevant variables for the ML task
batch_size = 16
num_classes = 10
learning_rate = 0.0001
num_epochs = 50
# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
ใน library torch และ torch.nn จะเป็น library เกี่ยวกับ neural network ทั้งหมด สำหรับ torchvision จะเป็น library เกี่ยวกับการประมวลผลภาพ
จากนั้นจะกำหนด hyperparameter ได้แก่
- batch_size เป็นขนาด 16 หมายความว่าจะนำภาพเข้าไปในโมเดลครั้งละ 16 ภาพโดยภาพมีขนาด 32x32x3
- num_class = 10 หมายความว่าภาพที่จะให้ทำนายมี 10 ประเภท หรือ 10 class
- learning_rate = 0.0001 กำหนดอัตราการเรียนรู้เท่ากับ 0.0001
- num_epochs = 50 กำหนดจำนวนรอบของการสอนโมเดล 20 รอบ
- สำหรับบรรทัดสุดท้าย คือ ถ้ามี cuda หรือ GPU ก็ให้ไปทำงานบน gpu
Loading the Dataset
ต่อไปเราจะโหลดข้อมูลเพื่อนำมา train โดยจะใช้โมดูล dataset ที่อยู่ใน torchvision โดยโมดูลนี้จะช่วยในการโหลด dataset ให้มาอยู่ในเครื่องคอมพิวเตอร์ (./data) โดยในระหว่างโหลดสามารถแปลงรูปภาพ (transformation) ได้ด้วย จากโปรแกรม คือ แปลงให้เป็น Tensor และ Normalize ระดับสีของแต่ละสีให้เหมาะสม
เราจะสร้าง dataset เป็น 3 ชุด โดยชุดหนึ่งสำหรับฝึกสอนโมเดล (training set) อีกชุดหนึ่งสำหรับทดสอบความถูกต้อง (validation set) และชุดสุดท้ายสำหรับทดสอบความถูกต้อง โดยแบ่งในอัตรา 80:10:10 จากนั้นส่ง dataset ต่อไปยัง dataloader ซึ่งจะทำหน้าที่จัดข้อมูลเป็น batch เพื่อส่งเข้าไปในโมเดลครั้งละ 1 batch
ในการสร้าง dataset สามารถกำหนดได้ว่าจะให้ทำ data augmentation ด้วยหรือไม่ โดยในโปรแกรมนี้ได้กำหนดให้ใน training dataset ให้ทำการ random crop และ random horizontal flip เพื่อเพิ่มความหลากหลายของข้อมูลให้มากขึ้น
นอกจากนั้นยังมีการขยายขนาดภาพจาก 32x32 เป็น 227x227 เพื่อให้เหมือนกับภาพที่ใช้กับโมเดล AlexNet ต้นฉบับ
class CIFAR10Dataset(Dataset):
def __init__(self, data_dir, train=True, augment=False, valid_size=0.1, random_seed=1):
self.data_dir = data_dir
self.train = train
self.augment = augment
self.valid_size = valid_size
self.random_seed = random_seed
self.normalize = transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010],
)
if self.train:
if self.augment:
self.transform = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
self.normalize,
])
else:
self.transform = transforms.Compose([
transforms.Resize((227, 227)),
transforms.ToTensor(),
self.normalize,
])
else:
self.transform = transforms.Compose([
transforms.Resize((227, 227)),
transforms.ToTensor(),
self.normalize,
])
self.dataset = datasets.CIFAR10(
root=self.data_dir, train=self.train,
download=True, transform=self.transform,
)
if self.train:
num_train = len(self.dataset)
indices = list(range(num_train))
split = int(np.floor(self.valid_size * num_train))
np.random.seed(self.random_seed)
np.random.shuffle(indices)
self.train_idx, self.valid_idx = indices[split:], indices[:split]
def __len__(self):
return len(self.dataset)
def __getitem__(self, idx):
return self.dataset[idx]
def get_train_sampler(self):
return SubsetRandomSampler(self.train_idx)
def get_valid_sampler(self):
return SubsetRandomSampler(self.valid_idx)
def get_data_loaders(data_dir, batch_size, augment=False, valid_size=0.1, random_seed=1):
train_dataset = CIFAR10Dataset(data_dir, train=True, augment=augment, valid_size=valid_size, random_seed=random_seed)
test_dataset = CIFAR10Dataset(data_dir, train=False)
train_loader = DataLoader(
train_dataset, batch_size=batch_size, sampler=train_dataset.get_train_sampler()
)
valid_loader = DataLoader(
train_dataset, batch_size=batch_size, sampler=train_dataset.get_valid_sampler()
)
test_loader = DataLoader(
test_dataset, batch_size=batch_size, shuffle=True
)
return train_loader, valid_loader, test_loader
# Usage:
train_loader, valid_loader, test_loader = get_data_loaders(
data_dir='./data',
batch_size=batch_size,
augment=False,
random_seed=42
)
AlexNet from Scratch
คราวนี้มาดูโปรแกรมส่วนของโมเดล โปรแกรมนี้เขียนให้อ่านได้ง่ายโดยแบ่งเป็น Layer ที่ชัดเจน
- Layer 1 มี filter 96 แผ่น มี kernel size ขนาด 11x11 ใช้ stride=4 ไม่มี padding จากนั้นนำมาทำ batch normalization เพื่อปรับค่า weight ให้ไม่กระโดดกันมาก และส่งผ่าน ReLU แล้วจึงทำ max pooling
- Layer 2 มี filter 256 แผ่น มี kernel size ขนาด 5x5 ใช้ stride=1 และ padding=2 จากนั้นนำมาทำ batch normalization และส่งผ่าน ReLU แล้วจึงทำ max pooling
- Layer 3 มี filter 384 แผ่น มี kernel size ขนาด 3x3 ใช้ stride=1 และ padding=1 จากนั้นนำมาทำ batch normalization และส่งผ่าน ReLU แต่ไม่มีการทำ pooling
- Layer 4 เหมือนกับ Layer 3
- Layer 5 มี filter 256 แผ่น มี kernel size ขนาด 3x3 ใช้ stride=1 และ padding=1 จากนั้นนำมาทำ batch normalization และส่งผ่าน ReLU แล้วจึงทำ max pooling
- Layer 6–8 ทำ Fully Connected โดยมีจำนวน node 4096, 4096 และ 1000 ตามลำดับ โดยมีการทำ dropout 50%
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
nn.BatchNorm2d(96),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer4 = nn.Sequential(
nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer5 = nn.Sequential(
nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(9216, 4096),
nn.ReLU())
self.fc1 = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU())
self.fc2= nn.Sequential(
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.fc1(out)
out = self.fc2(out)
return out
Setting training loop
จากนั้นเราจะเขียนโปรแกรม function ที่ทำหน้าที่ในการ train
def train_model(model, train_loader, valid_loader, criterion, optimizer, epochs=20, device='cuda'):
training_logs = {
"train_loss": [], "train_acc": [], "validate_loss": [], "validate_acc": []
}
for epoch in range(epochs):
# Training phase
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
train_loss = running_loss / len(train_loader)
train_accuracy = 100 * correct / total
training_logs["train_loss"].append(train_loss)
training_logs["train_acc"].append(train_accuracy)
# Validation phase
model.eval()
running_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for images, labels in valid_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
valid_loss = running_loss / len(valid_loader)
valid_accuracy = 100 * correct / total
training_logs["validate_loss"].append(valid_loss)
training_logs["validate_acc"].append(valid_accuracy)
if epoch % 5 == 0:
print(f'Epoch [{epoch+1}/{epochs}] :: ',end='')
print(f'Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.2f}% ',end='')
print(f'Valid Loss: {valid_loss:.4f}, Valid Accuracy: {valid_accuracy:.2f}%')
print('-' * 80)
return training_logs
ขออธิบาย code ในส่วนของฟังก์ชัน train_model
- ฟังก์ชันจะรับพารามิเตอร์ model , train_loader, test_loader, criterion, optimizer และ epochs
- model คือ ตัว instance ของโมเดลที่สร้างขึ้นจาก class
- train_loader และ test_loader คือ ตัวโหลดข้อมูลเข้ามาทีละ batch เพื่อเข้ามา train ในแต่ละรอบ
- criterion คือ loss function ที่ใช้คำนวณว่าการทำงานมีผลลัพธ์ใกล้กับสิ่งที่ต้องการมากน้อยเพียงใด
- optimizer คือ ฟังก์ชันที่ทำการคำนวณค่าพารามิเตอร์ที่จะต้องปรับในโมเดล
- epochs คือ จำนวนรอบในการทำงาน
- ในบรรทัดแรกจะมีการเตรียมพื้นที่สำหรับเก็บผลการทำงานในแต่ละรอบ โดยเก็บใน training_logs
- จากนั้นจะเข้าสู่ loop แรก ซึ่งจะทำงานตามจำนวน epochs และภายใน loop นอกนี้จะแบ่งการทำงานเป็น 2 ส่วน คือ ส่วนของ training loop และ evaluation loop
- ใน loop ในที่ 1 เป็น training loop จะรับข้อมูลภาพและ label มาจาก train loader จากนั้นจะนำเข้าสู่ device ซึ่งอาจเป็น GPU หรือ CPU
- จากนั้นจะเป็นการทำ forward pass โดยให้ model ทำการทำนายตามข้อมูลใน model โดยจะได้ข้อมูลกลับมาใน output ซึ่งจะนำไปคำนวณค่า loss ในบรรทัด
loss=criterion(output, labels)
- จากนั้นจึงทำ backward pass โดยเริ่มจากเคลียรข้อมูล gradient จากการทำงานในรอบก่อนหน้าออก
optimizer.zero_grad()
- จากนั้นนำค่า loss ไปคำนวณค่า gradient ใหม่สำหรับปรับพารามิเตอร์ โดยใช้ฟังก์ชัน
loss.backward()
- และสุดท้ายนำไปปรับ weights ในฟังก์ชัน
optimizer.step()
- ใน loop ในที่ 2 จะเป็น loop สำหรับนำข้อมูลที่ใช้ประเมิน evaluation data มาทดสอบกับโมเดล จะได้เห็นภาพว่าในแต่ละ epoch โมเดลมีการปรับตัวดีขึ้นมากน้อยเพียงใด โดยการทำงานจะคล้ายกับ training loop ทุกประการ จะต่างกันตรงที่จะไม่มีการปรับ weights
- ส่วนสุดท้ายจะเป็นการเก็บข้อมูลของแต่ละ epoch ลงใน training_logs และในทุก 5 epoch จะแสดงผลการทำงานออกมาที่จอภาพ
การสอน model (Training)
หลังจากที่สร้างฟังก์ชันสำหรับ train แล้ว ก็มาทำการสอนโมเดลกัน โดยกำหนด loss function ซึ่งในที่นี้จะใช้ CrossEntropyLoss เนื่องจากเป็นการทำงานแบบ multiple classification และเลือก optimization algorithm ซึ่งจะเลือกฟังก์ชัน SDG (Stochastic Gradient Descent) ในการทำงาน เมื่อพร้อมแล้วก็เรียกฟังก์ชัน train model มาทำงาน
model = AlexNet(num_classes).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=0.005, momentum=0.9)
training_logs = train_model(model, train_loader, valid_loader, criterion, optimizer, epochs=num_epochs)
ซึ่งจะแสดงผลลัพธ์ดังนี้
Epoch [1/51] :: Train Loss: 1.6504, Train Accuracy: 39.44% Valid Loss: 1.2519, Valid Accuracy: 55.04%
--------------------------------------------------------------------------------
Epoch [6/51] :: Train Loss: 0.7332, Train Accuracy: 74.44% Valid Loss: 0.6569, Valid Accuracy: 76.70%
--------------------------------------------------------------------------------
Epoch [11/51] :: Train Loss: 0.5107, Train Accuracy: 82.05% Valid Loss: 0.5793, Valid Accuracy: 80.54%
--------------------------------------------------------------------------------
Epoch [16/51] :: Train Loss: 0.3665, Train Accuracy: 87.03% Valid Loss: 0.5341, Valid Accuracy: 82.76%
--------------------------------------------------------------------------------
Epoch [21/51] :: Train Loss: 0.2528, Train Accuracy: 91.12% Valid Loss: 0.5653, Valid Accuracy: 82.40%
--------------------------------------------------------------------------------
Epoch [26/51] :: Train Loss: 0.1788, Train Accuracy: 93.76% Valid Loss: 0.5684, Valid Accuracy: 83.42%
--------------------------------------------------------------------------------
Epoch [31/51] :: Train Loss: 0.1220, Train Accuracy: 95.78% Valid Loss: 0.5434, Valid Accuracy: 85.24%
--------------------------------------------------------------------------------
Epoch [36/51] :: Train Loss: 0.0858, Train Accuracy: 97.09% Valid Loss: 0.5081, Valid Accuracy: 85.86%
--------------------------------------------------------------------------------
Epoch [41/51] :: Train Loss: 0.0639, Train Accuracy: 98.00% Valid Loss: 0.5097, Valid Accuracy: 86.32%
--------------------------------------------------------------------------------
Epoch [46/51] :: Train Loss: 0.0501, Train Accuracy: 98.57% Valid Loss: 0.5263, Valid Accuracy: 85.28%
--------------------------------------------------------------------------------
Epoch [51/51] :: Train Loss: 0.0359, Train Accuracy: 99.08% Valid Loss: 0.4865, Valid Accuracy: 86.58%
--------------------------------------------------------------------------------
จะเห็นได้ว่าค่า loss ลดลงในทุก epoch ซึ่งแสดงให้เห็นว่า model สามารถเรียนรู้ และทำงานได้ดีขึ้นเรื่อยๆ ตามรอบการทำงาน อย่างไรก็ตามจะเห็นได้ว่า ในรอบหลังๆ ค่า loss มีการลดลงในอัตราที่น้อยลง และ validation accuracy ถูกต้องในอัตราที่ลดลง และมีค่าที่ต่างจาก training accuracy ซึ่งแสดงให้เห็นว่ายังคงเกิด overfitting ขึ้น ซึ่งมีหลายวิธีที่จะแก้ไขได้อีก
การทดสอบ model (Testing)
จะแบ่งออกเป็น 2 ส่วน คือ แสดงโดยกราฟ ซึ่งใช้โปรแกรมต่อไปนี้
def plot_graph(training_logs):
epochs = len(training_logs["train_loss"])
epochs_range = range(1, epochs + 1)
plt.figure(figsize=(12, 5))
# Plot loss
plt.subplot(1, 2, 1)
plt.plot(epochs_range, training_logs["train_loss"], label='Train Loss')
plt.plot(epochs_range, training_logs["validate_loss"], label='Valid Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.title('Loss vs Epochs')
# Plot accuracy
plt.subplot(1, 2, 2)
plt.plot(epochs_range, training_logs["train_acc"], label='Train Accuracy')
plt.plot(epochs_range, training_logs["validate_acc"], label='Valid Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.title('Accuracy vs Epochs')
plt.tight_layout()
plt.show()
plot_graph(training_logs)
ซึ่งได้ผลดังนี้
และเขียนโปรแกรมเพื่อนำ test set ซึ่งโมเดลยังไม่เคยเห็นภาพมาก่อนมาทดสอบ โดยใช้โปรแกรมต่อไปนี้
def test_model(model, test_loader, criterion, device='cuda'):
model.eval()
running_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
test_loss = running_loss / len(test_loader)
test_accuracy = 100 * correct / total
print(f'Test Loss: {test_loss:.4f}, Test Accuracy: {test_accuracy:.2f}%')
return test_loss, test_accuracy
test_loss, test_accuracy = test_model(model, test_loader, criterion)
ปรากฏผลว่า Test Loss: 0.5121, Test Accuracy: 85.75% ซึ่งถือว่าดีขึ้นกว่า LeNet5 ซึ่งได้ผลการทำงานแค่ 62%
สรุปผล
เราลองมาสรุปผลการทำงานกัน
- เราได้เริ่มจากอธิบายสถาปัตยกรรมของโมเดล AlexNet สิ่งที่ใหม่ของ AlexNet
- จากนั้นได้อธิบายการแบ่งข้อมูล และ pre-processed CIFAR10 dataset โดยใช้
torchvision
- และใช้
PyTorch
ในการสร้าง AlexNet model - สุดท้ายเราได้ train และ test การทำงาน บน CIFAR10 dataset และโมเดลก็ทำงานได้ดีพอสมควร
ต่อไปเราจะลองโมเดลอื่นๆ กับชุดข้อมูลนี้ว่าจะให้ผลเป็นอย่างไร