来源:原始 Jupyter Notebook。内容已转换为网页阅读格式;下载原文件

在上一节中,我们构造了三个两层神经网络来解决 AND、OR、NOT 问题,并构造了一个三层神经网络来解决 XOR 问题,那么这些神经网络结构是如何构造出来的呢?神经元之间的权重和每个神经元的阈值又是如何确定的呢?如果是线性可分问题,两层神经网络就可以解决,这也就是感知机模型,通过前面学习的随机梯度下降法来训练感知机即可求解,如果是线性不可分问题,需要构造更复杂的多层网络结构,通常使用 反向传播算法(error BackPropagation,简称 BP 算法,也叫做 误差逆传播算法)。

神经网络的符号表示

假设我们要求解的神经网络如下图所示:

该神经网络的特点如下:

  • 输入层有 dd 个节点,表示输入的特征向量为 dd
  • 输出层有 ll 个节点,表示输出向量为 ll 维,也就是 ll 类分类问题,l=2l = 2 时就是二分类问题
  • 隐层有 qq 个节点
  • ii 个输入层神经元和第 hh 个隐层神经元之间的连接权重为 vihv_{ih}
  • hh 个隐层神经元的阈值为 γh\gamma_h
  • hh 个隐层神经元和第 jj 个输出层神经元之间的连接权重为 whjw_{hj}
  • jj 个输出层神经元的阈值为 θj\theta_j

所以有,第 hh 个隐层神经元接受到的输入为:

αh=v1hx1+v2hx2++vdhxd=i=1dvihxi \alpha_h = v_{1h}x_1 + v_{2h}x_2 + \dots + v_{dh}x_d = \sum_{i=1}^d v_{ih}x_i

它的输出为:

bh=f(αhγh) b_h = f(\alpha_h - \gamma_h)

这里的 f(z)f(z) 表示激活函数,譬如 Sigmoid 函数。最后得到,第 jj 个输出层神经元的输入为:

βj=w1jb1+w2jb2++wqjbq=h=1qwhjbh \beta_j = w_{1j}b_1 + w_{2j}b_2 + \dots + w_{qj}b_q = \sum_{h=1}^q w_{hj}b_h

它的输出为:

yj=f(βjθj) y_j = f(\beta_j - \theta_j)

神经网络的损失函数

从上面的计算过程中可以看出,这里一共有 dq+lq+q+ldq + lq + q + l 个参数:输入层到隐层的 dqdq 个权值,隐层到输出层的 lqlq 个权值,qq 个隐层神经元的阈值,ll 个输出层神经元的阈值。要求解这些参数,一个很容易想到的方法是使用梯度下降法,首先定义神经网络的损失函数,然后给每个参数一个初始值,再根据损失函数的梯度对初始值迭代更新,最终收敛。那么神经网络的损失函数该如何定义呢?

假设对于输入样本 xk\bf{x}_k 我们有输出 y^k=(y^1k,y^2k,,y^lk)\hat{\bf{y}}_k = (\hat{y}_1^k, \hat{y}_2^k, \dots, \hat{y}_l^k),和线性回归一样,我们可以得到预测值和真实值的平方误差:

Ek=12j=1l(y^jkyjk)2 E_k = \frac{1}{2} \sum_{j=1}^l (\hat{y}_j^k - y_j^k)^2

很显然,我们可以把这个函数当作神经网络的损失函数,我们的目标就是让它的值最小。不过要注意的是,这里的损失函数是定义在某一个样本上的,也就是说每次仅针对一个训练样本更新连接权值和阈值,这种方法叫做 标准BP算法。如果我们把损失函数定义成所有样本损失的平均值的话:

E=1mk=1mEk E = \frac{1}{m} \sum_{k=1}^m E_k

这就是 累积BP算法。这有点类似于随机梯度下降和标准梯度下降。

BP算法的推导

在BP算法中,最重要的就是如何计算各个参数对损失函数的梯度,待确定的参数虽然很多,但是我们可以把它分成四种类型,同一种类型的参数计算方法是一样的,这四种类型分别为:输入层到隐层的权值 vihv_{ih},隐层到输出层权值 whjw_{hj},隐层神经元的阈值 γh\gamma_h,输出层神经元的阈值 θj\theta_j。所以要分别计算下面四个梯度:

vih=ηEkvihwhj=ηEkwhjγh=ηEkγhθj=ηEkθj \begin{align} \nabla v_{ih} &= -\eta \frac{\partial E_k}{\partial v_{ih}} \\ \nabla w_{hj} &= -\eta \frac{\partial E_k}{\partial w_{hj}} \\ \nabla \gamma_{h} &= -\eta \frac{\partial E_k}{\partial \gamma_{h}} \\ \nabla \theta_{j} &= -\eta \frac{\partial E_k}{\partial \theta_{j}} \\ \end{align}

我们先来看 Ekθj\frac{\partial E_k}{\partial \theta_{j}},这是输出层神经元的阈值的梯度,直接对 EkE_k 求导是不行的,因为 EkE_k 并不是 θj\theta_j 的函数,但是我们发现 EkE_kyjy_j 的函数,而 yjy_j 又是 θ\theta 的函数,所以可以使用求导的链式法则:

Ekθj=Ekyjyjθj \frac{\partial E_k}{\partial \theta_{j}} = \frac{\partial E_k}{\partial y_j} \frac{\partial y_j}{\partial \theta_{j}}

其中,yjy_j 是 Sigmoid 函数,它具有如下性质:

f(x)=f(x)(1f(x)) f'(x) = f(x)(1-f(x))

所以:

yjθj=yj(1yj) \frac{\partial y_j}{\partial \theta_{j}} = -y_j(1-y_j)

另外,很容易求得:

Ekyj=y^jyj \frac{\partial E_k}{\partial y_j} = \hat{y}_j-y_j

