본문 바로가기
DL/CNN

[DL][CNN] LeNet-5

by 어떻게든 되겠지~ 2025. 1. 15.

 

 

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% 정도로 뛰어난 성능을 보임을 알 수 있습니다.

반응형