Improving the way neural networks learn(cross-entropy function)

说在前面的话:由于hexo渲染引擎会把下划线渲染成斜体符号,编辑公式变得格外困难(尽管本地预览一点问题也没有),所以以后下标较多的公式全部用图片代替。
参考资料:neural networks and deep learning


最初我们的网络采用quadratic function作为我们最后的损失函数,但这却带来了一些问题,在neural networks and deep learningChapter3中,作者以单个神经元(激励函数为sigmoid、cost function为quadratic function)为例,input为1,desired output为0,观察训练过程发现,当weight和bias初始值比较大时,会出现前150个训练周期cost都维持在较高水平不变化的情况,观察之前的神经网络也会出现类似的问题。
这显然是与我们的期望不符合的,从人类的认知角度来看,我们当然希望cost越大network学习得越快——即cost下降得越快,就好比显微镜观察事物的粗调节,反之即为细调节;从实际训练来看,出现上面的情况意味着我们需要更多的training epoch,只有当epoch数超过某个“阈值”,神经网络才会开始学到东西,这无疑给训练带来了麻烦。

cost下降缓慢的原因

先以单个神经元为例,quadratic cost function分别对weight和bias求导:
$$
\frac{\partial C}{\partial w}=(a-y)\sigma’(z)x=a\sigma’(z)……(1)
$$

$$
\frac{\partial C}{\partial b}=(a-y)\sigma’(z)=a\sigma’(z)……(2)
$$

式中y和x分别为0和1。观察发现两者导函数中都含有σ’(z)项,这里激励函数为sigmoid function,结合该函数的图像可以发现,当z的绝对值很大时,sigmoid function的导函数σ’(z)趋于0,此时梯度下降法近乎失效,weight和bias几乎不变。
之前搭建的神经网络也有这样的问题,结合BP算法的四个公式
$$
\delta^L=\nabla_aC\bigodot\sigma’(z^L)
$$

$$
\delta^l=((w^{l+1})^T\delta^{l+1})\bigodot\sigma’(z^l)
$$

$$
\frac{\partial{C}}{\partial{w^l_{jk}}}=a^{l-1}_k\delta^l_j
$$

$$
\frac{\partial{C}}{\partial{b^l_j}}=\delta^l_j
$$

发现quadratic function对weight和bias的导函数同样涉及到了σ’(z)项,原因此时就很清晰了。

cross-entropy function的来源

面对上面的问题,显然有两个解决的大方向,一是更改activation function,使得导数不会出现上面的情况,比如ReLU、Leaky ReLU等,对于这样的线性变换,quadratic cost function就不会有这样的问题;二是更改cost function,cross-entropy function就是这样的方法。
既然问题出在σ’(z)这一项上面,就想办法使得cost function求导之后不含这项,仍然先以单个neuron为例,结合(2)式,我们期望
$$
\frac{\partial C}{\partial b}=(a-y)……(3)
$$
新的cost function对b求导后有
$$
\frac{\partial C}{\partial b}=\frac{\partial C}{\partial a}\sigma’(z)
$$
这里激励函数仍为sigmoid函数,所以有
$$
\sigma’(z)=\sigma(z)(1-\sigma(z))=a(1-a)
$$
因此
$$
\frac{\partial C}{\partial b}=\frac{\partial C}{\partial a}a(1-a)
$$
结合(3)式,得到
$$
\frac{\partial C}{\partial a}=\frac{a-y}{a(1-a)}
$$
两边对a积分,得到
$$
C=-[y\ln a+(1-y)\ln(1-a)]+const
$$
对于整个training set的多个input则写作
$$
C=-\frac{1}{n}\sum_x[y\ln a+(1-y)\ln(1-a)]+const
$$
以上是单个neuron的情况,扩展到多层多神经元的网络,有

1

这样的损失函数即为cross-entropy function,代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CrossEntropyCost(object):

@staticmethod
def fn(a, y):
"""Return the cost associated with an output ``a`` and desired output
``y``. Note that np.nan_to_num is used to ensure numerical
stability. In particular, if both ``a`` and ``y`` have a 1.0
in the same slot, then the expression (1-y)*np.log(1-a)
returns nan. The np.nan_to_num ensures that that is converted
to the correct value (0.0).
"""
return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))

@staticmethod
def delta(z, a, y):
"""Return the error delta from the output layer. Note that the
parameter ``z`` is not used by the method. It is included in
the method's parameters in order to make the interface
consistent with the delta method for other cost classes.
"""
return (a-y)

BP算法反向传播时修改如下:

1
2
3
4
# backward pass
delta = (self.cost).delta(zs[-1], activations[-1], y)
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())

cross-entropy function的合理性

首先,理想输出y为0或1(对于每个神经元都如是),0≤a≤1,显然C恒为正数。另外,无论y是0还是1,当a→y时,C总是趋向于0。因此C满足作为cost function的基本要求。
再看看我们之前的期望是否达到,C对weight求导
$$
\frac{\partial C}{\partial w_j}=-\frac{1}{n}\sum_x(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)})\frac{\partial \sigma}{\partial w_j}
$$

$$
=-\frac{1}{n}\sum_x(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)})\sigma’(z)x_j
$$

$$
=\frac{1}{n}\sum_xx_j(\sigma(z)-y)
$$

当输出结果接近desired output时,导数值趋于0,学习较慢,反之学习较快,对于bias也有同样的结果,我们的预期基本达到。

其他

在我们识别手写数字的网络中,y值为0或1,但在其他任务中y可以是0到1之间的值,此时当a=y时,交叉熵函数仍取最小值,此时函数值-[ylna+(1-y)ln(1-a)]称为binary entropy。
cross-entropy function对最后accuracy的提高并没有多大帮助,它主要是使得网络学习过程更加合理且有效率,并且与神经元的饱和有关——而这恰恰是神经网络学习过程中的一个重要问题。

0%