1998년 Yann Lecun 연구팀은 "Gradient-based learning applied to document recognition” 논문에서 LeNet을 통해 CNN(Convolutional Neural Network) 구조를 세상에 처음 공개한다. 기존의 DNN(Deep Neural Network)은 1차원 형태의 데이터를 사용하다 보니, 2차원 형태의 이미지의 입력값을 처리할 때 이미지의 공간적, 지역적 정보 손실이 불가피 했다. CNN은 이러한 문제점을 해결하며, 이미지 분야에서 딥러닝을 크게 도약 하게 만든다.
1998년 이후 2012년 AlexNet, 2014년 VGGNet, 2015년 ResNet을 비롯하여 많은CNN 구조의 Networks들이 등장하였다.



Convolutional Layer (C1)
First Pooling Layer (S2)
Second Convolutional Layer (C3)
Second Pooling Layer (S4)
Third Convolutional Layer (C5)
Fully Connected Layer (F6)
Output Layer
- 84개의 노드를 10개의 출력 클래스 (0~9 숫자)로 연결한다.
- 각 노드는 특정 숫자 클래스에 해당하는 확률을 나타낸다.
Stride & Padding
- Stride
Stride는 컨볼루션 필터가 입력 데이터 위를 이동하는 간격을 의미한다. 기본적으로 stride는 필터가 한 번에 이동하는 픽셀 수를 정의하며, 주로 컨볼루션에서는 기본값은 1을 사용하며, 풀링에서는 윈도우와 같은 값을 사용한다. 수가 클수록 출력 맵에 크기를 줄일 수 있으며, 계산 비용을 줄여줄 수 있다.- Padding
Padding은 입력 데이터의 경계에 값을 추가하는 것을 의미한다. 주로 컨볼루션 연산 후 출력 크기를 조정하거나, 경계 정보 손실을 방지하기 위해 사용되며, 기본값은 0을 사용한다. 출력 맵에 크기를 유지시켜 주거나 입력 데이터의 경계 정보를 보존하여 컨볼루션이 경계 영역에서도 충분한 정보를 활용할 수 있도록 한다.
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
# LeNet-5 모델 정의
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0)
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0)
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
self.conv3 = nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0)
self.fc1 = nn.Linear(120 * 1 * 1, 84)
self.fc2 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
x = F.relu(self.conv3(x))
x = x.view(-1, 120 * 1 * 1)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
# 데이터셋 로딩 및 변환
transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
# 모델, 손실 함수, 옵티마이저 정의
model = LeNet5()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for data, target in train_loader:
optimizer.zero_grad()
outputs = model(data)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")
# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data, target in test_loader:
outputs = model(data)
_, predicted = torch.max(outputs.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f"Accuracy: {100 * correct / total}%")

| Layer 이름 | Layer 설명 | |
|---|---|---|
| 1 | Input Layer | 227 x 227 크기의 RGB 이미지 입력 |
| 2 | Conv1 | |
| 3 | Max Pool1 | |
| 4 | Conv2 | |
| 5 | Max Pool2 | |
| 6 | Conv3 | |
| 7 | Conv4 | |
| 8 | Conv5 | |
| 9 | Max Pool3 | |
| 10 | FC1 | |
| 11 | FC2 | |
| 12 | FC3 | |
| 13 | OutPut | Layer 1,000개의 출력 클래스 |
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
# AlexNet 모델 정의
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv1 = nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0)
self.pool1 = nn.MaxPool2d(kernel_size=3, stride=2)
self.conv2 = nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2)
self.pool2 = nn.MaxPool2d(kernel_size=3, stride=2)
self.conv3 = nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1)
self.conv4 = nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1)
self.conv5 = nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1)
self.pool3 = nn.MaxPool2d(kernel_size=3, stride=2)
self.fc1 = nn.Linear(256 * 6 * 6, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, 1000)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
x = F.relu(self.conv3(x))
x = F.relu(self.conv4(x))
x = F.relu(self.conv5(x))
x = self.pool3(x)
x = x.view(-1, 256 * 6 * 6)
x = F.relu(self.fc1(x))
x = F.dropout(x, p=0.5)
x = F.relu(self.fc2(x))
x = F.dropout(x, p=0.5)
x = self.fc3(x)
return x
# 데이터셋 로딩 및 변환
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(227),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])
train_dataset = datasets.ImageFolder(root='./data/train', transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_dataset = datasets.ImageFolder(root='./data/val', transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
# 모델, 손실 함수, 옵티마이저 정의
model = AlexNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for data, target in train_loader:
optimizer.zero_grad()
outputs = model(data)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")
# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data, target in test_loader:
outputs = model(data)
_, predicted = torch.max(outputs.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f"Accuracy: {100 * correct / total}%")

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
# VGG-16 모델 정의
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__()
self.conv1 = nn.Sequential( # Block 1
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv2 = nn.Sequential( # Block 2
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv3 = nn.Sequential( # Block 3
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv4 = nn.Sequential( # Block 4
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv5 = nn.Sequential( # Block 5
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.fc_layers = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 1000)
)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = x.view(x.size(0), -1) # Flatten
x = self.fc_layers(x)
return x
# 데이터셋 로딩 및 변환
transform = transforms.Compose([
transforms.Resize((224, 224)), # VGG 입력 크기 224x224
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)), # ImageNet 평균과 표준편차
])
train_dataset = datasets.ImageFolder(root='./data/train', transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
test_dataset = datasets.ImageFolder(root='./data/val', transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)
# 모델, 손실 함수, 옵티마이저 정의
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = VGG16().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.0001)
# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for data, target in train_loader:
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
outputs = model(data)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")
# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
outputs = model(data)
_, predicted = torch.max(outputs, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f"Accuracy: {100 * correct / total:.2f}%")