AI 공부 저장소

Softmax Classifier의 개념과 코드 구현 본문

Artificial Intelligence/ML&DL

Softmax Classifier의 개념과 코드 구현

aiclaudev 2022. 2. 10. 17:02

 

본 글은 Sung Kim 교수님의 PyTorchZeroToAll 강의를 토대로 저의 지식을 아주 조금 덧붙여 작성하였습니다.
글 작성에 대한 허락을 받아, 개인 공부용으로 작성합니다.
문제가 발생할 시 비공개로 전환함을 알립니다.
https://www.youtube.com/channel/UCML9R2ol-l0Ab9OXoNnr7Lw

 

 

 

 

① 개요

위와 같은 이미지 데이터를 분류한다고 생각해봅시다. 각 이미지 데이터가 Input으로 들어올텐데요, 그렇다면 Output은 0~9 사이의 숫자일 것입니다. 이러한 상황에서 Softmax Classifier를 사용해볼 수 있습니다.

 

위 그림을 보면, Output이 총 10개인데, 각 Output은 '확률'을 의미합니다. 즉 P(y=0)은 y가 0일 확률, P(y=1)은 

y가 1일 확률을 의미하는 것이죠. 각 데이터에 대해 10개의 확률 Output을 출력하면, 확률이 가장 높은 값으로 y를 선정하는 것 등이 가능합니다.

 

생각해볼 것은, Input이 N X 2 인 Matrix이고 Output이 N X 10 인 Matrix일 때, w는 어떠한 형태의 Matrix이냐는 것인데요, 행렬 곱을 이해하고 계신 분은 쉽게 2 X 10 이어야 한다는 것을 아실 겁니다. 어쨌든, 위 그림처럼 Softmax Classifier는 각 레이블에 속할 '확률'을 Output으로 출력합니다. 

 

 

 

② Softmax란?

Softmax 함수는 위 수식과 같은 형태를 가지고 있습니다. 간단히 해석하자면, Input으로 들어온 값을 '확률' 로 바꾸어주는 것입니다.

 

초기 Input에 대해 Linear를 적용하여 Logits를 출력합니다. 그리고 이 Logits에 대해 Softmax 함수를 적용하여 각 레이블에 속할 확률을 출력합니다.

 

전체적인 과정을 살펴보면 위와 같습니다. 

다시 한번 설명하자면, Input에 대해 Linear Model을 적용하여 Logits를 구하고, 이 Logits에 대해 Softmax를 적용하여 확률 형태의 y(hat)을 출력합니다. 이후, y(hat)을 y와 비교하여 Loss를 구해봐야 하는데요, 이 때 중요한 점은 y의 형태가 1-Hot Labels 형태라는 것입니다. NLP를 해보신 분은 One-Hot Encoding 과 같은 기법을 들어보셨을텐데요, 이와 유사합니다. 간단히 설명하자면, "해당되는 자리에는 1, 그렇지 않으면 0" 입니다. 예를 들어, 위 그림에서 y는 가장 첫번 째 자리에 1.0이 있고, 나머지 자리는 0입니다. 이것은 'y는 P(y=0)일 확률이 1이고, 나머지는 확률이 0이므로, y의 레이블 값은 0이겠구나'라고 해석 가능합니다.

 

위 수식은 Softmax Classifier에 걸맞는 Loss 계산 식입니다.

 

 

 

③ Cost Function : cross entropy

위 코드는, 좌측 그림에서의 loss를 구하는 코드입니다. 

 

 

조금 더 파이토치 답게 loss를 구한 코드입니다.

이 때, loss = nn.CrossEntropyLoss( )로 생성된 loss객체를 사용할 때의 주의점을 알아봅시다. 즉, 아래와 같은 코드를 사용할 때 주의점입니다.

l1 = loss(Y_pred1, Y)
l2 = loss(Y_pred2, Y)

① 실제 값 Y는 One-Hot 형태가 아니다. 클래스 형태이다.

② 예측 값 Y_pred는 Logit이다. (Softmax 적용 결과인 확률의 형태가 아님) loss 함수 안에 이미 Softmax 기능이 들어가있다.

 

 

위 코드는 어떻게 구성되었는지 스스로 생각해봅시다.

 

 

 

④ 코드 구현

 

# https://github.com/pytorch/examples/blob/master/mnist/main.py
from __future__ import print_function
from torch import nn, optim, cuda
from torch.utils import data
from torchvision import datasets, transforms
import torch.nn.functional as F
import time

# Training settings
batch_size = 64
device = 'cuda' if cuda.is_available() else 'cpu'
print(f'Training MNIST Model on {device}\n{"=" * 44}')

# MNIST Dataset
train_dataset = datasets.MNIST(root='./mnist_data/',
                               train=True,
                               transform=transforms.ToTensor(),
                               download=True)

test_dataset = datasets.MNIST(root='./mnist_data/',
                              train=False,
                              transform=transforms.ToTensor())

# Data Loader (Input Pipeline)
train_loader = data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=False)


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.l1 = nn.Linear(784, 520)
        self.l2 = nn.Linear(520, 320)
        self.l3 = nn.Linear(320, 240)
        self.l4 = nn.Linear(240, 120)
        self.l5 = nn.Linear(120, 10)

    def forward(self, x):
        x = x.view(-1, 784)  # Flatten the data (n, 1, 28, 28)-> (n, 784)
        x = F.relu(self.l1(x))
        x = F.relu(self.l2(x))
        x = F.relu(self.l3(x))
        x = F.relu(self.l4(x))
        return self.l5(x)


model = Net()
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)


def train(epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 10 == 0:
            print('Train Epoch: {} | Batch Status: {}/{} ({:.0f}%) | Loss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))


def test():
    model.eval()
    test_loss = 0
    correct = 0
    for data, target in test_loader:
        data, target = data.to(device), target.to(device)
        output = model(data)
        # sum up batch loss
        test_loss += criterion(output, target).item()
        # get the index of the max
        pred = output.data.max(1, keepdim=True)[1]
        correct += pred.eq(target.data.view_as(pred)).cpu().sum()

    test_loss /= len(test_loader.dataset)
    print(f'===========================\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} '
          f'({100. * correct / len(test_loader.dataset):.0f}%)')


if __name__ == '__main__':
    since = time.time()
    for epoch in range(1, 10):
        epoch_start = time.time()
        train(epoch)
        m, s = divmod(time.time() - epoch_start, 60)
        print(f'Training time: {m:.0f}m {s:.0f}s')
        test()
        m, s = divmod(time.time() - epoch_start, 60)
        print(f'Testing time: {m:.0f}m {s:.0f}s')

    m, s = divmod(time.time() - since, 60)
    print(f'Total Time: {m:.0f}m {s:.0f}s\nModel was trained on {device}!')

 

 

 

 

⑤ Exercise

 

 

 

 

본 글은 Sung Kim 교수님의 PyTorchZeroToAll 강의를 토대로 저의 지식을 아주 조금 덧붙여 작성하였습니다.
글 작성에 대한 허락을 받아, 개인 공부용으로 작성합니다.
문제가 발생할 시 비공개로 전환함을 알립니다.
https://www.youtube.com/channel/UCML9R2ol-l0Ab9OXoNnr7Lw