Logit, Sigmoid, Softmax

Logit, Sigmoid, Softmax

오늘은 Logit, Sigmoid, Softamx에 대해서 알아보려고 합니다. 코드와 함께 Logit, Sigmoid, Softmax의 차이와 활용처를 알아보겠습니다.

import torch
import torch.nn as nn

x = torch.rand(8, 256)
print(x)

linear_layer = nn.Linear(256, 5)
tensor([[0.8509, 0.5229, 0.0945,  ..., 0.0343, 0.9828, 0.8862],
        [0.2340, 0.6683, 0.6794,  ..., 0.0669, 0.7153, 0.4655],
        [0.8962, 0.0695, 0.5760,  ..., 0.9555, 0.3334, 0.7940],
        ...,
        [0.0981, 0.1942, 0.7533,  ..., 0.2697, 0.7147, 0.5421],
        [0.3843, 0.4266, 0.9193,  ..., 0.1393, 0.6543, 0.1268],
        [0.5053, 0.7492, 0.6640,  ..., 0.8146, 0.7813, 0.4540]])

오늘 실험에 사용한 tensor 하나를 만들었습니다. 배치 크기는 8이고, 입력 값은 256 차원이라고 가정했습니다. 그리고 Fully Connected Layer로 사용한 Linear Layer를 하나 만듭니다. Linear Layer의 출력이 5 차원이기 때문에 이 네트워크는 5개의 클래스를 구분하는 Classification Model이라고 가정할 수 있습니다.

Logit

Classification을 위한 Deep Leraning 네트워크는 중간 레이어로 CNN, RNN, LSTM, Transformer Encoder 등을 사용해서 다양하게 구성하지만, 마지막 레이어는 보통 Fully Connected Layer를 사용하고 노드를 클래스의 수만큼 만듭니다. 각 노드가 각 클래스를 표현하게 하는 셈이죠. 이 때 마지막 노드에서 아무런 Activation Function을 거치지 않은 값을 Logit이라고 부릅니다.

logit_result = linear_layer(x)
print(logit_result)
tensor([[-0.0557,  0.2072,  0.4761,  0.1407, -0.1976],
        [-0.3598,  0.4113,  0.3919,  0.1451, -0.2700],
        [-0.3468,  0.4821,  0.2684,  0.0978, -0.3284],
        [-0.1237,  0.2871,  0.3894,  0.2996, -0.4048],
        [-0.2347,  0.3236,  0.4654,  0.1430, -0.3452],
        [-0.0594,  0.4404,  0.2653,  0.2619, -0.5347],
        [-0.2096,  0.1484,  0.4985,  0.0108, -0.1633],
        [-0.1117,  0.3121,  0.4766,  0.1980, -0.3321]],
       grad_fn=<AddmmBackward>)

아주 기본적으로 weight를 곱하고 bias를 더한 값입니다.

Sigmoid

Logit은 기본적으로 어떤 제약 사항도 없기 때문에 최대, 최소값을 알 수 없습니다. 하지만 여기에 Sigmoid를 취해주면 각 값이 0과 1사이로 압축됩니다. 즉, Sigmoid를 거친값은 최소가 0, 최대가 1이라는 보장을 할 수 있습니다.

sigmoid_layer = nn.Sigmoid()
sigmoid_result = sigmoid_layer(logit_result)
print(sigmoid_result)
tensor([[0.4861, 0.5516, 0.6168, 0.5351, 0.4507],
        [0.4110, 0.6014, 0.5967, 0.5362, 0.4329],
        [0.4141, 0.6182, 0.5667, 0.5244, 0.4186],
        [0.4691, 0.5713, 0.5961, 0.5743, 0.4002],
        [0.4416, 0.5802, 0.6143, 0.5357, 0.4146],
        [0.4852, 0.6083, 0.5659, 0.5651, 0.3694],
        [0.4478, 0.5370, 0.6221, 0.5027, 0.4593],
        [0.4721, 0.5774, 0.6169, 0.5493, 0.4177]], grad_fn=<SigmoidBackward>)

