0Log(0)처리하기

Tensorflow에서 cross-entropy를 구현하는 법에 대해 좋은
Stack overflow포스트가 있어서 공유

0log(0)=NaN?

log(0)가 NaN이 되는 문제를 해결하는 방법이 몇가지 있는데
x가 0.0을 포함한 텐서라고 했을때

  1. tf.log(tf.clip_by_value(x,1e-7,1))
  2. tf.log(x+1e-7)

각각의 문제점은

  1. 1e-7미만에서 gradient가 갱신되지 않음
  2. 1e-7이하의 방향으로 지속적으로 값을 갱신시킨다.
    • $ \ln(\text{1e-7})\risingdotseq -16 $ 쯤 되는데
      x의 이상적인 값이 1e-7이하라면 이 방향으로 학습이 진행될때마다 학습을 하게된다.
    • float32의 fraction은 23bits인데(IEEE754)대략 1.192e-7 $\doteqdot$ (2**-23)쯤된다.
      epsilon값을 왜 대부분 1e-7로 설정하는지에 대한 이유 중 하나라고 생각한다(뇌피셜).
      1e-6까지는 10진수를 2진수로 표현하였을 때, 정확도가 유지된다.
      그러나, 그 이하에서는 숫자간의 step이 10진수 표현과 어긋나게 된다.(소수점 표현의 exponent가 사용됨)

numeric stability

포스트에서 explode가 문제가 아니라 discontinuity가 문제라고 언급하고 있다. 해결법은

  safe_x = tf.where(tf.equal(x, 0.), tf.ones_like(x), x)
  return -tf.reduce_sum(y * tf.log(safe_x))

요런식으로 해주면 된다.
정답의 one-hot을 $\hat y$, predict를 $y$라고 했을때
\(\hat{y}\log{y} = \begin{cases} 0\log(0)=0, & y=0\\ \hat{y}\log{y}, & \text{else}.\\ \end{cases}\)

python으로 확인을 해보면

import tensorflow as tf
import os
sess = tf.InteractiveSession()
os.environ["CUDA_VISIBLE_DEVICES"]="-1"
#case1:
y_hat = tf.constant([0.99,0.0,0.99,0.0])
y = tf.constant([0.1,0.1,0.0,0.0])

# y_hat이 non-zero, zero,     non-zero, zero
# y가     non-zero, non-zero, zero,     zero
# y_hat*log(y)의 값은 각각 [non-zero, 0.0, 0.0, 0.0]
# 원하는 값: [real, 0.0, 0.0, 0.0]

case1 = tf.reduce_sum(y_hat*tf.log(y))
case1_gv = tf.gradients(case1,y)[0]
print("case1: ", case1.eval())
print("gv_case1: ", case1_gv.eval())

safe_y  = tf.where(tf.equal(y, 0.) , tf.ones_like(y), y)
case2 = tf.reduce_sum(y_hat*tf.log(safe_y))
case2_gv = tf.gradients(case2,y)[0]
print("case2: ", case2.eval())
print("gv_case2: ", case2_gv.eval())
'''
case1:  nan
gv_case1:  [9.9 0.  inf nan]
case2:  -2.2795594
gv_case2:  [9.9 0.  0.  0. ]
'''
sess.close()

이렇게 작동하는걸 알 수 있다.