softmax详细的梯度求导

参考原文:https://blog.****.net/wfei101/article/details/80807749
https://blog.****.net/fsdfasfawre/article/details/80586123

本文通过一个简单的例子来看softmax的梯度求导过程.

1 softmax函数

softmax用于多分类过程中,它将多个神经元的输出,映射到(0,1)区间内,可以看成概率来理解,从而来进行多分类!

假设我们有一个数组z=(3,1,3)Tz=(3,1,-3)^T,那么这个元素的softmax值就是yi=ezijezjy_i=\frac{e^{z_i}}{\sum_j e^{z_j}}.

更形象的如下图表示:

softmax详细的梯度求导

注意: 要求编写python代码,完成图示的计算.

2 softmax相关求导

当我们对分类的Loss进行改进的时候,我们要通过梯度下降,每次优化一个step大小的梯度,这个时候我们就要求Loss对每个权重矩阵的偏导,然后应用链式法则。在这个过程中,你会发现用了softmax函数之后,梯度求导过程非常非常方便!

下面我们举出一个简单例子,原理一样,目的是为了帮助大家容易理解!

softmax详细的梯度求导

我们能得到下面公式:

z1=w11o1+w12o2+w13o3z_1=w_{11}o_1+w_{12}o_2+w_{13}o_3
z2=w21o1+w22o2+w23o3z_2=w_{21}o_1+w_{22}o_2+w_{23}o_3
z3=w31o1+w32o2+w33o3z_3=w_{31}o_1+w_{32}o_2+w_{33}o_3

其中,z4,z5,z6z_4,z_5,z_6分别代表结点4,5,6的输出,o1,o2,o3o_1,o_2,o_3代表是结点1,2,3往后传的输入.

那么我们可以经过softmax函数得到:

a1=ez1ez1+ez2+ez3,a_1=\frac{e^{z_1}}{e^{z_1}+e^{z_2}+e^{z_3}},
a2=ez2ez1+ez2+ez3,a_2=\frac{e^{z_2}}{e^{z_1}+e^{z_2}+e^{z_3}},
a3=ez3ez1+ez2+ez3a_3=\frac{e^{z_3}}{e^{z_1}+e^{z_2}+e^{z_3}}.

好了,我们的重头戏来了,怎么根据求梯度,然后利用梯度下降方法更新梯度!

要使用梯度下降,肯定需要一个损失函数,这里我们使用交叉熵作为我们的损失函数,为什么使用交叉熵损失函数,不是这篇文章重点,后面有时间会单独写一下为什么要用到交叉熵函数(这里我们默认选取它作为损失函数)

交叉熵函数形式如下:

Loss=iyilnai Loss=-\sum_{i}y_i lna_i
其中y代表我们的真实值,a代表我们softmax求出的值。i代表的是输出结点的标号!在上面例子,i就可以取值为1,2,33三个结点(当然我这里只是为了简单,真实应用中可能有很多结点).

参数的形式在该例子中,总共分为w11,w12,w13,w21,w22,w23,w31,w32,w33这些,那么比如我要求出w11,w12,w13的偏导,就需要将Loss函数求偏导传到结点4,然后再利用链式法则继续求导即可,举个例子此时求w41的偏导为:

Lossw11=Lossa1a1z1z1w11=1a1a1z1o1 \begin{array}{lcl} \frac{\partial Loss}{\partial w_{11}}&=&\frac{\partial Loss}{\partial a_1}\frac{\partial a_1}{\partial z_1}\frac{\partial z_1}{\partial w_{11}}\\ &=&-\frac{1}{a_1}\frac{\partial a_1}{\partial z_1}o_1 \end{array}

其中,(z1=w11o1+w12o2+w13o3)(z_1=w_{11}o_1+w_{12}o_2+w_{13}o_3),则关键求出a1z1\frac{\partial a_1}{\partial z_1},w21, ,w33w_{21},\cdots,w_{33}等参数的偏导同理可以求出.

这里分为俩种情况:

如果j=ij=i:

