본문 바로가기
ML & DL/tensorflow

[Tensorflow] 분류에 사용되는 activation과 loss function(softmax/log_softmax/categorical_crossentropy)

by 별준 2020. 12. 5.

(tensorflow v2)

 

 

Tensorflow로 Classification을 수행하면, 모델 output에서 activation 함수로 sigmoid나 softmax를 적용하게 됩니다. 

그리고 loss는 이진 분류는 binary_crossentropy와 다중 분류는 categorical_crossentropy를 자주 사용합니다.

 

이번 글에서는 tensorflow에는 softmax/log_softmax를 살펴보고, categorical_crossentropy가 어떻게 수행이 되는지 살펴보기 위해서 실험을 해보았습니다.

(Pytorch에 대한 내용은 아래 게시글을 참조하시기 바랍니다.)

2020/12/02 - [ML & DL/pytorch] - [Pytorch] softmax와 log_softmax (그리고 CrossEntropyLoss)

 

[Pytorch] softmax와 log_softmax (그리고 CrossEntropyLoss)

Pytorch로 MNIST 분류 예제 문제를 구현하다가, torch.nn.functional에 softmax, log_softmax 두 가지가 있다는 것을 발견했습니다. 2020/12/01 - [ML & DL/pytorch] - [Pytorch] MNIST Classification (2020/12/0..

junstar92.tistory.com

 

우선 Pytorch로 진행한 것과 동일하게 테스트를 진행해보도록 하겠습니다.

 

1. softmax

x = np.random.randint(0,5,(3,))
print(x)

x를 임의로 생성해주고, x에 softmax 함수를 적용하면 0~1 사이의 값을 가지고 총합이 1인 행렬로 변환합니다.

y = tf.nn.softmax(x.astype(np.float32))
print(y.numpy())
print(y.numpy().sum())

pytorch와는 다르게 정확히 1은 아니네요.

 

이번에는 (3,5) 행렬을 생성해서 softmax를 적용해보도록 하겠습니다.

x = np.random.rand(3,5)
print(x)

y = tf.nn.softmax(x)
print(y.numpy())

print(y.numpy().sum(axis=1))

역시 각 행의 합이 1인 행렬로 변환되었습니다.

 

2. log softmax

log softmax는 단순히 softmax에 log를 취한 것과 같습니다.

y = tf.math.log(tf.nn.softmax(x.astype(np.float32)))
print(y.numpy())

 

그리고, 다음의 함수로 log softmax를 계산할 수도 있습니다.

y = tf.nn.log_softmax(x.astype(np.float32))
print(y.numpy())

softmax보다 log softmax를 사용하는 경우가 많은데, 이는 수치적으로 효과가 softmax보다 더 좋으며, gradient optimization을 향상시키기 때문입니다. 특히 연산량이 많은 모델을 학습할 때에 더욱 효과적이라고 합니다.

 

3. categorical_crossentropy와 softmax_cross_entropy_with_logits

이번에는 다중 분류의 loss 함수로 자주 사용되는 tf.keras.losses.categorical_crossentropy와 softmax cross entropy loss를 계산할 수 있는 함수인 tf.nn.softmax_cross_entropy_with_logits에 대해서 알아보도록 하겠습니다.

사용되는 x는 위에서 사용했던 것 그대로 사용했습니다.

 

label로 사용할 y를 생성해주고, onehot encoding을 적용한 y_one_hot도 생성해줍니다.

y = np.random.randint(0, 5, (3,))
print(y)
y_one_hot = tf.one_hot(y, depth=5, dtype=tf.float64)
print(y_one_hot)

우선 우리가 keras model로 사용할 때 자주 설정하는 categorical_crossentropy에 대해서 살펴보도록 하겠습니다. 보통 model의 마지막에 softmax를 적용하니깐, softmax를 적용해서 계산해보고, 적용하지 않고 계산해보도록 하겠습니다.

loss = tf.keras.losses.categorical_crossentropy(y_one_hot, tf.nn.softmax(x))
print(loss.numpy())

loss = tf.keras.losses.categorical_crossentropy(y_one_hot, x)
print(loss.numpy())

두 결과가 다르게 나왔습니다. 

두 값이 다르게 나온 것으로 봐선 categorical_crossentropy 내부에서 softmax연산이 수행되지 않는 것으로 보입니다.

내부코드를 살펴보니 tf.keras.losses.categorical_crossentropy의 내부 호출은 다음 순으로 이루어집니다.

tf.keras.backend.categorial_crossentropy -> tf.nn.softmax_cross_entropy_with_logits_v2

 

그리고 tf.nn.softmax_cross_entropy_with_logits_v2tf.nn.softmax_cross_entropy_with_logits 입니다.

위 함수는 내부적으로 효율을 위해서 logits에 softmax를 수행한다고 합니다.

따라서 x에 아무런 처리없이 함수를 수행해서 loss를 구해보았습니다.

loss = tf.nn.softmax_cross_entropy_with_logits(y_one_hot, x)
print(loss.numpy())

softmax를 적용한 tf.keras.losses.categorical_crossentropy와 동일합니다. 즉, 우리가 keras model에서 주로 사용하는 categorical_crossentropy에는 loss를 계산하기 전 softmax 연산이 필요하다는 것이죠.

따라서, 아래 두 연산은 동일한 loss를 갖게 됩니다.

그런데 방금 tf.keras.losses.categorical_crossentropy는 결국 tf.nn.softmax_cross_entropy_with_logits을 호출한다고 했는데 결과가 다르게 나와서 조금 더 찾아보니, tf.keras.losses.categorical_crossentropy에는 from_logits이라는 매개변수가 있었습니다. 

from_logits의 default가 False이기 때문에 softmax가 수행된 output(즉, 라벨에 대한 확률 분포)을 매개변수로 사용한다라고 인식하는 것이고, True라면 softmax를 거치지 않은 raw output이 되어서 결과가 다르게 계산되고 있었습니다.

 

그래서 from_logits을 True로 설정하고 tf.keras.losses.categorical_crossentropy로 x를 그대로 사용해서 loss를 구하면 동일한 결과를 얻을 수 있습니다.

loss = tf.keras.losses.categorical_crossentropy(y_one_hot, x, from_logits=True)
print(loss.numpy())

 

그런데 Pytorch에서 CrossEntropyLoss는 내부에서 log_softmax를 수행하고 있었습니다. 그렇다면 tensorflow의 softmax_cross_entropy_with_logits은 과연 어떤 softmax를 사용할 지 궁금해서 추가로 직접 CrossEntropyLoss를 계산해보았습니다.

CrossEntropyLoss는 아래와 같이 계산됩니다.

P(x)는 우리가 가지고 있는 label 정보, 곧 y_one_hot을 의미합니다. 그리고 Q(x)는 모델을 통해서 얻은 각 라벨의 확률을 의미하죠. 그래서 Q(x)는 x에 softmax를 적용한 것이라고 보시면 됩니다. 

따라서 위 식은 다음과 코드로 계산할 수 있습니다.

print(tf.reduce_sum(-tf.nn.softmax(x)*y_one_hot, -1))

결과가 tf.nn.softmax_cross_entropy_with_logits과 다르게 나왔습니다. 

그렇다면, log_softmax를 적용해서 구해보도록 하겠습니다.

print(tf.reduce_sum(-tf.nn.log_softmax(x)*y_one_hot, -1))

결과가 동일하게 나온것을 확인할 수 있습니다 !

마지막으로 확실하게 하기 위해서, pytorch의 CrossEntropyLoss를 사용해서 구해보도록 하겠습니다. pytorch의 CrossEntropyLoss는 내부에서 log_softmax를 사용하는 것이 확실하기 때문에 이 결과까지 동일한지 확인해보도록 하겠습니다.

torch.nn.functional.cross_entropy는 loss를 구해서 평균으로 output으로 리턴합니다.

따라서, 위에서 tensorflow에서 구한 loss를 평균을 내보면, 동일한 결과를 얻을 수 있습니다.

 

즉, tf.nn.softmax_cross_entropy_with_logits 내부에서는 log_softmax가 사용되는 것으로 확인됩니다.

 

 

이번 글의 결론입니다.

  1. tf.keras.losses.categorical_crossentropy에서 y_pred는 softmax를 수행한 각 라벨의 확률 분포이여야 한다. 하지만 from_logits을 True로 설정하면 softmax를 수행하지 않은 logits을 입력으로 사용해도 된다.
  2. tf.keras.losses.categorical_crossentropy를 수행하는 것은 tf.nn.softmax_cross_entropy_with_logits를 호출하는 것이다. 하지만 from_logits이 True일 때, 완전히 동일하다. False인 경우에는 내부에서 softmax가 수행되지 않는다.
  3. tf.nn.softmax_cross_entropy_with_logits의 내부에서는 log_softmax가 수행되어서 Loss를 구한다.

댓글