Softmax

Sigmoid를 취한 후에 각 노드의 값은 0과 1사이가 됐지만, 각 노드의 Sigmoid 후 값은 독립적으로 계산됩니다. 다르게 말하면 첫번째 노드(logit_result[0][0])의 Sigmoid 값을 구할 때 다른 이웃 노드(logit_result[0][1]이나 logit_result[0][2])의 값을 고려하지 않습니다. 하지만 어떨 때는 총 5개 노드의 값이 서로를 고려해서 상대적인 크기를 나타냈으면 할 때가 있습니다. 특히, 이 5개(혹은 분류하려는 클래스의 수만큼의 노드의 수) 노드의 합이 항상 1.0이면 편리할 때가 많습니다. 왜일까요? 모든 값의 합이 항상 1.0이라는 것은 일종의 확률이라는 뜻이고 다양한 통계적인 기법을 적용할 수 있기 때문입니다.

softmax_layer = nn.Softmax(-1)
softmax_result = softmax_layer(logit_result)
print(softmax_result)
tensor([[0.1643, 0.2137, 0.2796, 0.1999, 0.1425],
        [0.1245, 0.2691, 0.2640, 0.2062, 0.1362],
        [0.1295, 0.2968, 0.2397, 0.2021, 0.1320],
        [0.1548, 0.2334, 0.2586, 0.2364, 0.1169],
        [0.1405, 0.2456, 0.2830, 0.2050, 0.1258],
        [0.1658, 0.2732, 0.2294, 0.2286, 0.1031],
        [0.1481, 0.2118, 0.3006, 0.1845, 0.1551],
        [0.1540, 0.2353, 0.2773, 0.2099, 0.1235]], grad_fn=<SoftmaxBackward>)

앞의 Sigmoid와 비교해보겠습니다.

print(torch.sum(sigmoid_result, -1))
print(torch.sum(softmax_result, -1))
tensor([2.6404, 2.5783, 2.5422, 2.6111, 2.5863, 2.5940, 2.5689, 2.6335],
       grad_fn=<SumBackward1>)
