실제 프로젝트를 통해 딥러닝 전체 워크플로우를 익히기
1. 실습 프로젝트: MNIST 손글씨 숫자 인식
데이터셋 소개
- 데이터: 70,000개의 28×28 흑백 손글씨 숫자 이미지
- 클래스: 0-9 (10개)
- 분할: 60,000개 훈련, 10,000개 테스트
- 목표: 98% 이상 정확도
2. Keras 구현 (전체 코드)
2.1 라이브러리 임포트
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns
2.2 데이터 로드 및 전처리
# 데이터 로드
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
print(f"훈련 데이터: {X_train.shape}") # (60000, 28, 28)
print(f"테스트 데이터: {X_test.shape}") # (10000, 28, 28)
# 데이터 시각화
plt.figure(figsize=(10, 4))
for i in range(10):
plt.subplot(2, 5, i+1)
plt.imshow(X_train[i], cmap='gray')
plt.title(f"Label: {y_train[i]}")
plt.axis('off')
plt.tight_layout()
plt.show()
# 전처리
# 1. Flatten: (28, 28) → (784,)
X_train = X_train.reshape(-1, 784).astype('float32')
X_test = X_test.reshape(-1, 784).astype('float32')
# 2. 정규화: [0, 255] → [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0
print(f"전처리 후 훈련 데이터: {X_train.shape}") # (60000, 784)
print(f"전처리 후 테스트 데이터: {X_test.shape}") # (10000, 784)
2.3 모델 구축
model = keras.Sequential([
# 입력층
layers.Input(shape=(784,)),
# 첫 번째 은닉층
layers.Dense(512, kernel_initializer='he_normal'),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Dropout(0.2),
# 두 번째 은닉층
layers.Dense(256, kernel_initializer='he_normal'),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Dropout(0.2),
# 세 번째 은닉층
layers.Dense(128, kernel_initializer='he_normal'),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.Dropout(0.2),
# 출력층
layers.Dense(10, activation='softmax')
])
# 모델 요약
model.summary()
출력 예시:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 512) 401920
batch_normalization (None, 512) 2048
activation (Activation) (None, 512) 0
dropout (Dropout) (None, 512) 0
dense_1 (Dense) (None, 256) 131328
...
=================================================================
Total params: 669,706
Trainable params: 666,634
Non-trainable params: 3,072
2.4 모델 컴파일
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
2.5 콜백 설정
callbacks = [
# 조기 종료
EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True,
verbose=1
),
# 학습률 감소
ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=5,
min_lr=1e-7,
verbose=1
)
]
2.6 모델 학습
history = model.fit(
X_train, y_train,
batch_size=128,
epochs=50,
validation_split=0.1, # 10%를 검증 세트로
callbacks=callbacks,
verbose=1
)
2.7 학습 곡선 시각화
plt.figure(figsize=(12, 4))
# 손실 곡선
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Loss Curves')
plt.grid(True)
# 정확도 곡선
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='Train Acc')
plt.plot(history.history['val_accuracy'], label='Val Acc')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Accuracy Curves')
plt.grid(True)
plt.tight_layout()
plt.show()
2.8 모델 평가
# 테스트 세트 평가
test_loss, test_acc = model.evaluate(X_test, y_test, verbose=0)
print(f'Test Loss: {test_loss:.4f}')
print(f'Test Accuracy: {test_acc:.4f}')
# 예측
y_pred = model.predict(X_test)
y_pred_classes = y_pred.argmax(axis=1)
# 혼동 행렬
cm = confusion_matrix(y_test, y_pred_classes)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix')
plt.show()
# 분류 보고서
print("\nClassification Report:")
print(classification_report(y_test, y_pred_classes))
2.9 잘못 예측한 샘플 시각화
# 잘못 예측한 인덱스 찾기
wrong_indices = np.where(y_pred_classes != y_test)[0]
# 처음 10개 시각화
plt.figure(figsize=(12, 8))
for i, idx in enumerate(wrong_indices[:10]):
plt.subplot(2, 5, i+1)
# 원본 이미지 복원 (784 → 28x28)
img = X_test[idx].reshape(28, 28)
plt.imshow(img, cmap='gray')
plt.title(f"True: {y_test[idx]}\nPred: {y_pred_classes[idx]}")
plt.axis('off')
plt.tight_layout()
plt.show()
2.10 모델 저장 및 로드
# 모델 저장
model.save('mnist_model.keras')
# 모델 로드
loaded_model = keras.models.load_model('mnist_model.keras')
# 로드한 모델로 예측
predictions = loaded_model.predict(X_test[:5])
print("예측 결과:", predictions.argmax(axis=1))
print("실제 레이블:", y_test[:5])
3. PyTorch 구현 (전체 코드)
3.1 라이브러리 임포트
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, classification_report
3.2 데이터 준비
from tensorflow.keras.datasets import mnist
# 데이터 로드
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 전처리
X_train = X_train.reshape(-1, 784).astype('float32') / 255.0
X_test = X_test.reshape(-1, 784).astype('float32') / 255.0
# NumPy → Tensor
X_train_tensor = torch.FloatTensor(X_train)
y_train_tensor = torch.LongTensor(y_train)
X_test_tensor = torch.FloatTensor(X_test)
y_test_tensor = torch.LongTensor(y_test)
# 데이터 로더 생성
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
test_dataset = TensorDataset(X_test_tensor, y_test_tensor)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)
3.3 모델 정의
class MNISTModel(nn.Module):
def __init__(self):
super(MNISTModel, self).__init__()
self.fc1 = nn.Linear(784, 512)
self.bn1 = nn.BatchNorm1d(512)
self.dropout1 = nn.Dropout(0.2)
self.fc2 = nn.Linear(512, 256)
self.bn2 = nn.BatchNorm1d(256)
self.dropout2 = nn.Dropout(0.2)
self.fc3 = nn.Linear(256, 128)
self.bn3 = nn.BatchNorm1d(128)
self.dropout3 = nn.Dropout(0.2)
self.fc4 = nn.Linear(128, 10)
def forward(self, x):
x = self.fc1(x)
x = self.bn1(x)
x = torch.relu(x)
x = self.dropout1(x)
x = self.fc2(x)
x = self.bn2(x)
x = torch.relu(x)
x = self.dropout2(x)
x = self.fc3(x)
x = self.bn3(x)
x = torch.relu(x)
x = self.dropout3(x)
x = self.fc4(x)
return x
# 모델 생성
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = MNISTModel().to(device)
print(model)
3.4 손실 함수와 옵티마이저
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5, verbose=True
)
3.5 학습 함수
def train(model, train_loader, criterion, optimizer, device):
model.train()
running_loss = 0.0
correct = 0
total = 0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
# 순전파
outputs = model(inputs)
loss = criterion(outputs, labels)
# 역전파
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 통계
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
epoch_loss = running_loss / len(train_loader)
epoch_acc = 100. * correct / total
return epoch_loss, epoch_acc
3.6 검증 함수
def validate(model, val_loader, criterion, device):
model.eval()
running_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
epoch_loss = running_loss / len(val_loader)
epoch_acc = 100. * correct / total
return epoch_loss, epoch_acc
3.7 학습 루프
num_epochs = 50
best_val_acc = 0
history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}
for epoch in range(num_epochs):
train_loss, train_acc = train(model, train_loader, criterion, optimizer, device)
# 테스트 세트를 검증으로 사용 (간단하게)
val_loss, val_acc = validate(model, test_loader, criterion, device)
# 학습률 스케줄러
scheduler.step(val_loss)
# 기록
history['train_loss'].append(train_loss)
history['train_acc'].append(train_acc)
history['val_loss'].append(val_loss)
history['val_acc'].append(val_acc)
print(f'Epoch {epoch+1}/{num_epochs}')
print(f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')
print(f'Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%')
print('-' * 50)
# 최고 모델 저장
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), 'best_mnist_model.pth')
print(f'✓ 모델 저장 (Val Acc: {val_acc:.2f}%)')
3.8 학습 곡선 시각화
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history['train_loss'], label='Train Loss')
plt.plot(history['val_loss'], label='Val Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Loss Curves')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(history['train_acc'], label='Train Acc')
plt.plot(history['val_acc'], label='Val Acc')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.title('Accuracy Curves')
plt.grid(True)
plt.tight_layout()
plt.show()
3.9 모델 평가
# 최고 모델 로드
model.load_state_dict(torch.load('best_mnist_model.pth'))
# 평가
test_loss, test_acc = validate(model, test_loader, criterion, device)
print(f'Test Accuracy: {test_acc:.2f}%')
# 예측
model.eval()
all_preds = []
all_labels = []
with torch.no_grad():
for inputs, labels in test_loader:
inputs = inputs.to(device)
outputs = model(inputs)
_, predicted = outputs.max(1)
all_preds.extend(predicted.cpu().numpy())
all_labels.extend(labels.numpy())
# 혼동 행렬
cm = confusion_matrix(all_labels, all_preds)
print("\nConfusion Matrix:")
print(cm)
print("\nClassification Report:")
print(classification_report(all_labels, all_preds))
4. 성능 개선 팁
기대 성능
- 기본 모델: ~97-98%
- 최적화된 모델: ~98-99%
개선 방법
1. 더 깊은 모델
# 층 추가
layers.Dense(1024, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(256, activation='relu'),
layers.Dense(128, activation='relu'),
2. 데이터 증강
from tensorflow.keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=10,
width_shift_range=0.1,
height_shift_range=0.1,
zoom_range=0.1
)
3. CNN 사용 (훨씬 더 좋은 성능)
# 차후 CNN 학습에서 다룰 예정
# Conv2D 사용하면 99%+ 가능
5. 실습 과제
과제 1: Fashion MNIST
- MNIST와 동일한 형식
- 의류 이미지 분류 (티셔츠, 바지, 신발 등 10개 클래스)
- 목표: 90% 이상
from tensorflow.keras.datasets import fashion_mnist
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()
과제 2: 하이퍼파라미터 튜닝
- 학습률 변경: 0.0001, 0.001, 0.01
- 배치 크기 변경: 32, 64, 128, 256
- Dropout 비율 변경: 0.1, 0.3, 0.5
- 어떤 조합이 가장 좋은지 실험
과제 3: 정규화 기법 비교
- 모델 A: Dropout만
- 모델 B: Batch Norm만
- 모델 C: Dropout + Batch Norm
- 모델 D: 정규화 없음
- 성능 비교
6. 다음 단계
배운 것
- ✅ 전체 딥러닝 워크플로우
- ✅ 데이터 전처리
- ✅ 모델 구축 및 학습
- ✅ 성능 평가
- ✅ Keras와 PyTorch 구현
다음 학습
-
컴퓨터 비전 (CNN) - 이미지에 특화된 신경망
- Convolution Layer
- Pooling Layer
- 대표 아키텍처 (ResNet, VGG 등)
-
자연어 처리 (RNN, Transformer) - 텍스트 처리
- RNN, LSTM
- Transformer
- BERT, GPT
핵심 요약
워크플로우
- 데이터 로드 및 전처리
- 모델 구축
- 컴파일 (손실, 옵티마이저)
- 콜백 설정
- 학습
- 평가 및 시각화
기억할 것
- ✅ 데이터 정규화는 필수
- ✅ 검증 세트로 모니터링
- ✅ 학습 곡선 시각화
- ✅ Early Stopping 사용
- ✅ 최고 모델 저장
← 딥러닝 기초