본문 바로가기
AI 컴퓨터 비전프로젝트/[ML,DL]머신러닝,딥러닝

[DL] CNN 모델만들기,MNLIST 분류

by 바다의 공간 2024. 7. 28.

1. 간단한 CNN모델 만들기

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

CNN모델을 간단하게 만들기 위해서 필요한 모듈들을 임포트해주었씁니다.

 

# 배치크기 * 채널 (1: 그레이스킬, 3: 트루컬러) *너비 * 높이
inputs = torch.Tensor(1, 1, 28, 28)
print(inputs.shape)

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

괄호의 의미는 순서대로(배치크기, 채널, 너비, 높이) 입니다.

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

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

지금 이미지 1개를 가지고 가중치를 32개로 늘려줍니다.

kernel_size=3 의미는 학습되는 커널이 되고 3*3 짜리 정방향이 되겠죠

padding을 same으로 하면 다음 레이어에서 이미지 크기가 동일하게 유지됩니다.

(28,28) 부분

 

out변수에는 위에서 만들었던 이미지를 가지고 conv1를 작동을하고 out으로 들어가게 됩니다.

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

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

CNN은 COVD레이어가 있고 크기를 줄이는 MAXPOLL을 사용합니다.

그러면 사이즈가 반인 14로 줄어드는것을 확인 할 수 있습니다.

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

#torch.Size([1, 64, 14, 14])

# 2번째 MaxPool2D
pool = nn.MaxPool2d(kernel_size=2)
out = pool(out)
print(out.shape)
#torch.Size([1, 64, 7, 7])
flatten = nn.Flatten()
out = flatten(out)
print(out.shape)

#torch.Size([1, 3136])

fc = nn.Linear(3136,10)
out =fc(out)
print(out.shape)
#torch.Size([1, 10])

nn.Flatten을 하게되면 1줄로 쭈욱 평평하게 해줍니다.

 

3136이 나온 이유는 64 * 7 * 7 입니다.

 

그 다음 회귀문제(선을 그어서) 3136을 받아서 클래스를 10개로 내보내려고합니다.

그러면 10개의 확률로 내보내줄겁니다.


2. cnn으로 MNLIST 분류하기

import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

#GPU사용
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)

train_data = datasets.MNIST(
    root='data',
    train=True,
    transform=transforms.ToTensor(),
    download=True)

 MNLIST를 작성하면 데이터를 가져올 수 있습니다.

root는 폴더명이 data를 가져오는거입니다.(리눅스랑 비슷한)

trasform = transform.ToTenson()  = ndarray로 가져오는것이 아니라 애초부터 tensor형으로 가져옵니다.

download를 하게되면 파일에 data파일이 생성되게 됩니다.

 

똑같이 test_data로 가져오겠습니다.

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

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

test_data

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

 

이렇게 데이터셋이 준비가 되었죠.

 

이제 데이터로더를 만들겠습니다.

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

imgs, labels = next(iter(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')

batch_size = 64  == 배치사이즈는 64개 단위로 하겠습니다.

 

그 이후 

img, labels = next(iter(loader)) 순차적으로 나열되는데이터고 끄집어낸것을 ,imgs, labels로 넣고

서브플랏으로 보여주게 만들고

라벨은 타이틀에 찍어주는걸로 만들었습니다.

들어가있는 데이터를 출력을

한 결과입니다. 총 64개정도 나옵니다.

 

 

모델만들기

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),
    nn.Flatten(),
    nn.Linear(64*7*7, 10)
).to(device)

여기서 이제 히든레이어를 쌓아주는 작업입니다

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):
    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}%')

여기까지 쓰고 돌리려면 gpu를 사용을해야 좀 더 빨라집니다.

test_loader = DataLoader(
    dataset=test_data,
    batch_size=64,
    shuffle=False
)

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

#테스트 정확도는 98.95% 입니다!

구글코랩 압축된 파일 풀기

1. 구글코랩 에 드라이브 연결

2. 트리를 내린 후 압축폴더를 복사해줍니다. (폴더로 이동)

%cd 경로
%체인지 디렉토리

3. 압축푸는 코드 작성하기

!unzip -qq "경로"

 

이렇게하면 압축이 풀립니다.


 

3. ○, X, △ 분류하기

 

○, X, △ 를 그림판에 여러가지 이미지를 저장 후 cnn 으로 학습을 시켜 해당 데이터를 분류하는 모델을 만들어보자.

 

#데이터경로 확인하기

1. 트레인경로

2.테스트 경로

#데이터 경로
train_path = '/content/drive/MyDrive/컴퓨터 비전 시즌2/4. 딥러닝 기초/Data/shape/train'
test_path = '/content/drive/MyDrive/컴퓨터 비전 시즌2/4. 딥러닝 기초/Data/shape/test'

 

