카테고리 없음

손글씨 도형 분류하기(CNN)

dldbwls0818 2025. 2. 2. 05:24

1. 손글씨 도형

- 그림판으로 그린 손글씨 이미지 총 300개의 이미지를 압축시켜놓은 파일입니다.

shape.zip
0.20MB

사용하던 data 폴더에 압축파일을 옮겨주세요

 

# Python에서 사용하는 파일 시스템 경로 변경 명령으로
# 현재 디렉토리를 변경해줍니다
%cd /content/drive/MyDrive/(상위폴더 1)/(상위폴더 2)/data
# 압축풀기
# -qq : quickmode 결과를 출력을 하지 않음.
!unzip -qq "/content/drive/MyDrive/(상위폴더 1)/(상위폴더 2)/data/shape.zip"
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt

transform = transforms.Compose([
	transforms.Resize((28, 28)),
    # Grayscale(1) : 모든 이미지를 Grayscale로 변환, 0.5입력 시 50프로만 랜덤하게
    transforms.Grayscale(1),
    transforms.ToTensor(),
    # RandomInvert(1) : 모든 이미지 색상 반전, 0.5입력 시 50프로만 랜덤하게
    transforms.RandomInvert(1),
    transforms.Normalize((0.5), (0.5)),
])

train_path = '/content/drive/MyDrive/(상위폴더 1)/(상위폴더 2)/data/shape/train'
test_path = '/content/drive/MyDrive/(상위폴더 1)/(상위폴더 2)/data/shape/test'

trainset = torchvision.datasets.ImageFolder(
    root=train_path,
    transform=transform
)
testset = torchvision.datasets.ImageFolder(
    root=test_path,
    transform=transform
)
len(trainset), len(testset)

# 10번째 데이터 가져오기
trainset.__getitem__(10)

# 데이터셋의 class 확인하기
trainset.classes, testset.classes

loader = DataLoader (
    dataset = trainset,
    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(class_map[label.item()])
    ax.axis('off')
    
# 장치 확인
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(device)

2. CNN 모델 만들기

class ConvNeuralNetwork(nn.Module) :
	def __init__(self) :
    	super(ConvNeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.classifier = nn.Sequential(
        	# Conv layer 1
            nn.Conv2d(1, 28, kernel_size=3, padding='same'),
            nn.ReLU(),

            # Conv layer2
            nn.Conv2d(28, 28, kernel_size=3, padding='same'),
            nn.ReLU(),

            # Max Pooling layer
            nn.MaxPool2d(kernel_size=2),
            nn.Dropout(0.25),

            # Conv layer3
            nn.Conv2d(28, 56, kernel_size=3, padding='same'),
            nn.ReLU(),

            # Conv layer4
            nn.Conv2d(56, 56, kernel_size=3, padding='same'),
            nn.ReLU(),
            
            # Max Pooling layer
            nn.MaxPool2d(kernel_size=2),
            nn.Dropout(0.25)
        )
        self.Linear = nn.Linear(56 * 7 * 7, 3)
        
	def forward(self, x) :
        x = self.classifier(x)
        x = self.flatten(x)
        output = self.Linear(x)
        return output

2-1. CNN 모델 학습시키기

model = ConvNeuralNetwork().to(device)
print(model)

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

def train_loop(loader, model, loss_fn, optimizer) :
    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 = loss_fn(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        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 += acc

    avg_loss = sum_losses / len(loader)
    avg_acc = sum_accs / len(loader)
    return avg_loss, avg_acc
    
epochs = 50

for i in range(epochs) :
    print(f"------------------------------------------------")
    avg_loss, avg_acc = train_loop(loader, model, loss, optimizer)
    print(f'Epoch: {i+1:4d}/{epochs}, Loss: {avg_loss:.6f}, Accuracy: {avg_acc:.2f}%')
print('Done!!')

2-2. CNN 모델 Test하기

test_loader = DataLoader(
    dataset=testset,
    batch_size=32,
    shuffle=False
)

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

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')
def test(model, test_loader) :
    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)

        # torch.cat((y_pred_list, y_pred_index), dim = 0) : 행(0)으로 합치기
        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 += acc

    avg_acc = sum_accs / len(test_loader)
    return y_pred_list, y_true_list, img_list, avg_acc
    
y_pred_list, y_true_list, img_list, avg_acc = test(model, test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다.')
fig, axes = plt.subplots(4, 8, figsize=(16, 8))

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()

3. 모델 저장하고 불러오기

# 모델의 가중치(Weight)와 매개변수(Parameters)만 저장
# 모델의 구조가 저장되지 않으므로 모델 클래스 정의가 없으면 복원 할 수 없음
torch.save(model.state_dict(), 'model_weights.pth')

model2 = ConvNeuralNetwork().to(device)
print(model2)

# 모델을 만들고 학습을 하지 않았기 때문에 정확도는 매우 떨어질 것이다
y_pred_list, y_true_list, img_list, avg_acc = test(model2, test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다.')

# 저장된 모델의 파라미터 불러오기
model2.load_state_dict(torch.load('model_weights.pth'))

# 학습을 마치고 난 이후의 파라미터들이 저장되어있는 것을 가져다 사용했기 때문에
# 학습을 마치고 난 이후의 결과가 나올 것이다
y_pred_list, y_true_list, img_list, avg_acc = test(model2, test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다.')

4. 모델 전체 저장하기

- 모델 Class와 Weight가 함께 저장되므로, 복원 시 모델 구조를 별도로 정의할 필요가 없습니다.

torch.save(model, 'model.pth')

model3 = torch.load('model.pth')

# 모델의 구조 자체까지도 저장되었기 때문에 바로 사용이 가능하고
# 정확도도 학습을 마친 정확도가 나오게 된다
y_pred_list, y_true_list, img_list, avg_acc = test(model3, test_loader)
print(f'테스트 정확도는 {avg_acc:.2f}% 입니다.')