1. LeNet-5 란?
LeNet-5는 합성곱 신경망이라는 개념을 최초로 Yann LeCun이 개발한 구조이다. 수표에 쓴 손글씨 숫자를 인식하는 Deep Learning 구조 LeNet-5fmf 1955년 발표하였는데, 이것이 현재 CNN의 초석이 되었다. LeNet-5는 합성곱(Convolutional)과 다운 샘플링(Down - Sampling 혹은 Pooling)을 반복적으로 거치면서 마지막에 완전 연결층에서 분류를 수행한다.
- Input : 32 x 32 x 1
- Convolution Layer 1(C1) : 5x5 합성곱 => 28 x 28의 Feature Map 6개 생성
- in_channels=1, out_channels=6, kernel_size=5, stride=1
- $W = H = \frac{32 - 5 + 2 * 0}{1} + 1 = 28$
- S2 : Down Sampling(Average Pooliing) => Feature map의 크기를 14 x 14로 줄인다.
- kernel_size=2, stride=2
- $W = H = \frac{28 - 2}{2} + 1 = 14$
- C3 : 5x5 합성곱 => 10x10의 Feature Map 16개 생성
- in_channels=10, out_channels=16, kernel_size=5, stride=1
- $W = H = \frac{15 - 5 + 2 * 0}{1} + 1 = 10$
- S4 : Donw Sampling => Feature Map 크기를 5x5로 줄인다.
- kernel_size=2, stride=2
- $W = H = \frac{10 - 2}{2} + 1 = 5$
- C5 : 5x5 합성곱 => 1x1의 Feature Map 120개 생성
- in_channels=10, out_channels=120, kernel_size=5, stride=1
- $W = H = \frac{5 - 5 + 2 * 0}{1} + 1 = 1$
- F6 : Fully Connected Layer(120 => 84)
- in_features=120, out_features=84
- Output : Fully Conneted Layer(84 => 10)
- in_features=120, out_features=84
- Activation Function : Hyperbolic Tangent
2. LeNet-5 구현
1) 라이브러리 호출
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torchsummary import summary
import matplotlib.pyplot as plt
device = "cuda" if torch.cuda.is_available() else "cpu"
2) Image 데이터 전처리 방법 정의
transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor()
])
3) MNIST 데이터 셋 불러오기
torchvision 패키지에서 제공하는 MNIST 데이터 셋을 불러옵니다.
MNIST 데이터셋은 손글씨 이미지 데이터이며 60000개의 Train data와 10000개의 Test 데이터를 가집니다.
Image Data 전처리 방법은 앞서 정의한 방법으로 수행됩니다.
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32)
4) 데이터 시각화
fig, axes = plt.subplots(1, 5, figsize=(12, 3))
for i, ax in enumerate(axes):
image, label = train_dataset[i]
ax.imshow(image.squeeze(), cmap="gray")
ax.set_title(f"Label: {label}")
ax.axis("off")
plt.tight_layout()
plt.show()
5) LeNet-5 정의
위에서 봤던 LeNet5를 Pytorch를 통해 구현해보았습니다.
Activation Function
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, stride=1)
self.tanh1 = nn.Tanh()
self.avgpool1 = nn.AvgPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1)
self.tanh2 = nn.Tanh()
self.avgpool2 = nn.AvgPool2d(2, 2)
self.conv3 = nn.Conv2d(16, 120, kernel_size=5, stride=1)
self.tanh3 = nn.Tanh()
self.fc1 = nn.Linear(120, 84)
self.tanh4 = nn.Tanh()
self.fc2 = nn.Linear(84, 10)
def forward(self, x):
x = self.conv1(x)
x = self.tanh1(x)
x = self.avgpool1(x)
x = self.conv2(x)
x = self.tanh2(x)
x = self.avgpool2(x)
x = self.conv3(x)
x = self.tanh3(x)
x = x.view(-1, 120)
x = self.fc1(x)
x = self.tanh4(x)
output = self.fc2(x)
return output
6) Model 선언 및 torchsummary로 확인
torchsummary 패키지에서 제공하는 summary를 통해 Model의 구조를 간단하게 살펴볼 수 있습니다.
summary에 model과 input_size를 넘겨주어 확인할 수 있습니다.
model = LeNet5()
model.to(device)
summary(model, (1, 32, 32))
7) Optimizer 및 Loss Function 정의
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
8) Model Train
num_epoch = 10
for epoch in range(num_epoch):
print('Epoch {}/{}'.format(epoch + 1, num_epoch))
print('-'*20)
epoch_loss = 0.0
epoch_acc = 0.0
corrects = 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()
_, preds = torch.max(outputs, 1)
epoch_loss += loss.item() * images.size(0)
corrects += torch.sum(preds == labels.data)
epoch_loss = epoch_loss / len(train_loader.dataset)
epoch_acc = corrects.double() / len(train_loader.dataset)
print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss, epoch_acc))
Epoch 1/10
--------------------
Loss: 0.2301 Acc: 0.9322
Epoch 2/10
--------------------
Loss: 0.0808 Acc: 0.9751
Epoch 3/10
--------------------
Loss: 0.0564 Acc: 0.9824
Epoch 4/10
--------------------
Loss: 0.0449 Acc: 0.9857
Epoch 5/10
--------------------
Loss: 0.0360 Acc: 0.9881
Epoch 6/10
--------------------
Loss: 0.0317 Acc: 0.9896
Epoch 7/10
--------------------
Loss: 0.0271 Acc: 0.9913
Epoch 8/10
--------------------
Loss: 0.0241 Acc: 0.9921
Epoch 9/10
--------------------
Loss: 0.0202 Acc: 0.9933
Epoch 10/10
--------------------
Loss: 0.0182 Acc: 0.9942
Accruracy가 대략 98% 정도로 뛰어난 성능을 보임을 알 수 있습니다.
9) Model Test
model.eval()
test_loss = 0.0
test_acc = 0.0
corrects = 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)
_, preds = torch.max(outputs, 1)
test_loss += loss.item() * images.size(0)
corrects += torch.sum(preds == labels.data)
test_loss = test_loss / len(test_loader)
test_acc = corrects.double() / len(test_loader.dataset)
print('Test Loss: {:.4f} Test Accuracy: {:.4f}'.format(test_loss, test_acc))
Test Loss: 1.2576 Test Accuracy: 0.9886
Test 과정에서도 Accruracy가 대략 98% 정도로 뛰어난 성능을 보임을 알 수 있습니다.
반응형