Python/딥러닝(DL)

Python(34)- CNN 모델링

두설날 2024. 6. 20. 14:54

*이 글을 읽기전에 작성자 개인의견이 있으니, 다른 블로그와 교차로 읽는것을 권장합니다.*

1. CNN(Convolutional Neural Networks)

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

  • 이미지같은 경우 위치정보(x,y)좌표를 가지고 있는데, 비슷한 좌표를 학습하는데 더 많은 데이터를 필요. 좌표를 잃어버리지 않는 2차원 이미지를 그대로 넣는 convolution 과정 필요

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

  • 이미지를 분류할 때 DNN(Deep Nerual Network)의 문제점
    • 일반적인 DNN은 1차원 형태의 데이터를 사용해야 함
    • 2차원 이상의 데이터가 입력되는 경우 flatten 시켜서 한 줄로 데이터를 변환 후 넣어야 함
    • 이미지의 공간적/지역적 정보가 손실
  • CNN은 이미지를 그대로(Raw Input)받음으로 공간적/지역적 정보를 유지
    • 참고: Raw Input은 행 입력이 아닌, 데이터 그대로 입력을 말함

1-2. CNN에서의 이미지 데이터

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

  • 이미지 정보는 0~255까지의 256개의 숫자로 표현
  • 빨강 255, 파랑 255, 녹색 255 -> 흰색
  • 빨강 0, 파랑 0 , 녹색 0 -> 검정
  • 빨강 255, 파랑 0, 녹색 0 -> 빨강


2. Convolution 연산

  • 컨볼루션 연산을 진행하면 출력 텐서의 크기가 작아짐
  • (6,6) * (3,3) = (4,4)
  • 여기서 사용되는 컨볼루션 필터는 코드로 kernel_size로 사용

  • 패딩 : 입력값 주위로 인위적으로 0을 넣어서 입력값의 크기를 키워 결과값이 작아지는 것을 방지

  • 2D 컨볼루션 : 컬러이미지에서는 2D 컨볼루션 연산을 수행

  • 풀링 : 중요한 특징을 추출하고 차원을 축소하기 위해 풀링 연산을 사용MaxPool 또는 AvgPool 사용
  • 스트라이드 : 필터를 적용하는 간격을 설정

  • 드롭아웃(dropout) 레이어
    • 오버피팅을 막기 위해 사용하는 레이어
    • 학습중일 때, 랜덤하게 값을 발생하여 학습을 방해함으로
  • FC 레이어
    • 이미지를 분류 또는 예측하기 위해 사용되는 레이어

3. CNN을 구성하는 레이어

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


4. 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

CNN은 이미지 처리에 사용되나 이미지 처리만을 위한 모델은 아니고, 자연어 처리도 수행합니다

import torch
import torch.nn as nn # 뉴럴 네트워크
import torch.optim as optim
# 배치크기 * 채널(1: 그레이스케일, 3: 컬러) * 너비 * 높이
inputs = torch.Tensor(1, 1, 28, 28)
print(inputs.shape)

# 첫번째 Conv2D
# 32개 feature, 3X3 생성
conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding='same')
out = conv1(inputs)
print(out.shape)

28X28X1 3D 차원에 +1차원 더해서 4차원 생성

padding = 'same' : 너비X높이 동일 설정

kernel_size : 컨볼루션 필터 크기 지정

conv 는 convolution 축약

pool은 pooling축약

# 첫번째 MaxPool2D
pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)

# 두번째 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)

# 텐서 차원 평탄화 -> 1차원
flatten = nn.Flatten()
out = flatten(out)
print(out.shape) # 64 * 7 * 7

# 입력 3136, 출력 10 조정
fc = nn.Linear(3136, 10)
out = fc(out)
print(out.shape)

CNN으로 MNIST 분류하기

import torchvision.datasets as datasets
import torchvision.transforms as trnasforms
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 = trnasforms.ToTensor(),
    download = True
)

test_data = datasets.MNIST(
    root = 'data',
    train = False,
    transform = trnasforms.ToTensor(),
    download = True
)
print(train_data)
print(test_data)

loader = DataLoader(
    dataset = train_data,
    batch_size = 64,
    shuffle = True
)

imgs, labels = next(iter(loader))
figs, axes = plt.subplots(8,8, figsize=(16,16))

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 = nn.Sequential(
    nn.Conv2d(1, 32, kernel_size=3, padding='same'),
    # ReLU(x)=max(0,x)
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2),

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

    nn.Flatten(),
    nn.Linear(64*7*7, 10)
).to(device)

print(model)

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

epochs = 10

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

    for x_batch, y_batch in loader:
        x_batch = x_batch.to(device)
        y_batch = y_batch.to(device)
        y_pred = model(x_batch)
        loss = nn.CrossEntropyLoss()(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        sum_losses = sum_losses + loss
        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_loader = DataLoader(
    dataset = test_data,
    batch_size = 64,
    shuffle = True
)

# iter : 반복자(iterator)를 생성하기 위해 사용하는 내장 함수
imgs, labels = next(iter(test_loader))
fig, axes = plt.subplots(8, 8, figsize=(16, 16))

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)
    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}% 입니다.')