所以有:

Ekθj=yj(1yj)(y^jyj) \frac{\partial E_k}{\partial \theta_{j}} = -y_j(1-y_j)(\hat{y}_j-y_j)

然后再来计算 Ekwhj\frac{\partial E_k}{\partial w_{hj}},同样,根据链式法则:

Ekwhj=Ekyjyjβjβjwhj \frac{\partial E_k}{\partial w_{hj}} = \frac{\partial E_k}{\partial y_j} \frac{\partial y_j}{\partial \beta_j} \frac{\partial \beta_j}{\partial w_{hj}}

其中,

βjwhj=bh \frac{\partial \beta_j}{\partial w_{hj}} = b_h Ekyjyjβj=Ekβj=Ekθj=yj(1yj)(y^jyj) \frac{\partial E_k}{\partial y_j} \frac{\partial y_j}{\partial \beta_j} = \frac{\partial E_k}{\partial \beta_j} = -\frac{\partial E_k}{\partial \theta_j} = y_j(1-y_j)(\hat{y}_j-y_j)

所以有:

Ekwhj=yj(1yj)(y^jyj)bh \frac{\partial E_k}{\partial w_{hj}} = y_j(1-y_j)(\hat{y}_j-y_j) b_h

然后再来计算 Ekγh\frac{\partial E_k}{\partial \gamma_{h}}

Ekγh=Ekbhbhγh \frac{\partial E_k}{\partial \gamma_{h}} = \frac{\partial E_k}{\partial b_h} \frac{\partial b_h}{\partial \gamma_{h}}

bhb_h 是 Sigmoid 函数,有:

bhγh=bh(1bh) \frac{\partial b_h}{\partial \gamma_{h}} = -b_h(1-b_h)

这里要注意的是 Ekbh\frac{\partial E_k}{\partial b_h} 的求导,bhb_h 和输出层之间有 ll 条连线,所以要求和:

Ekbh=j=1lEkyjyjβjβjbh \frac{\partial E_k}{\partial b_h} = \sum_{j=1}^l \frac{\partial E_k}{\partial y_j} \frac{\partial y_j}{\partial \beta_j} \frac{\partial \beta_j}{\partial b_h}

其中 Ekyjyjβj\frac{\partial E_k}{\partial y_j} \frac{\partial y_j}{\partial \beta_j} 在上面计算过,不妨令其为 gjg_j,另外:

βjbh=whj \frac{\partial \beta_j}{\partial b_h} = w_{hj}

所以:

Ekbh=j=1lgjwhj \frac{\partial E_k}{\partial b_h} = \sum_{j=1}^l g_j w_{hj}

于是就得到了:

Ekγh=bh(1bh)j=1lgjwhj \frac{\partial E_k}{\partial \gamma_{h}} = -b_h(1-b_h) \sum_{j=1}^l g_j w_{hj}

最后我们计算 Ekvih\frac{\partial E_k}{\partial v_{ih}}

Ekvih=Ekbhbhαhαhvih=Ekbhbh(1bh)xi=bh(1bh)xij=1lgjwhj \begin{align} \frac{\partial E_k}{\partial v_{ih}} &= \frac{\partial E_k}{\partial b_h} \frac{\partial b_h}{\partial \alpha_h} \frac{\partial \alpha_h}{\partial v_{ih}} \\ &= \frac{\partial E_k}{\partial b_h} b_h(1-b_h) x_i \\ &= b_h(1-b_h) x_i \sum_{j=1}^l g_j w_{hj} \end{align}

这样就得到了所有参数的梯度更新公式,从而使用梯度下降算法不断迭代,直到收敛,得到各个参数的值。

过拟合

在上面我们学习了如何使用 BP 算法求解神经网络中的各个参数,不过有一点要注意,这个神经网络的隐层有 qq 个神经元,可以证明,只需要隐层包含足够多的神经元,神经网络就可以以任意精度逼近任意复杂度的连续函数。那么这个 qq 该如何选取呢?遗憾的是,一般没什么好的办法,实际运用中都是靠不断的试错来选一个比较靠谱的 qq

正是由于神经网络的表示能力太强大,神经网络经常会遭遇过拟合问题。有两种方法来缓解过拟合:

  • 早停(early stopping):将数据分为训练集和验证集,训练集用来计算梯度、更新权重和阈值,验证集用来估计误差,若训练集误差降低但验证集误差升高,则停止训练,同时返回具有最小验证集误差的权重和阈值。
  • 正则化(regularization):在损失函数中增加一个用于描述网络复杂度的部分,例如权重和阈值的平方和,于是损失函数就变成这样:
E=λ1mk=1mEk+(1λ)iwi2 E = \lambda \frac{1}{m} \sum_{k=1}^m E_k + (1-\lambda) \sum_i w_i^2

全局最小和局部极小

根据梯度下降法求解出来的参数值可以使得当前点的梯度为零,这时我们得到的解是局部极小解,如果损失函数只有一个局部极小,那么这个解也是全局最小解,但是现实任务中,损失函数通常具有多个局部极小,不能保证我们得到的解是全局最小的,我们要想办法避免参数寻优陷入局部极小。通常使用下面几种方法:

  • 以多组不同参数值初始化神经网络,相当于从多个不同的初始点开始搜索,从中选择使损失函数最小的参数;
  • 使用模拟退火(simulated annealing),在每一步都以一定概率接受比当前解更差的结果,在每步迭代过程中,接受次优解的概率要随着时间的推移而逐渐降低,保证算法稳定;
  • 随即梯度下降
  • 遗传算法(genetic algorithms)

参考

  1. https://blog.csdn.net/zhaomengszu/article/details/77834845
  2. https://www.jianshu.com/p/c5cda5a52ee4