我是靠谱客的博主 优秀中心,最近开发中收集的这篇文章主要介绍softmax loss函数和求导以及数值稳定性讨论,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

转:

https://zhuanlan.zhihu.com/p/27223959

目录

背景与定义

导数

softmax的计算与数值稳定性

Loss function

对数似然函数

交叉熵

Loss function求导


Softmax函数

背景与定义

在Logistic regression二分类问题中,我们可以使用sigmoid函数将输入wx + b映射到(0, 1)区间中,从而得到属于某个类别的概率。将这个问题进行泛化,推广到多分类问题中,我们可以使用softmax函数,对输出的值归一化为概率值。

这里假设在进入softmax函数之前,已经有模型输出c值,其中c是要预测的类别数,模型可以是全连接网络的输出a,其输出个数为c,即输出为a_{1}, a_{2}, ..., a_{c}

所以对每个样本,它属于类别i的概率为:

y_{i} = frac{e^{a_i}}{sum_{k=1}^{c}e^{a_k}}    forall i in 1...c

通过上式可以保证sum_{i=1}^{c}y_i = 1,即属于各个类别的概率和为1。

导数

对softmax函数进行求导,即求

frac{partial{y_{i}}}{partial{a_{j}}}

i项的输出对第j项输入的偏导。
代入softmax函数表达式,可以得到:

frac{partial{y_{i}}}{partial{a_{j}}} = frac{partial{ frac{e^{a_i}}{sum_{k=1}^{c}e^{a_k}} }}{partial{a_{j}}}

用我们高中就知道的求导规则:对于

f(x) = frac{g(x)}{h(x)}

它的导数为

f'(x) = frac{g'(x)h(x) - g(x)h'(x)}{[h(x)]^2}

所以在我们这个例子中,

g(x) = e^{a_i} \ h(x) = sum_{k=1}^{c}e^{a_k}

上面两个式子只是代表直接进行替换,而非真的等式。

e^{a_i}(即g(x))对a_j进行求导,要分情况讨论:

  1. 如果i = j,则求导结果为e^{a_i}
  2. 如果i ne j,则求导结果为0

再来看sum_{k=1}^{c}e^{a_k}a_j求导,结果为e^{a_j}

所以,当i = j时:

frac{partial{y_{i}}}{partial{a_{j}}} = frac{partial{ frac{e^{a_i}}{sum_{k=1}^{c}e^{a_k}} }}{partial{a_{j}}}= frac{ e^{a_i}sigma - e^{a_i}e^{a_j}}{sigma^2}=frac{e^{a_i}}{sigma}frac{sigma - e^{a_j}}{sigma}=y_i(1 - y_j)

i ne j时:

frac{partial{y_{i}}}{partial{a_{j}}} = frac{partial{ frac{e^{a_i}}{sum_{k=1}^{c}e^{a_k}} }}{partial{a_{j}}}= frac{ 0 - e^{a_i}e^{a_j}}{sigma^2}=-frac{e^{a_i}}{sigma}frac{e^{a_j}}{sigma}=-y_iy_j

其中,为了方便,令sigma = sum_{k=1}^{c}e^{a_k}

对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

一种简单有效避免该问题的方法就是让exp(x)中的x值不要那么大或那么小,在softmax函数的分式上下分别乘以一个非零常数:

y_{i} = frac{e^{a_i}}{sum_{k=1}^{c}e^{a_k}}= frac{ee^{a_i}}{sum_{k=1}^{c}ee^{a_k}}= frac{e^{a_i+log(e)}}{sum_{k=1}^{c}e^{a_k+log(e)}}= frac{e^{a_i+f}}{sum_{k=1}^{c}e^{a_k+f}}

这里log(e)是个常数,所以可以令它等于f。加上常数f之后,等式与原来还是相等的,所以我们可以考虑怎么选取常数f。我们的想法是让所有的输入在0附近,这样e^{a_i}的值不会太大,所以可以让f的值为:

f = -max(a_1, a_2, ..., a_c)

这样子将所有的输入平移到0附近(当然需要假设所有输入之间的数值上较为接近),同时,除了最大值,其他输入值都被平移成负数,e为底的指数函数,越小越接近0,这种方式比得到nan的结果更好。

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进行优化,在分类问题中,我们一般使用最大似然估计(Maximum likelihood estimation)来构造损失函数。对于输入的x,其对应的类标签为t,我们的目标是找到这样的theta使得p(t|x)最大。在二分类的问题中,我们有:

