본문 바로가기
Python/ML&DL

[파이썬, Python] 딥러닝 - 1️⃣ CNN(Convolutional Neural Network

by coding-choonsik 2023. 7. 1.
728x90
반응형
SMALL

1. CNN(Convolutional Neural Network)

  • 합성곱 인공 신경망
  • 전통적인 뉴럴 네트워크에 컨볼루셔널 레이어를 붙인 형태
  • 컨볼루셔널 레이어를 통해 입력 받은 이미지에 대한 특징(Featuer)을 추출하게 되고 추출한 특징을 기반으로 기존의 뉴럴 네트워크에 이용하여 분류

▲ CNN의 원리

 

 

[CNN체험] https://adamharley.com/nn_vis/

 

 

An Interactive Node-Link Visualization of Convolutional Neural Networks

An Interactive Node-Link Visualization of Convolutional Neural Networks Adam W. Harley Abstract Convolutional neural networks are at the core of state-of-the-art approaches to a variety of computer vision tasks. Visualizations of neural networks typically

adamharley.com


 

1-1. CNN을 사용하는 이유

  • 이미지를 분류할 때 DNN(Deep Neural Network)의 문제점
  • 일반적인 DNN은 1차원 형태의 데이터를 사용 ➡️ 2차원 이상의 데이터가 입력되는 경우는 flatten 시켜서 한줄로 데이터를 변환 후 넣음
  • 이미의 공간적/지역적 정보가 손실됨
DNN의 문제점을 해결하기 위해 이미지를 그대로(Raw Input) 받으므로 공간적/지역적 정보를 유지

 

 

1-2. 이미지 데이터(Image Data)

  • 컬러 이미지는 3개의 채널(width, height, channel)로 이루어진 텐서(Tensor)
  • 컴퓨터는 이미지를 숫자로 인식하여 연산을 함

 

▲ Color Image의 구조 

  • 이미지의 정보는 0 ~ 255 까지의 255개의 숫자로 표현
  • 빨강(R) 255, 파랑(B) 255, 초록(G) 255은 흰색
  • 빨강 255, 파랑 0, 초록 0은 빨강
  • 빨강 0, 파랑 255, 초록 0은 파랑
  • 빨강 0, 파랑 0, 초록 255은 초록

2. Convolutional 연산

 

2-1. 연산의 원리

▲ Convolutional 연산의 원리, Input 텐서의 크기보다 Result의 크기가 더 작아짐

 

  • 컨볼루션 연산을 진행하면 출력 텐서의 크기가 작아짐

▲ 컬러 이미지의 컨볼루션 연산

  • 컬러 이미지에는 2D 컨볼루션 연산을 사용

2-2. 패딩(Padding)

  • 입력값 주위로 0(통상적으로)을 넣어서 입력값의 크기를 인위적으로 키워 결과값이 작아지는 것을 방지
  • 출력 크기를 동일하게하기 위해 패딩(padding)을 사용 (시작할 때 없는 테두리 값을 주면 동일한 크기의 output이 나옴)

▲ padding의 원리


2-3. 풀링(Pooling)

  • 중요한 특징을 추출하고 차원을 축소하기 위해 Pooling 연산을 사용
    • MaxPool(MaxPool2D)
    • AvgPool(AvgPool2D)

 

▲ pooiing의 원리


2-4. 스트라이드(stride)

  • 필터를 적용하는 간격의 설정(필터를 적용하는 단계 간의 이동 거리)
  • 스트라이드의 값은 일반적으로 1보다 크거나 같은 자연수로 설정
  • 필터를 적용해서 얻어낸 결과를 Feature map 또는 Activation map이라고 부름

▲ stride가 1일 때


2-5. 드롭아웃 레이어(Dropout Layer)

  • 오버피팅(과적합)을 막기 위해 사용하는 레이어
  • 학습중일 때 랜덤하게 값을 발생하여 학습을 방해함으로 학습용 데이터의결과가 치우치는 것을 방지함
  • 학습 데이터에 지나치게 적합되는 것을 억제하고, 다양한 특징을 학습할 수 있도록 도와줌

▲ dropout 적용


2-6. FC Layer(Fully Connected) 레이어

  • 이미지를 분류 또는 설명하기 위해 예측하는 레이어
  • 이전 레이어의 모든 뉴런과 연결된 가중치를 갖고 있음
  • 각 입력 뉴런은 출력 뉴런과 fully connected되어 있으며, 입력과 가중치의 곱을 통해 출력 값을 계산
  • 계산된 출력 값은 활성화 함수를 통과한 뒤 다음 레이어로 전달
  • 전체 신경망 구조에서 FC Layer는 주로 마지막 레이어로 사용

3. CNN을 구성하는 레이어

 

 

  • Conv2D: 특징 추출
  • ReLU: 활성화 함수
  • MaxPool2D: 차원 축소
  • Conv2D: 특징 추출
  • ReLU: 활성화 함수
  • MaxPool2D: 차원 축소
    ...
  • Flatten: 다차원에서 1차원으로 변경
  • Linear: 선형 회귀
  • ReLU: 활성화 함수
    ...
  • Sigmoid(or Softmax)

3-1. 간단한 CNN 모델 실습

import torch
import torch.nn as nn
import torch.optim as optim

# 배치 크기 * 채널(1: grayscale, 3: color) * 높이 * 너비
inputs = torch.Tensor(1, 1, 28, 28)
print(inputs.shape)

>>> torch.Size([1, 1, 28, 28])

✅ 첫번째 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)  # 출력이 32개

