11. 파이토치(PyTorch) 튜토리얼 - 분류기(Classifier) 학습하기

Yeonghyeon·2022년 8월 4일
0
post-custom-banner

본 포스팅은 파이토치(PYTORCH) 한국어 튜토리얼을 참고하여 공부하고 정리한 글임을 밝힙니다.


데이터

  • 일반적으로 이미지나 텍스트, 오디오, 비디오 데이터를 다룰 때는 numpy 배열로 불러온 후, 그 배열을 torch.Tensor로 변환해줌
    • 이미지: Pillow, OpenCV
    • 오디오: SciPy, LibROSA
    • 텍스트: python, NLTK, SpaCy
  • 특별한 영상 분야를 위한 torchvision 패키지: ImageNet이나 CIFAR10, MNIST 등과 같이 일반적으로 사용하는 데이터셋을 위한 데이터 로더torchvision.datasets과 데이터 변환기 torch.utils.data.DataLoader가 포함되어 있음

[예제] CIFAR10 데이터셋

  • class 종류: ‘비행기(airplane)’, ‘자동차(automobile)’, ‘새(bird)’, ‘고양이(cat)’, ‘사슴(deer)’, ‘개(dog)’, ‘개구리(frog)’, ‘말(horse)’, ‘배(ship)’, ‘트럭(truck)’
  • 이미지 크기: 3x32x32 (32x32 이미지가 3개 채널의 색상으로 이루어짐)

이미지 분류기 학습하기

  1. torchvision 을 사용하여 CIFAR10의 학습용/테스트용 데이터셋을 불러온 후 정규화 해줌

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

  3. 손실 함수를 정의

  4. 학습용 데이터를 사용하여 신경망을 학습

  5. 테스트용 데이터를 사용하여 신경망을 검사

1. CIFAR10을 불러오고 정규화하기

import torch
import torchvision
import torchvision.transforms as transforms
  • torchvision 데이터셋의 output은 [0,1] 범위를 갖는 PILImage 이미지
  • 따라서 [-1, 1]의 범위로 정규화된 Tensor로 변환 ➡️ torchvision.transforms 필요
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5,))]
)

batch_size = 4

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                        download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                          shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
import matplotlib.pyplot as plt
import numpy as np

# 이미지 보여주는 함수
def imshow(img):
    img = img / 2 + 0.5 # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

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

# 이미지 보여주기
# torchvision.utils.make_grid(): 4차원 (배치, 채널, 높이, 너비)
imshow(torchvision.utils.make_grid(images)) # 그리드 형태로 이미지 보여줌
# 정답(label) 출력
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))

Out:

cat   cat   dog   bird

합성곱 신경망(Convolution Neural Network) 정의하기

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


class Net(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


net = Net()

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

  • 교차 엔트로피 손실(Cross-Entropy Loss)과 모멘텀(momentum) 값을 갖는 SGD 사용
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

4. 신경망 학습하기

for epoch in range(2):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
      # [inputs, labels]의 목록인 data로부터 입력 받음
      inputs, labels = data

      # gradient 매개변수 0으로 초기화
      optimizer.zero_grad()

      # 순전파 + 역전파 + 최적화
      outputs = net(inputs)
      loss = criterion(outputs, labels)
      loss.backward()
      optimizer.step()

      # 통계 출력
      running_loss += loss.item()
      if i % 2000 == 1999: # print every 2000 mini-batches
          print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
          running_loss = 0.0

print('Finished Training')  

Out:

[1,  2000] loss: 2.204
[1,  4000] loss: 1.832
[1,  6000] loss: 1.670
[1,  8000] loss: 1.587
[1, 10000] loss: 1.529
[1, 12000] loss: 1.453
[2,  2000] loss: 1.396
[2,  4000] loss: 1.348
[2,  6000] loss: 1.343
[2,  8000] loss: 1.312
[2, 10000] loss: 1.289
[2, 12000] loss: 1.257
Finished Training
  • 학습 모델 저장
PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

5. 테스트용 데이터로 신경망 검사하기

  • 신경망이 예측한 출력과 진짜 정답(Ground-truth)을 비교하는 방식으로 확인
  • epoch이 2회밖에 안되어 신경망이 전혀 배운 게 없을 수도 있음
dataiter = iter(testloader)
images, labels = dataiter.next()

# 이미지를 출력합니다.
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))

Out:

GroundTruth:  cat   ship  ship  plane
  • 저장했던 모델을 불러와 예측
net = Net()
net.load_state_dict(torch.load(PATH))

outputs = net(images)
  • 출력은 배치당 10개 분류 각각에 대한 값으로 나타남
  • 10개 분류 중 가장 높은 값(모델이 예측한 1위)을 갖는 인덱스를 뽑아서 확인
_, predicted = torch.max(outputs, 1)

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

Out:

Predicted:  cat   ship  car   plane
  • 전체 데이터셋에 대한 예측 성능을 확인
correct = 0
total = 0

# 학습 중이 아니므로 출력에 대한 gradient 계산할 필요 x
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} %')

Out:

Accuracy of the network on the 10000 test images: 54 %
  • 어떤 것을 더 잘 분류하고, 어떤 것을 더 못했는지 알아보자
# 각 분류(class)에 대한 예측값 계산을 위해 준비
correct_pred = {classname: 0 for classname in classes}
total_pred = {classname: 0 for classname in 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[classes[label]] += 1
            total_pred[classes[label]] += 1

# 각 분류별 정확도 출력
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} %')

Out:

Accuracy for class: plane is 70.6 %
Accuracy for class: car   is 87.4 %
Accuracy for class: bird  is 41.8 %
Accuracy for class: cat   is 24.3 %
Accuracy for class: deer  is 44.1 %
Accuracy for class: dog   is 48.6 %
Accuracy for class: frog  is 66.3 %
Accuracy for class: horse is 50.3 %
Accuracy for class: ship  is 53.8 %
Accuracy for class: truck is 53.1 %
post-custom-banner

0개의 댓글