tensor([1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
       grad_fn=<SumBackward1>)

Sigmoid를 취합값들의 합은 1.0이 아니지만, Softmax를 취하고 나면 값의 합이 1.0이 되는 것을 볼 수 있습니다.

실전에서 의미

지금까지는 그냥 수학적인 이야기에 가까웠습니다. 그래서 이런 특징들이 실제 문제에서는 어떤 의미가 있을까요? 하나씩 살표보겠습니다.

Multi Classification 문제

입력값이 5개의 클래스 중에 어디에 속하는지 찾는 문제가 있다고 해보겠습니다. 클래스는 5개이지만 이 중 가장 속할 가능성이 높은 하나의 클래스만 고르는 문제입니다. 가만히 생각해보면 Logit, Sigmoid, Softmax를 거치면서 값을 변했지만 값들의 크기 순서는 같습니다. 수학적으로는 Sigmoid가 단조증가 함수이기 때문에 Logit의 크기를 그대로 유지하고, Softmax는 Logit의 합이 1.0이 되도록 비율만 조정하기 때문입니다. 말이 조금 복잡한데 아래 코드를 보면 조금 더 명확히 알 수 있습니다.

print(torch.argmax(logit_result, -1))
print(torch.argmax(sigmoid_result, -1))
print(torch.argmax(softmax_result, -1))
tensor([2, 1, 1, 2, 2, 1, 2, 2])
tensor([2, 1, 1, 2, 2, 1, 2, 2])
tensor([2, 1, 1, 2, 2, 1, 2, 2])

보시는 것처럼 세 경우 모두 최대값인 노드가 같은 것을 알 수 있습니다. Multi Classification은 앞에 말한대로 가장 속할 가능성이 높은 하나의 클래스만 고르는 문제이기 때문에 이 순서만이 중요합니다. 즉, 정답 클래스를 고르기 위해서는 Logit, Sigmoid, Softmax를 모두 사용할 수 있습니다.

하지만 실제로는 그렇지 않습니다. 모델을 훈련시킬 때 보통 Loss Function으로 Cross Entropy를 사용하는데, Cross Entropy Loss는 내부적으로는 Log Softmax 후 Negative Log Likelihood를 취합니다. 즉, Cross Entropy Loss를 쓴다는 것은 최종 레이어의 Active Function이 Softmax라는 의미입니다.

정리하면, Multi Classification 문제를 위해서는 Softmax를 최종 레이어에 사용한다입니다.

Multi Label 문제

유사하지만 다른 Multi Label 문제가 있습니다. 개, 고양이, 말 사진을 구분하는 문제는 Multi Classification 문제입니다. 왜냐면 어떤 사진은 고양이거나, 고양이거나, 말이거나 셋 중 하나이니까요. 하지만 한 사진에 동물이 여럿 찍혔을 수도 있습니다. 어떤 사진은 고양이 사진이면서 동시에 개 사진일 수도 있는거죠.

이를 해결하기 위한 간단한 방법은 각 노드의 값이 기준 값(Threshold)를 넘으면 그 노드가 대표하는 클래스에 속한다고 보고, 기준 값보다 낮으면 그 클래스에 속하지 않는다고 보는 방법입니다. 기준 값을 넘는 노드가 여러 개라면 모두 정답 클래스(Label)로 간주합니다.

이런 정의에서 Logit은 사용할 수 없습니다. 최대, 최소값이 정해지지 않았기 때문에 기준 값을 적용할 수가 없기 때문입니다. Softmax도 사용할 수 없습니다. 모든 노드의 값을 고려해서 전체 합이 1.0이 되도록 크기를 줄이는 과정에서 각 노드의 최대값, 최소값이 같지 않게 되었기 때문입니다. 반면 Sigmoid는 모든 조건을 충족합니다. 각 노드의 최대값이 1, 최소값이 0이기 때문입니다.

print(sigmoid_result > 0.55)
tensor([[False,  True,  True, False, False],
        [False,  True,  True, False, False],
        [False,  True,  True, False, False],
        [False,  True,  True,  True, False],
        [False,  True,  True, False, False],
        [False,  True,  True,  True, False],
        [False, False,  True, False, False],
        [False,  True,  True, False, False]])

위와 같은 코드를 사용하면 기준 값(이 경우에는 0.55)을 넘는 클래스(Label)을 구할 수 있습니다. 물론 기준 값은 실제 문제와 모델의 상태에 따라 달라지기 때문에 실험을 통해 결정해야합니다.

정리하면, Multi Label 문제를 위해서는 Sigmoid를 최종 레이어에 사용한다입니다.

마무리

지금까지 Logit, Sigmoid, Softmax에 대해서 알아보았습니다. 한번 더 요약해보면 다음과 같습니다.

  • 여러 개의 클래스 중 하나의 클래스를 고르는 Multi Classification 문제에는 Softmax를 사용한다.
  • 이러 개의 클래스 중 여러 개의 Label을 고르는 Multi Label 문제에는 Sigmoid를 사용한다.

마지막으로 Pytorch를 사용해서 이런 내용을 적용하려고 할 때 몇가지 주의점이 있습니다.

  • CrossEntropyLoss를 사용할 때는 최종 레이어에 Softmax를 쓰면 안 됩니다. 즉, Logit이어야합니다. CrossEntropyLoss가 내부적으로 LogSoftmax 후 NLLLoss를 하기 때문입니다. 최종 레이어에 Softmax를 하면 Softmax를 두번하는 셈입니다.
  • Binary Classification을 위해서 BCEWithLogitsLoss를 사용한다면, 역시 마지막 레이어에 Sigmoid나 Softmax를 사용하면 안 됩니다. Logit이어야합니다. BCEWithLogitsLoss가 내부적으로 Sigmoid 후 BCELoss를 하기 때문입니다.