>>> torch.Size([1, 32, 28, 28])

 

✅ 2번째 MaxPool2D

# MaxPool2d(kernel_size: 스트라이드가 2(2칸씩 오른쪽 아래로 이동))
pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)  # torch.Size([1, 32, 14, 14]): 크기가 줄음

>>> torch.Size([1, 32, 14, 14])

 

✅ 두번째 Conv2D

# Conv2d(입력 데이터가 32개, 출력되는 피쳐의 수 64개, 마스크가 3*3짜리(기울기), padding='same': 테두리를 채워서 크기를 유지)
conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding='same')
out= conv2(out)
print(out.shape)  # 출력이 64개

>>> torch.Size([1, 64, 7, 7])

 

✅ 두번째 MaxPool2D

# MaxPool2d(kernel_size: 스트라이드가 2(2칸씩 오른쪽 아래로 이동))
pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)  # torch.Size([1, 64, 7, 7]): 크기가 줄음

>>> torch.Size([1, 64, 7, 7])

 

✅ Flatten(배치를 제외한 3차원 데이터를 1차원으로 펼침 ➡️ 1차원으로 만들어야 nn.Linear() 레이어에 넣을 수 있음)

flatten = nn.Flatten()
out = flatten(out)
print(out.shape)  # 64 * 7 * 7 = 3136(한줄로 펼쳐짐)

>>> torch.Size([1, 3136])

Dense(Fully Connected) 레이어

# 선형 회귀
# Linear(3136개의 입력값, 10개의 결과값(0~9))
fc = nn.Linear(3136, 10)
out = fc(out)
print(out.shape)  # torch.Size([1, 10]): 10개의 확률로 나옴

>>> torch.Size([1, 10])

4. CNN으로 MNIST 분류하기

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

✅ GPU 환경 설정

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)

>>> cuda

 

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

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)

>>> Dataset MNIST
    Number of datapoints: 60000
    Root location: data
    Split: Train
    StandardTransform
Transform: ToTensor()
Dataset MNIST
    Number of datapoints: 10000
    Root location: data
    Split: Test
    StandardTransform
Transform: ToTensor()

 

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

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 모델 만들기

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)

>>> 
Sequential(
  (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=same)
  (1): ReLU()
  (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (3): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same)
  (4): ReLU()
  (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (6): Flatten(start_dim=1, end_dim=-1)
  (7): Linear(in_features=3136, out_features=10, bias=True)
)

 

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

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+1: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_loss = sum_losses / len(test_loader)
avg_acc = sum_accs / len(test_loader)

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

>>> 테스트 정확도는 99.014732% 입니다

 

 

728x90
반응형
LIST