본문 바로가기
AI/Model

[Model] Classifier 분류기 모델

by TSpoons 2025. 2. 4.

https://tutorials.pytorch.kr/beginner/blitz/cifar10_tutorial.html

 

분류기(Classifier) 학습하기

지금까지 어떻게 신경망을 정의하고, 손실을 계산하며 또 가중치를 갱신하는지에 대해서 배웠습니다. 이제 아마도 이런 생각을 하고 계실텐데요, 데이터는 어떻게 하나요?: 일반적으로 이미지

tutorials.pytorch.kr

 
 

1. CIFAR10Dataset 정규화

0.5를 기준으로 정규분포를 따르도록 설정(Normalize)

import torch
import torchvision
import torchvision.transforms as transforms

import matplotlib.pyplot as plt
import numpy as np

class HyperParameters:
    def __init__(self, batch_size=4):
        self.batch_size_ = batch_size # 묶음 단위

    
class CIFAR10Dataset:
    def __init__(self, hp: HyperParameters):
        self.hp = hp
        self.classes = ('plane', 'car', 'bird', 'cat',
                        'deer', 'dog', 'frog', 'horse', 'ship', 'truck') #분류할 종류
        self.transform = transforms.Compose(
            [transforms.ToTensor(),
             transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])         
        self.data_path = './data'
        


    def loadDatasets(self):
        self.train_set = torchvision.datasets.CIFAR10(
            root=self.data_path,
            train=True,
            download=True,
            transform=self.transform
        )
        
        self.test_set = torchvision.datasets.CIFAR10(
            root=self.data_path,
            train=False,
            download=True,
            transform=self.transform
        )
        return self
    
    def createDataLoaders(self):
        if self.train_set is None or self.test_set is None:
            raise ValueError("데이터셋이 로드되지 않았습니다. loadDatasets()를 먼저 호출하세요.")
        
        self.train_loader = torch.utils.data.DataLoader(
            dataset=self.train_set,
            batch_size=self.hp.batch_size_,
            shuffle=True,
            num_workers=2
        )
        
        self.test_loader = torch.utils.data.DataLoader(
            dataset=self.test_set,
            batch_size=self.hp.batch_size_,
            shuffle=False,
            num_workers=2
        )
        return self
    
    def getLoaders(self):
        return self.train_loader, self.test_loader

# 이미지를 보여주기 위한 함수

def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()
    
if __name__ == "__main__": 
    hp = HyperParameters()
     
    trainloader, testloader = CIFAR10Dataset(hp).loadDatasets().createDataLoaders().getLoaders()



    # 학습용 이미지를 무작위로 가져오기
    dataiter = iter(trainloader)
    images, labels = next(dataiter)

    # 이미지 보여주기
    imshow(torchvision.utils.make_grid(images))
    # 정답(label) 출력
    print(' '.join(f'{hp.classes[labels[j]]:5s}' for j in range(hp.batch_size_)))

 

oupput:

bird  frog  deer  plane

 

2. 합성곱 신경망(Convolution Neural Network) 정의

import torch.nn as nn
import torch.nn.functional as F

class ConvNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x, 1) # 배치를 제외한 모든 차원을 평탄화(flatten)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

 

if __name__ == "__main__": 
    hp = HyperParameters()
    
    dataloader = CIFAR10Dataset(hp)
    trainloader, testloader = dataloader.loadDatasets().createDataLoaders().getLoaders()

    net = ConvNet()

 
 

3. 손실 함수와 Optimizer 정의하기

분류모델에서는 교차 엔트로피 손실(Cross-Entropy loss)모멘텀(momentum) 값을 갖는 SGD를 사용합니다.
 
그래서 하이퍼파라미터 클래스를 수정하여 명시하였고,
optimizer의 경우에는 net의 파라미터가 필요하기 떄문에 learning 클래스에 처리

class HyperParameters:
    def __init__(self, batch_size=4, lr=0.001, momentum=0.9):
        self.batch_size_ = batch_size
        self.loss_func = nn.CrossEntropyLoss()
        self.lr_ = lr
        self.momentum_ = momentum

 
 
 

4. 신경망 학습하기

