본문 바로가기
머신러닝 & 딥러닝

20. 간단한 CNN 모델 만들기

by 곽정우 2024. 6. 17.

import torch
import torch.nn as nn
import torch.optim as optim
# 배치크기 * 채널 * 너비 * 높이

inputs = torch.Tensor(1,1,28,28)
print(inputs.shape)


첫번째 Conv2D

# Conv2d(입력 데이터가 1개, 출력되는 피쳐의 수 32개, 마스크가 3*3짜리(기울기), padding='same': 테두리를 채워서 크기를 유지)

conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding='same')
out = conv1(inputs)
print(out.shape)

# 우선 CNN 레이어를 설정하는 과정에서 컨볼루션 연산을 수행합니다.
# 여기서 kernel_size는 필터의 크기를 의미합니다.
# padding='same'은 입력 이미지의 크기를 유지하기 위한 설정입니다.
# 따라서 이 설정으로 인해 출력 채널 수가 이전보다 32개 증가한 것을 확인할 수 있습니다.


첫번째 MaxPool2D

pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)

# 다음으로는 풀링 작업을 수행하여 이미지의 중요하지 않은 부분을 제거합니다.
# 이를 위해 이전에 설정한 28의 너비와 높이를 줄이기 위해 kernel_size를 조절합니다.
# 결과적으로 높이와 너비가 절반으로 줄어든 것을 확인할 수 있습니다.

두번째 Conv2D

conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding='same')
out = conv2(out)
print(out.shape)


두번째 MaxPool2D

pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)

flatten = nn.Flatten()
out = flatten(out)
print(out.shape)  # 64 * 7 * 7 = 3136

 

fc = nn.Linear(3136, 10)
out = fc(out)
print(out.shape)


CNN으로 MNIST 분류하기

  • MNIST는 손으로 쓴 숫자(0부터 9까지)로 이루어진 대표적인 데이터셋
import torchvision.datasets as datasets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)

# 학습 데이터셋과 테스트 데이터셋 불러오기

train_data = datasets.MNIST(
    root = 'data',   # 데이터셋이 저장될 디렉토리 경로
    train=True,      # 훈련 데이터를 가져옴
    transform=transforms.ToTensor(),  # 이미지 데이터를 텐서로 변환하는 변환
    download=True   # 데이터셋이 로컬에 없을 경우 인터넷에서 데이터를 자동으로 다운로드
)
test_data = datasets.MNIST(
    root = 'data',
    train=False,    # test용으로 설정하여 로드
    transform=transforms.ToTensor(),  # 이미지 데이터를 텐서로 변환하는 변환
    download=True
)


print(train_data)
print(test_data)

# 데이터로더를 사용하여 학습 데이터셋을 배치 단위로 로드하기

loader = DataLoader(
    dataset = train_data,   # 훈련 데이터셋을 배치단위로 로드
    batch_size=64,  # 배치 크기 64
    shuffle=True  # 데이터를 섞어줌
)
# 학습 데이터셋 시각화

imgs, labels = next(iter(loader))  # 데이터로더에서 배치를 가져옴

fig, axes = plt.subplots(8, 8, figsize=(16, 16))  # 16*16픽셀의 이미지데이터가 8행 8열로 만드는 subplot

for ax, img, label in zip(axes.flatten(), imgs, labels):
  ax.imshow(img.reshape((28,28)), cmap='gray')  #  이미지를 28x28 크기로 변형, 흑백으로 표시
  ax.set_title(label.item())  # lable값을 제목으로 설정
  ax.axis('off')  # 축 숨김

# 이제 구성된 CNN 모델을 활용하여 MNIST 데이터셋을 학습시키려 합니다.
# 필요한 파이썬 모듈을 불러옵니다.
# MNIST 데이터셋을 train과 test로 분할합니다.
# 이후 train 데이터셋을 DataLoader로 변환합니다.
# 여기서 batch_size는 한 번에 처리할 데이터의 개수를 의미합니다.

