Softmax函数与交叉熵
设为 “星标”,重磅干货,第一时间送达!
来自 | 知乎
地址 | https://www.zhihu.com/question/278825804/answer/402634502
作者 | 天雨粟
编辑 | 机器学习算法与自然语言处理公众号
本文仅作学术分享,若侵权,请联系后台删文处理
Softmax函数
背景与定义
导数
对softmax函数的求导,我在两年前微信校招面试基础研究岗位一面的时候,就遇到过,这个属于比较基础的问题。
softmax的计算与数值稳定性
在Python中,softmax函数为:
def softmax(x):
exp_x = np.exp(x)
return exp_x / np.sum(exp_x)
传入[1, 2, 3, 4, 5]的向量
>>> softmax([1, 2, 3, 4, 5])
array([ 0.01165623, 0.03168492, 0.08612854, 0.23412166, 0.63640865])
但如果输入值较大时:
>>> softmax([1000, 2000, 3000, 4000, 5000])
array([ nan, nan, nan, nan, nan])
这是因为在求exp(x)时候溢出了:
import math
math.exp(1000)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# OverflowError: math range error
def softmax(x):
shift_x = x - np.max(x)
exp_x = np.exp(shift_x)
return exp_x / np.sum(exp_x)
>>> softmax([1000, 2000, 3000, 4000, 5000])
array([ 0., 0., 0., 0., 1.])
当然这种做法也不是最完美的,因为softmax函数不可能产生0值,但这总比出现nan的结果好,并且真实的结果也是非常接近0的。
UPDATE(2017-07-07):
有同学问这种近似会不会影响计算结果,为了看原来的softmax函数计算结果怎么样,尝试计算softmax([1000, 2000, 3000, 4000, 5000])
的值。由于numpy是会溢出的,所以使用Python中的bigfloat库。
import bigfloat
def softmax_bf(x):
exp_x = [bigfloat.exp(y) for y in x]
sum_x = sum(exp_x)
return [y / sum_x for y in exp_x]
res = softmax_bf([1000, 2000, 3000, 4000, 5000])
print('[%s]' % ', '.join([str(x) for x in res]))
结果:
[6.6385371046556741e-1738, 1.3078390189212505e-1303, 2.5765358729611501e-869, 5.0759588975494576e-435, 1.0000000000000000]
可以看出,虽然前四项结果的量级不一样,但都是无限接近于0,所以加了一个常数的softmax对原来的结果影响很小。
Loss function
对数似然函数
交叉熵
Loss function求导
TensorFlow
方法1:手动实现(不建议使用)
在TensorFlow中,已经有实现好softmax函数,所以我们可以自己构造交叉熵损失函数:
import tensorflow as tf
import input_data
x = tf.placeholder("float", shape=[None, 784])
label = tf.placeholder("float", shape=[None, 10])
w_fc1 = tf.Variable(tf.truncated_normal([784, 1024], stddev=0.1))
b_fc1 = tf.Variable(tf.constant(0.1, shape=[1024]))
h_fc1 = tf.matmul(x, w_fc1) + b_fc1
w_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))
b_fc2 = tf.Variable(tf.constant(0.1, shape=[10]))
y = tf.nn.softmax(tf.matmul(h_fc1, w_fc2) + b_fc2)
cross_entropy = -tf.reduce_sum(label * tf.log(y))
cross_entropy = -tf.reduce_sum(label * tf.log(y))是交叉熵的实现。先对所有的输出用softmax进行转换为概率值,再套用交叉熵的公式。
方法2:使用tf.nn.softmax_cross_entropy_with_logits(推荐使用)
import tensorflow as tf
import input_data
x = tf.placeholder("float", shape=[None, 784])
label = tf.placeholder("float", shape=[None, 10])
w_fc1 = tf.Variable(tf.truncated_normal([784, 1024], stddev=0.1))
b_fc1 = tf.Variable(tf.constant(0.1, shape=[1024]))
h_fc1 = tf.matmul(x, w_fc1) + b_fc1
w_fc2 = tf.Variable(tf.truncated_normal([1024, 10], stddev=0.1))
b_fc2 = tf.Variable(tf.constant(0.1, shape=[10]))
y = tf.matmul(h_fc1, w_fc2) + b_fc2
cross_entropy = -tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=label, logits=y))
TensorFlow已经实现好函数,用来计算label和logits的softmax交叉熵。注意,该函数的参数logits在函数内会用softmax进行处理,所以传进来时不能是softmax的输出了。
区别
既然我们可以自己实现交叉熵的损失函数,为什么TensorFlow还要再实现tf.nn.softmax_cross_entropy_with_logits函数呢?
这个问题在Stack overflow上已经有Google的人出来回答(传送门),原话是:
If you want to do optimization to minimize the cross entropy, AND you’re softmaxing after your last layer, you should use tf.nn.softmax_cross_entropy_with_logits instead of doing it yourself, because it covers numerically unstable corner cases in the mathematically right way. Otherwise, you’ll end up hacking it by adding little epsilons here and there.
也就是说,方法1自己实现的方法会有在前文说的数值不稳定的问题,需要自己在softmax函数里面加些trick。所以官方推荐如果使用的loss function是最小化交叉熵,并且,最后一层是要经过softmax函数处理,则最好使用tf.nn.softmax_cross_entropy_with_logits函数,因为它会帮你处理数值不稳定的问题。
总结
全文到此就要结束了,可以看到,前面介绍这么多概念,其实只是为了解释在具体实现时候要做什么样的选择。可能会觉得有些小题大做,但对于NN这个黑盒子来说,我们现暂不能从理论上证明其有效性,那在工程实现上,我们不能再将它当作黑盒子来使用。
Reference
The Softmax function and its derivative Peter’s Notes CS231n Convolutional Neural Networks for Visual Recognition cs229.stanford.edu/note 交叉熵(Cross-Entropy) - rtygbwwwerr的专栏 - 博客频道 - CSDN.NET difference between tensorflow tf.nn.softmax and tf.nn.softmax_cross_entropy_with_logits
文章同时发在CSDN上:blog.csdn.net/behamcheu#
可以扫描下方二维码,小助手将会邀请您入群交流,
注意:请大家添加时修改备注为 [学校/公司 + 姓名 + 方向]
例如 —— 哈工大+张三+对话系统。
号主,微商请自觉绕道。谢谢!