class learning():
    def __init__(self, net, hp:HyperParameters):
        self.net = net
        self.criterion = hp.loss_func
        self.optimizer = optim.SGD(net.parameters(), lr=hp.lr_, momentum=hp.momentum_)
        
    def train(self, trainloader, num_epochs=2): # epoch = 2
        for epoch in range(num_epochs):
            running_loss = 0.0
            for i, data in enumerate(trainloader, 0):
                inputs, labels = data
                        
                # 변화도(Gradient) 매개변수를 0으로 만들고
                self.optimizer.zero_grad()
                         
                # 순전파 + 역전파 + 최적화
                outputs = self.net(inputs)
                loss = self.criterion(outputs, labels)
                loss.backward()
                self.optimizer.step()
				               
                # 통계를 출력
                running_loss += loss.item()
                if i % 2000 == 1999:
                    print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
                    running_loss = 0.0
if __name__ == "__main__": 
    hp = HyperParameters()
    
    dataloader = CIFAR10Dataset(hp)
    trainloader, testloader = dataloader.loadDatasets().createDataLoaders().getLoaders()

    net = ConvNet()

    # 4. 학습
    trainer = learning(net, hp)
    trainer.train(trainloader)

 
학습용 데이터셋을 2회 반복하여 신경망을 학습시켰다.

 
4-1. 학습한 모델을 저장

PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

 

5. 시험용 데이터로 신경망 검사하기

 
신경망이 예측한 출력과 진짜 정답(Ground-truth)을 비교하는 방식으로 확인합니다.
만약 예측이 맞다면 샘플을 〈맞은 예측값(correct predictions)〉 목록에 넣겠습니다.
 
 

if __name__ == "__main__": 
    hp = HyperParameters()
    
    dataloader = CIFAR10Dataset(hp)
    trainloader, testloader = dataloader.loadDatasets().createDataLoaders().getLoaders()

    net = ConvNet()

    # 4. 학습
    # trainer = learning(net, hp)
    # trainer.train(trainloader)
    
    # 저장 및 로드
    PATH = './saved_models/cifar_net.pth'
    # torch.save(net.state_dict(), PATH)
    
    net.load_state_dict(torch.load(PATH))
    # net.load_state_dict(torch.load(PATH, weights_only=True))
    
    dataiter = iter(testloader)
    images, labels = next(dataiter)

    outputs = net(images)
    
    _, predicted = torch.max(outputs, 1)

    print('Predicted: ', ' '.join(f'{hp.classes[predicted[j]]:5s}'
                              for j in range(4)))

 

 
 
 

