안녕하세요! 딥러닝에 입문할 때 가장 먼저 접하게 되는 컴퓨터 비전 분야, 바로 '이미지 분류(Image Classification)'인데요. 막상 이론을 배우고 코드를 짜려고 하면 데이터셋마다 채널 수가 다르고 해상도가 달라서 "어라? 왜 에러가 나지?" 하고 당황하셨던 경험이 있으실 겁니다.
그래서 이번 포스팅에서는 딥러닝의 기초 체력을 탄탄하게 기를 수 있도록, PyTorch(파이토치)를 활용해 가장 대표적인 두 가지 데이터셋인 MNIST와 CIFAR-10을 다루는 방법을 준비했습니다. 데이터셋의 특성을 이해하고, 그에 맞게 CNN 모델을 유연하게 수정하는 방법까지 아주 쉽게 풀어드릴 테니 차근차근 따라와 주세요!

📌 핵심 요약 3줄
- 기초부터 실전까지: 흑백 이미지(MNIST)와 컬러 이미지(CIFAR-10)의 특성을 비교하고 PyTorch로 구현합니다.
- 모델 구조의 이해: 데이터셋의 채널 수와 이미지 크기에 따라 CNN의 입력 채널 및 Fully Connected Layer(FC 레이어) 크기를 맞추는 방법을 배웁니다.
- 실전 문제 해결: 초보자가 흔히 겪는 데이터 차원(Dimension) 에러를 방지하는 팁과 실수 예방법을 제공합니다.
1. 서론
이미지 분류(Image Classification)는 컴퓨터가 이미지를 보고 "이것은 고양이입니다", "이것은 자동차입니다"라고 특정 클래스에 할당하는 작업입니다. 딥러닝 비전 분야의 가장 기본적이면서도 중요한 주춧돌이라고 할 수 있죠.
오늘 실습에서는 파이토치를 사용해 직접 CNN(합성곱 신경망) 모델을 설계하고, 데이터셋의 형태가 바뀔 때 코드의 어느 부분을 유연하게 수정해야 하는지 핵심 포인트를 짚어보겠습니다.
2. 데이터셋 소개
우리가 오늘 사용할 두 데이터셋은 딥러닝 계의 'Hello World'라고 불릴 만큼 유명하지만, 성격이 완전히 다릅니다. 두 데이터셋의 차이점을 표로 깔끔하게 정리해 드릴게요.
📊 MNIST vs CIFAR-10 데이터셋 한눈에 비교
| 특징 | MNIST 데이터셋 | CIFAR-10 데이터셋 |
| 이미지 형태 | 흑백 (Grayscale) | 컬러 (RGB) |
| 입력 채널 수 (Channels) | 1개 | 3개 |
| 이미지 해상도 (Size) | 28 x 28 픽셀 | 32 x 32 픽셀 |
| 클래스 종류 | 숫자 0 ~ 9 (10개) | 비행기, 자동차, 새, 고양이 등 (10개) |
| 학습 데이터 수 | 60,000개 | 50,000개 |
| 테스트 데이터 수 | 10,000개 | 10,000개 |
| 주요 특징 | 배경이 깨끗하고 형태가 단순함 | 사물의 형태, 색상, 배경이 다양해 난이도가 높음 |
3. PyTorch를 활용한 이미지 분류 모델 구현 (MNIST 기준)
3.1 라이브러리 불러오기 및 데이터 로딩
코드가 겹치거나 가독성이 떨어지지 않도록 가이드라인에 맞춰 정돈된 코드를 보여드릴게요. 먼저 필요한 도구들을 가져오고 MNIST 데이터를 다운로드합니다.
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
# 1. 하이퍼파라미터 설정
batch_size = 64
learning_rate = 0.001
epochs = 10
# 2. MNIST 전처리 변환 정의 (흑백 이미지이므로 채널 1개 기준 정규화)
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 3. MNIST 데이터셋 로드
train_dataset = torchvision.datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False, transform=transform, download=True)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
3.2 CNN 모델 정의
여기서 주의 깊게 보셔야 할 점은 forward 함수 내부의 들여쓰기 오류를 바로잡고 코드가 안정적으로 실행되도록 다듬은 부분입니다.
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# MNIST는 흑백이므로 in_channels=1 입니다.
self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 28x28 이미지 -> conv1 -> conv2 -> pool(2x2)을 거치면 14x14 크기가 됩니다.
self.fc1 = nn.Linear(64 * 14 * 14, 128)
self.fc2 = nn.Linear(128, 10)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.pool(self.relu(self.conv2(x)))
x = x.view(x.size(0), -1) # Flatten (1차원 펼치기)
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
(💡 참고: 기존 코드의 64 * 7 * 7은 풀링 레이어가 conv2 뒤에 한 번만 적용되었을 때의 실제 차원인 64 * 14 * 14로 수정하여 에러를 방지했습니다!)
3.3 모델 학습
model = CNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
def train(model, train_loader, criterion, optimizer, epochs):
model.train()
for epoch in range(epochs):
running_loss = 0.0
for images, labels in train_loader:
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f'Epoch [{epoch+1}/{epochs}], Loss: {running_loss/len(train_loader):.4f}')
# 학습 시작
train(model, train_loader, criterion, optimizer, epochs)
3.4 모델 평가
def evaluate(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy: {100 * correct / total:.2f}%')
# 평가 시작
evaluate(model, test_loader)
4. CIFAR-10을 위한 모델 수정 (차이점 분석)
자, 이제 흑백이 아닌 컬러 이미지(CIFAR-10) 데이터셋으로 바꿀 때 코드가 어떻게 달라지는지 확인해 볼까요? 변경해야 할 핵심 포인트는 딱 두 가지입니다!
- 입력 채널(in_channels): 1(흑백)에서 3(RGB 컬러)으로 변경
- FC 레이어 입력 크기: 이미지 해상도가 32x32로 커졌기 때문에 풀링 후 크기가 64 * 16 * 16으로 바뀝니다.
# 1. CIFAR-10 전처리 (3채널 정규화)
transform_cifar10 = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
train_dataset_cifar10 = torchvision.datasets.CIFAR10(root='./data', train=True, transform=transform_cifar10, download=True)
test_dataset_cifar10 = torchvision.datasets.CIFAR10(root='./data', train=False, transform=transform_cifar10, download=True)
train_loader_cifar10 = torch.utils.data.DataLoader(train_dataset_cifar10, batch_size=batch_size, shuffle=True)
test_loader_cifar10 = torch.utils.data.DataLoader(test_dataset_cifar10, batch_size=batch_size, shuffle=False)
# 2. CIFAR-10 전용 CNN 모델 정의
class CIFAR10_CNN(nn.Module):
def __init__(self):
super(CIFAR10_CNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1) # 3채널로 변경!
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 32x32 이미지 -> pool 거치면 16x16 크기가 됩니다.
self.fc1 = nn.Linear(64 * 16 * 16, 128) # 데이터 크기에 맞춰 수정!
self.fc2 = nn.Linear(128, 10)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.pool(self.relu(self.conv2(x)))
x = x.view(x.size(0), -1)
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
🛠️ 개발을 위한 꿀팁 (Tips)
- GPU 가속은 필수! 딥러닝 연산은 CPU로 하면 속도가 정말 느립니다. 코드 상단에 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')를 선언하고, 모델과 데이터(.to(device))를 GPU로 보내서 연산 속도를 10배 이상 끌어올리세요!
- print() 함수로 차원 확인하기: CNN 레이어를 거치고 난 뒤 최종 데이터의 크기가 헷갈린다면, forward 함수 안에서 print(x.size())를 찍어보세요. 굳이 암산하지 않아도 다음 레이어의 입력 크기를 정확하게 알 수 있답니다.
- 데이터 증강(Data Augmentation) 활용하기: CIFAR-10처럼 난이도가 있는 데이터셋은 transforms.RandomHorizontalFlip()이나 transforms.RandomCrop() 같은 무작위 변형을 주면 과적합(Overfitting)을 막고 성능을 더 올릴 수 있습니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- 차원 불일치 에러 (RuntimeError: size mismatch): 초보자들이 가장 많이 만나는 에러입니다! CNN 레이어에서 Pooling을 거치며 줄어든 이미지 크기를 계산하지 않고 FC 레이어(nn.Linear)의 입력 크기를 잘못 입력하면 발생합니다. 데이터셋을 바꿀 때 항상 이미지 해상도를 체크하세요.
- 학습 모드 설정 누락: 모델을 평가할 때는 반드시 model.eval()을 켜주고, 드롭아웃이나 배치 정규화가 작동하지 않도록 해야 합니다. 반대로 학습할 때는 model.train()을 꼭 다시 켜주어야 학습이 제대로 이루어집니다.
- 옵티마이저 그레디언트 초기화 누락: optimizer.zero_grad()를 반복문 시작점에 넣지 않으면 이전 배치의 기울기(Gradient)가 누적되어 모델 학습이 산으로 가거나 수렴하지 않게 됩니다.
5. 결론
이번 포스팅에서는 PyTorch를 활용해 가장 기본이 되는 MNIST와 CIFAR-10 이미지 분류 모델을 직접 구현해 보았습니다.
단순히 코드를 복사해서 붙여넣는 것에 그치지 않고, 흑백과 컬러라는 데이터셋의 특성에 맞춰 신경망의 입력 채널과 Fully Connected 레이어의 크기를 유연하게 계산하고 수정하는 방법이 핵심이었습니다. 오늘 배운 내용을 토대로 더 깊고 다양한 레이어를 쌓아보거나 고해상도 커스텀 데이터셋에도 도전해 보세요!
궁금한 점이나 막히는 부분이 있다면 언제든지 댓글로 남겨주세요. 즐거운 딥러닝 공부 되세요!
'Python for AI, Embedded > Deep Learning: PyTorch & AI Modeling' 카테고리의 다른 글
| PyTorch로 시작하는 CNN 이미지 분류: 기초부터 실전 예제까지 (0) | 2026.05.14 |
|---|---|
| PyTorch CNN(합성곱 신경망) 완벽 가이드: 동작 원리부터 구현까지 (0) | 2026.05.13 |
| PyTorch 혼합 정밀도 학습(AMP)으로 딥러닝 학습 속도 2배 높이기 (0) | 2026.05.12 |
| PyTorch 모델 훈련 속도를 획기적으로 높이는 7가지 최적화 기법 (0) | 2026.05.11 |
| PyTorch GPU(CUDA) 설정 및 사용법 완벽 가이드: 성능 10배 높이기 (0) | 2026.05.10 |