ajzi=zi(ezjkezk)=(ezj)kezkezjezi(kezk)2=ezjkezkezjkezkezikezk=aj(1ai) \begin{array}{lcl} \frac{\partial a_j}{\partial z_i}&=&\frac{\partial}{\partial z_i}(\frac{e^{z_j}}{\sum_k e^{z_k}})\\ &=& \frac{(e^{z_j})'\cdot \sum_k e^{z_k}-e^{z_j}\cdot e^{z_i}}{(\sum_k e^{z_k})^2}\\ &=&\frac{e^{z_j}}{\sum_k e^{z_k}}-\frac{e^{z_j}}{\sum_k e^{z_k}}\cdot \frac{e^{z_i}}{\sum_k e^{z_k}}=a_j(1-a_i) \end{array}

j=i对应例子里就是如下图所示:
比如我选定了j为1,那么就是说我现在求导传到1结点这!

从而,Lossw11=1a1a1z1o1=1a1a1(1a1)o1=(1a1)o1\frac{\partial Loss}{\partial w_{11}}=-\frac{1}{a_1}\frac{\partial a_1}{\partial z_1} o_1=-\frac{1}{a_1} a_1(1-a_1) o_1=-(1-a_1)o_1.

3 softmax和交叉熵的原理

一般情况下,在神经网络中,最后一个输出层的节点个数与分类任务的目标数相等。假设最后的节点数为N,那么对于每一个样例,神经网络可以得到一个N维的数组作为输出结果,数组中每一个维度会对应一个类别。在最理想的情况下,如果一个样本属于k,那么这个类别所对应的的输出节点的输出值应该为1,而其他节点的输出都为0,即[0,0,1,0,….0,0],这个数组也就是样本的Label,是神经网络最期望的输出结果,交叉熵就是用来判定实际的输出与期望的输出的接近程度!

3.1 Softmax回归处理

神经网络的原始输出不是一个概率值,实质上只是输入的数值做了复杂的加权和与非线性处理之后的一个值而已,那么如何将这个输出变为概率分布?

这就是Softmax层的作用,假设神经网络的原始输出为y1,y2, ,yny_1,y_2,\cdots,y_n那么经过Softmax回归处理之后的输出为:

softmax(yi)=eyij=1neyj,\qquad softmax(y_i)=\frac{e^{y_i}}{\sum\limits_{j=1}^n e^{y_j}},

而单个节点的输出变成的一个概率值,经过Softmax处理后结果作为神经网络最后的输出。

3.2 交叉熵的原理

交叉熵刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。假设概率分布p为期望输出,概率分布q为实际输出,H(p,q)为交叉熵,则:
H(p,q)=xp(x)logq(x) H(p,q)=-\underset{x}{\sum}p(x)\log q(x)
举个例子: 假设N=3,期望输出为p=(1,0,0),实际输出q1=(0.5,0.2,0.3),q2=(0.8,0.1,0.1),那么:

H(p,q1)=(1×log0.5+0×log0.2+0×log0.3=0.3,H(p,q_1)=-(1\times \log^{0.5}+0\times \log^{0.2}+0\times \log^{0.3}=0.3,

H(p,q2)=(1×log0.8+0×log0.1+0×log0.1)=0.1.H(p,q_2)=-(1\times \log^{0.8}+0\times \log^{0.1}+0\times \log^{0.1})=0.1.

除此之外,交叉熵还有另一种表达形式,还是使用上面的假设条件:

H(p,q)=x(p(x)logq(x)+(1p(x))log(1q(x))) H(p,q)=-\underset{x}{\sum}(p(x)\log q(x)+(1-p(x))\log(1-q(x)))

其结果为:H(p,q1)=0.55,H(p,q2)=0.19.H(p,q_1)=0.55,H(p,q_2)=0.19.

以上的所有说明针对的都是单个样例的情况,而在实际的使用训练过程中,数据往往是组合成为一个batch来使用,所以对用的神经网络的输出应该是一个m*n的二维矩阵,其中m为batch的个数,n为分类数目,而对应的Label也是一个二维矩阵,还是拿上面的数据,组合成一个batch=2的矩阵:

q=(0.50.20.30.80.10.1),q=(100100).q=\left(\begin{matrix}0.5 & 0.2 & 0.3 \\ 0.8 & 0.1 & 0.1 \end{matrix}\right), q=\left(\begin{matrix}1 & 0 & 0\\ 1 & 0 & 0 \end{matrix}\right ).

所以交叉熵的结果应该是一个列向量(根据第一种方法):
H(p,q)=(0.30.1).H(p,q)=\begin{pmatrix}0.3 \\ 0.1 \end{pmatrix}.

而对于一个batch,最后取平均为0.2。

3.3 softmax的求导:

假设有n个输入(z1,z2, ,zn)(z_1,z_2,\cdots,z_n),将这n个输入到softmax函数得到n个输出(a1,a2, ,an)(a_1,a_2,\cdots,a_n). (a1,a2, ,an)(a_1,a_2,\cdots,a_n)分别对(z1,z2, ,zn)(z_1,z_2,\cdots,z_n)求偏导,得到雅克比矩阵如下:

az=(a1z1a1z2a1zna2z1a2z2a2znanz1anz2anzn) \frac{\partial a}{\partial z}=\begin{pmatrix} \frac{\partial a_1}{\partial z_1} & \frac{\partial a_1}{\partial z_2} & \cdots & \frac{\partial a_1}{\partial z_n} \\ \frac{\partial a_2}{\partial z_1} & \frac{\partial a_2}{\partial z_2} & \cdots & \frac{\partial a_2}{\partial z_n} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial a_n}{\partial z_1} & \frac{\partial a_n}{\partial z_2} & \cdots & \frac{\partial a_n}{\partial z_n} \\ \end{pmatrix}

这里softmax是一个RnRnR^n\rightarrow R^n的向量函数,a=softmax(z)a=softmax(z).

严格来说,梯度只是为标量函数来定义的,例如机器学习中的损失函数;对于像softmax这样的向量函数,说是“梯度”是不准确的;雅可比是一个向量函数的全部的导数,大多数情况下我们会说“导数”。

由上面的例子知道如果i=j,i=j,,则aizj=ai(1aj)\frac{\partial a_i}{\partial z_j}=a_i(1-a_j).

如果iji \neq j,则

aizj=ezik=1nezkzj=0eziezjΣ2=eziΣezjΣ=aiaj \frac{\partial a_i}{\partial z_j}=\frac{\partial \frac{e^{z_i}}{\sum_{k=1}^n e^{z_k}}}{\partial z_j} =\frac{0-e^{z_i}e^{z_j}}{\Sigma^2}=-\frac{e^{z_i}}{\Sigma}\frac{e^{z_j}}{\Sigma}=-a_ia_j

综上所述:
aizj={ai(1aj)i=jaiajij\frac{\partial a_i}{\partial z_j}=\left\{\begin{array}{lr} a_i(1-a_j) & i=j \\ -a_ia_j & i\neq j \end{array}\right.

如果引入克罗内克函数δij={1i=j0ij\delta_{ij}=\left\{\begin{array}{lr}1& i=j \\ 0 & i \neq j\end{array}\right.,则上式可以表示为:
aizj=ai(δijaj). \frac{\partial a_i}{\partial z_j}=a_i(\delta_{ij}-a_j).

在有的文献中,使用”1”作为函数名而不是克罗内克δ,则
aizj=ai(1(i=j)aj). \frac{\partial a_i}{\partial z_j}=a_i(1(i=j)-a_j).

最终,我们可以得到损失函数Loss对各参数z1,z2, ,znz_1,z_2,\cdots,z_n的偏导数.

首先, 根据复合函数求导法则:
Losszi=j=1nLossajajzi\frac{\partial Loss}{\partial z_i}=\sum\limits_{j=1}^n\frac{\partial Loss}{\partial a_j}\frac{\partial a_j}{\partial z_i}

Lossaj=(kyklnaj)aj=yj1aj. \frac{\partial Loss}{\partial a_j}=\frac{\partial (-\sum_k y_k \ln a_j)}{\partial a_j}=-y_j\frac{1}{a_j}.

又因为ajzi={aj(1ai)i=jaiajij\frac{\partial a_j}{\partial z_i}=\left\{\begin{array}{lr} a_j(1-a_i) & i=j \\ -a_ia_j & i\neq j \end{array}\right.
,我们把上面的组合起来得到,

Losszi=(j=1nyj1aj)ajzi=yiaiai(1ai)+jiyjajaiaj=yi+aiyi+jiaiyj=yi+aij=1nyj=yi+ai\begin{array}{lcl} \frac{\partial Loss}{\partial z_i}&=&(-\sum\limits_{j=1}^n y_j\frac{1}{a_j})\frac{\partial a_j}{\partial z_i}\\ &=&-\frac{y_i}{a_i}a_i(1-a_i)+\sum\limits_{j\neq i}\frac{y_j}{a_j}a_ia_j\\ &=&-y_i+a_iy_i+\sum\limits_{j\neq i}a_iy_j\\ &=&-y_i+a_i\sum\limits_{j=1}^ny_j=-y_i+a_i \end{array}

最后的结果看起来简单了很多,最后,针对分类问题,我们给定的结果yi最终只会有一个类别是1,其他类别都是0,因此,对于分类问题,这个结果等于:

Losszi=aiyi=ai1, \frac{\partial Loss}{\partial z_i}=a_i-y_i=a_i-1,
即,我们算得的梯度就是神经元的输出-1.