Tensorflow에서 cross-entropy를 구현하는 법에 대해 좋은
Stack overflow포스트가 있어서 공유
0log(0)=NaN?
log(0)가 NaN이 되는 문제를 해결하는 방법이 몇가지 있는데
x가 0.0을 포함한 텐서라고 했을때
- tf.log(tf.clip_by_value(x,1e-7,1))
- tf.log(x+1e-7)
각각의 문제점은
- 1e-7미만에서 gradient가 갱신되지 않음
- Dying ReLU와 비슷한 경우로도 볼 수 있다.
어떤식으로 문제가 되는지는 갓갓갓갓 카파씨 블로그를 참고.
Andrej Karpathy - Yes you should understand backprop
- Dying ReLU와 비슷한 경우로도 볼 수 있다.
- 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가 사용됨)
- $ \ln(\text{1e-7})\risingdotseq -16 $ 쯤 되는데
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()
이렇게 작동하는걸 알 수 있다.