# CNN 모델 만들기

model = nn.Sequential(
    nn.Conv2d(1, 32, kernel_size=3, padding='same'),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2),

    nn.Conv2d(32, 64, kernel_size=3, padding='same'),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2),

    # Flatten
    nn.Flatten(),
    nn.Linear(7 * 7 * 64, 10)
).to(device)  # gpu로 보냄

print(model)

# 모델을 정의합니다.
# 입력 채널 1개를 받아서 출력 채널을 32개로 만드는 첫 번째 Convolutional 레이어를 정의합니다.
# 각 필터의 크기는 3x3으로 설정됩니다.
# 출력 텐서의 높이와 너비를 반으로 줄이기 위해 stride를 2로 설정하여 MaxPooling을 두 번 반복합니다.
# 최종적으로 입력 이미지 구조는 (1, 64, 7, 7)이 되며, 총 64개의 채널로 나누어지고, 높이와 너비는 각각 7이 됩니다.

# 학습 데이터로 모델 학습시키기

optimizer = optim.Adam(model.parameters(), lr=0.001)

epochs = 10

for epoch in range(epochs+1):
  sum_losses = 0
  sum_accs = 0

  for x_batch, y_batch in loader:
    x_batch = x_batch.to(device)  # gpu 연산을 위해 보냄
    y_batch = y_batch.to(device)
    y_pred = model(x_batch)

    loss = nn.CrossEntropyLoss()(y_pred, y_batch)  # 3개 이상 클래스로 분류 -> CrossEntropyLoss
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # 배치 단위 loss 저장
    sum_losse = sum_losses + loss.item()
    # 배치 단위 정확도 저장
    y_prob = nn.Softmax(1)(y_pred)
    y_pred_index = torch.argmax(y_prob, axis=1)
    acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
    sum_accs = sum_accs + acc

  avg_loss = sum_losses / len(loader)
  avg_acc = sum_accs / len(loader)

  print(f'Epoch: {epoch:4d}/{epochs}  Loss: {avg_loss:6f} Accuracy: {avg_acc:2f}%')

# 데이터로더를 사용하여 test 데이터셋을 배치 단위로 로드하기

test_loader = DataLoader(
    dataset = test_data,
    batch_size=64,
    shuffle=True
)
# test 데이터셋 시각화

imgs, labels = next(iter(test_loader))

fig, axes = plt.subplots(8, 8, figsize=(16, 16))  # 16*16픽셀의 이미지데이터가 8행 8열로 만드는 subplot

for ax, img, label in zip(axes.flatten(), imgs, labels):
  ax.imshow(img.reshape((28,28)), cmap='gray')
  ax.set_title(label.item())
  ax.axis('off')

 

평가모드로 전환하는 이유

테스트나 추론 과정에서 일관된 결과를 얻기 위해,  드롭아웃이나 배치 정규화와 같은 학습 중에만 적용되는 기법들을 비활성화하여, 모델의 성능을 평가합니다.

# 모델을 테스트 모드로 전환하여 모델 성능 평가하기

model.eval()  # 모델을 테스트 모드로 전환

sum_accs = 0

for x_batch, y_batch in test_loader:
  x_batch = x_batch.to(device)  # gpu 연산을 위해 보냄
  y_batch = y_batch.to(device)
  y_pred = model(x_batch)
  y_prob = nn.Softmax(1)(y_pred)
  y_pred_index = torch.argmax(y_prob, axis=1)
  acc = (y_batch == y_pred_index).float().sum() / len(y_batch) * 100
  sum_accs = sum_accs + acc

avg_acc = sum_accs / len(test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다')

'머신러닝 & 딥러닝' 카테고리의 다른 글

22. 포켓몬 분류  (2) 2024.06.17
21. 전이 학습  (0) 2024.06.17
19. CNN 기초  (0) 2024.06.13
18. 비선형 활성화 함수  (0) 2024.06.13
17. 딥러닝  (0) 2024.06.13