if __name__ == "__main__":
    gpu_manager = GPUManager()
    device = gpu_manager.setDevice() 
    
    hp = HyperParameters()
    
    dataloader = CIFAR10Dataset(hp)
    trainloader, testloader = dataloader.loadDatasets().createDataLoaders().getLoaders()

    net = ConvNet()
    net.to(device)
    
    # 4. 학습
    # trainer = learning(net, hp)
    # trainer.train(trainloader)
    
    # 저장 및 로드
    PATH = './saved_models/cifar_net.pth'
    # torch.save(net.state_dict(), PATH)
    net.load_state_dict(torch.load(PATH, weights_only=True))
    
    dataiter = iter(testloader)
    images, labels = next(dataiter)

    
    # 이미지를 출력합니다.
    imshow(torchvision.utils.make_grid(images))

    outputs = net(images)
    
    _, predicted = torch.max(outputs, 1)

    print('Predicted: ', ' '.join(f'{dataloader.classes[predicted[j]]:5s}'
                              for j in range(4)))
    
    correct = 0
    total = 0
    # 학습 중이 아니므로, 출력에 대한 변화도를 계산할 필요가 없습니다
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            # 신경망에 이미지를 통과시켜 출력을 계산합니다
            outputs = net(images)
            # 가장 높은 값(energy)를 갖는 분류(class)를 정답으로 선택하겠습니다
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    print(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %')
    
    # 각 분류(class)에 대한 예측값 계산을 위해 준비
    correct_pred = {classname: 0 for classname in dataloader.classes}
    total_pred = {classname: 0 for classname in dataloader.classes}

    # 변화도는 여전히 필요하지 않습니다
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = net(images)
            _, predictions = torch.max(outputs, 1)
            # 각 분류별로 올바른 예측 수를 모읍니다
            for label, prediction in zip(labels, predictions):
                if label == prediction:
                    correct_pred[dataloader.classes[label]] += 1
                total_pred[dataloader.classes[label]] += 1


    # 각 분류별 정확도(accuracy)를 출력합니다
    for classname, correct_count in correct_pred.items():
        accuracy = 100 * float(correct_count) / total_pred[classname]
        print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')

 

 
 

 

6. GPU에서 학습하고, 블록화시키기

 
nvidia-smi로 진행상황 볼 수 있음

class ModelEvaluator:
    def __init__(self, net, dataloader, device):
       self.net = net
       self.dataloader = dataloader
       self.device = device
    
    
    @staticmethod
    def imshow(img):
        img = img / 2 + 0.5     # unnormalize
        npimg = img.numpy()
        plt.imshow(np.transpose(npimg, (1, 2, 0)))
        plt.show()
        
        
    def show_predictions(self, images):
        # 이미지를 GPU로 이동
        images = images.to(self.device)
        
        # 예측 수행
        outputs = self.net(images)
        _, predicted = torch.max(outputs, 1)
        
        # CPU로 다시 이동하여 표시
        images = images.cpu()
        predicted = predicted.cpu()
        
        self.imshow(torchvision.utils.make_grid(images))
        print('Predicted: ', ' '.join(f'{self.dataloader.classes[predicted[j]]:5s}'
                                 for j in range(len(predicted))))
        
    # 모델 전체 평가
    def evaluate(self, testloader):
        correct = 0
        total = 0
       
        with torch.no_grad(): # 학습 중이 아니므로, gradient 계산 안함함
            for data in testloader:
                # images, labels = data 
                images, labels = data[0].to(self.device), data[1].to(self.device)
                outputs = self.net(images)
                
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum().item()
        return 100 * correct / total
   
   
    # 모델 분류기별 평가
    def evaluate_by_class(self, testloader):
        correct_pred = {classname: 0 for classname in self.dataloader.classes}
        total_pred = {classname: 0 for classname in self.dataloader.classes}

        self.net.eval()  # 평가 모드로 설정
        with torch.no_grad():
           for data in testloader:
                # images, labels = data 
                images, labels = data[0].to(self.device), data[1].to(self.device)
                outputs = self.net(images)
                _, predictions = torch.max(outputs, 1)
               
                for label, prediction in zip(labels, predictions):
                    if label == prediction:
                        correct_pred[self.dataloader.classes[label]] += 1
                    total_pred[self.dataloader.classes[label]] += 1
        results = {}
        
        for classname, correct_count in correct_pred.items():
           accuracy = 100 * float(correct_count) / total_pred[classname]
           print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')
           results[classname] = accuracy
        return results
                    
if __name__ == "__main__":
    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
    
    hp = HyperParameters()
    
    dataloader = CIFAR10Dataset(hp)
    trainloader, testloader = dataloader.loadDatasets().createDataLoaders().getLoaders()

    net = ConvNet()
    net.to(device) # GPU
    
    # 4. 학습
    # trainer = Learning(net, hp)
    # trainer.train(device, trainloader)
    
    # 4.1 경로 설정 및 저장 
    PATH = './saved_models/cifar_net.pth'
    # torch.save(net.state_dict(), PATH)
    # 로드
    net.load_state_dict(torch.load(PATH, weights_only=True))
    
    # Prediction and evaluation
    dataiter = iter(testloader)
    images, labels = next(dataiter)
    evaluator = ModelEvaluator(net, dataloader, device)
    
    # 수정된 평가 호출
    evaluator.show_predictions(images)
    overall_accuracy = evaluator.evaluate(testloader)
    print(f'Accuracy of the network on the 10000 test images:  {overall_accuracy:.1f}%')
    class_accuracies = evaluator.evaluate_by_class(testloader)

 
 
 

7. 하이퍼파라미터 조정 후 모델 성능 개선

batch_size : 4 -> 32
epoch : 2 -> 30

test_accuracy 증가

class HyperParameters:
    def __init__(self, batch_size=32, lr=0.001, momentum=0.9, num_epoch=30):
        self.batch_size_ = batch_size
        self.loss_func = nn.CrossEntropyLoss()
        self.lr_ = lr
        self.momentum_ = momentum
        self.epoch_ = num_epoch

 

 

'AI > Model' 카테고리의 다른 글

[Model] MNIST 문자 인식 모델 구조화 및 설명  (1) 2025.01.18
[Pytorch] MNIST 문자 인식 모델  (2) 2025.01.10