p(t|x) = (y)^t(1-y)^{1-t}

其中,y = f(x)是模型预测的概率值,t是样本对应的类标签。

将问题泛化为更一般的情况,多分类问题:

p(t|x) = prod_{i=1}^{c}p(t_i|x)^{t_i} = prod_{i=1}^{c}y_i^{t_i}

由于连乘可能导致最终结果接近0的问题,一般对似然函数取对数的负数,变成最小化对数似然函数。

-log p(t|x) = -log prod_{i=1}^{c}y_i^{t_i} = -sum_{i = i}^{c} t_{i} log(y_{i})

交叉熵

说交叉熵之前先介绍相对熵,相对熵又称为KL散度(Kullback-Leibler Divergence),用来衡量两个分布之间的距离,记为d_{kl}(p||q)

begin{split}d_{kl}(p||q) &= sum_{x in x} p(x) log frac{p(x)}{q(x)} \& =sum_{x in x}p(x)log  p(x) - sum_{x in x}p(x)log  q(x) \& =-h(p) - sum_{x in x}p(x)log q(x)end{split}

这里h(p)p的熵。

假设有两个分布pq,它们在给定样本集上的交叉熵定义为:

ce(p, q) = -sum_{x in x}p(x)log q(x) = h(p) + d_{kl}(p||q)

从这里可以看出,交叉熵和相对熵相差了h(p),而当p已知的时候,h(p)是个常数,所以交叉熵和相对熵在这里是等价的,反映了分布pq之间的相似程度。关于熵与交叉熵等概念,可以参考该博客再做了解。

回到我们多分类的问题上,真实的类标签可以看作是分布,对某个样本属于哪个类别可以用One-hot的编码方式,是一个维度为c的向量,比如在5个类别的分类中,[0, 1, 0, 0, 0]表示该样本属于第二个类,其概率值为1。我们把真实的类标签分布记为p,该分布中,t_i = 1i属于它的真实类别c。同时,分类模型经过softmax函数之后,也是一个概率分布,因为sum_{i = 1}^{c}{y_i} = 1,所以我们把模型的输出的分布记为q,它也是一个维度为c的向量,如[0.1, 0.8, 0.05, 0.05, 0]。
对一个样本来说,真实类标签分布与模型预测的类标签分布可以用交叉熵来表示:

l_{ce} = -sum_{i = 1}^{c}t_i log(y_i)

可以看出,该等式于上面对数似然函数的形式一样!

最终,对所有的样本,我们有以下loss function:

l = -sum_{k = 1}^{n}sum_{i = 1}^{c}t_{ki} log(y_{ki})

其中t_{ki}是样本k属于类别i的概率,y_{ki}是模型对样本k预测为属于类别i的概率。

Loss function求导

对单个样本来说,loss functionl_{ce}对输入a_j的导数为:

frac{partial l_{ce}}{partial a_j} = -sum_{i = 1}^{c}frac {partial t_i log(y_i)}{partial{a_j}} = -sum_{i = 1}^{c}t_i frac {partial log(y_i)}{partial{a_j}} = -sum_{i = 1}^{c}t_i frac{1}{y_i}frac{partial y_i}{partial a_j}

上面对frac{partial{y_{i}}}{partial{a_{j}}}求导结果已经算出:

i = j时:frac{partial{y_{i}}}{partial{a_{j}}} = y_i(1 - y_j)

i ne j时:frac{partial{y_{i}}}{partial{a_{j}}} = -y_iy_j

所以,将求导结果代入上式:

begin{split}-sum_{i = 1}^{c}t_i frac{1}{y_i}frac{partial y_i}{partial a_j}&= -frac{t_i}{y_i}frac{partial y_i}{partial a_i} - sum_{i ne j}^{c} frac{t_i}{y_i}frac{partial y_i}{partial a_j} \& = -frac{t_j}{y_i}y_i(1 - y_j) - sum_{i ne j}^{c} frac{t_i}{y_i}(-y_iy_j) \& = -t_j + t_jy_j + sum_{i ne j}^{c}t_iy_j = -t_j + sum_{i = 1}^{c}t_iy_j \& = -t_j + y_jsum_{i = 1}^{c}t_i = y_j - t_jend{split}

 

最后

以上就是优秀中心为你收集整理的softmax loss函数和求导以及数值稳定性讨论的全部内容,希望文章能够帮你解决softmax loss函数和求导以及数值稳定性讨论所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部