todo-list

#이미지크기를  28*28 변환

#그레이스케일 적용 

#텐서로 변환

#정규화 적용

-연산도 작아지고, 크기도 작아지는 효과

#색반전 (배경 흰색, 글 검정-> 배경 검정, 글 흰색)

# 이미지 28*28 변환
# 그레이스케일 적용
# 텐서전환
# 정규화 적용
# 색반전(경 흰색, 글 검정 -> 배경 검정, 글 흰색)

transformer = transforms.Compose([
    transforms.Resize((28, 28)), #이미지 사이즈 변환
    transforms.Grayscale(1),
    transforms.ToTensor(), # 텐서형전환
    transforms.Normalize((0.5), (0.5)), #평균, 표준편차
    transforms.RandomInvert(1) #색 반전
])
trainset = torchvision.datasets.ImageFolder(root=train_path, transform=transformer)
testset = torchvision.datasets.ImageFolder(root=test_path, transform=transformer)

 

여기서 각 개수를 확인해보면 (len함수 활용)

트레인셋, 테스트 셋은 각각 (240, 60)개로 확인됩니다.

trainset.classes, testset.classes

#(['cir', 'tri', 'x'], ['cir', 'tri', 'x'])

어떻게 이미지를 알아보고 3개로 분할을 했을까 하지만

폴더이름으로 나누는것입니다.

class_map = {
    0: 'cir',
    1: 'tri',
    2: 'x'
}

데이터로더를 만들어주고

loader = DataLoader(
    dataset=trainset,
    batch_size=8,
    shuffle=True
)

이미지 시각화를 해줍니다.

imgs, labels = next(iter(loader))
fig, axes = plt.subplots(4,2, figsize=(8, 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')

여기서 한번 오류가 났는데 이유가

subplots 를 subplot으로 적어서 그랬습니다.

이렇게 랜덤 데이터셋을 예측하는 결과가 나오게 됩니다.

#모델만들기
model = nn.Sequential(
    nn.Conv2d(1, 32, kernel_size=3, padding='same'),
    nn.ReLU(),

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

    nn.MaxPool2d(kernel_size=2),
    nn.Dropout(0.25),

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

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

    nn.MaxPool2d(kernel_size=2),
    nn.Dropout(0.25),

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

#Sequential(
#  (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=same)
#  (1): ReLU()
#  (2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=same)
#  (3): ReLU()
#  (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#  (5): Dropout(p=0.25, inplace=False)
#  (6): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same)
#  (7): ReLU()
#  (8): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=same)
#  (9): ReLU()
#  (10): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#  (11): Dropout(p=0.25, inplace=False)
#  (12): Flatten(start_dim=1, end_dim=-1)
#  (13): Linear(in_features=3136, out_features=3, bias=True)
#)

 

학습 돌려보기

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

epochs = 20

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=testset,
    batch_size=8,
    shuffle=True
)

시각화 만들기

imgs, labels = next(iter(test_loader))
fig, axes = plt.subplots(2, 4, figsize=(8, 4))

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

 

 

평가하기

- 학습모드가 아닌 테스트 모드로 전환을 했습니다.

- 모델 소프트맥스로 다 똑같이 주도록 하겠습니다.

 

img_list ,y_pred_list, y_pred_list 는 변수로 비교하기 위해서 만들었습니다.

model.eval()

sum_accs = 0

img_list    = torch.Tensor().to(device)
y_pred_list = torch.Tensor().to(device)
y_true_list = torch.Tensor().to(device)


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)

    y_pred_list = torch.cat((y_pred_list, y_pred_index), dim=0)
    y_true_list = torch.cat((y_true_list, y_batch), dim=0)
    img_list = torch.cat((img_list, x_batch), dim=0)

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

결과값은 테스트 정확도는 87.50% 입니다.이 나옵니다

 

 

결과 확인하

fig, axes = plt.subplots(12, 5, figsize=(25, 25))

img_list_cpu = img_list.cpu()
y_pred_list_cpu = y_pred_list.cpu()
y_true_list_cpu = y_true_list.cpu()

for ax, img, y_pred, y_true in zip(axes.flatten(), img_list_cpu, y_pred_list_cpu, y_true_list_cpu):
  ax.imshow(img.reshape(28, 28), cmap='gray')
  ax.set_title(f'pred: {class_map[y_pred.item()]}, true: {class_map[y_true.item()]}')
  ax.axis('off')

plt.show()


과적합이 일어난것같다면?

nn.Dropout(0.25) 를 해주면 약 25퍼센트정도 데이터에 흠집을 내줘서(값을 살짝 변형되게)

해줍니다 그러면 오버피팅이 덜 일어납니다.