Python-NumPy 搭建 BP 神经网络模块。
BP (Back Propagation) 神经网络是一种用残差反向传播算法训练的多层前馈神经网络。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需已知描述这种映射关系的数学方程。它的学习规则是使用梯度下降法,通过反向传播来不断调整网络的权值和阈值,使网络的损失函数最小。BP神经网络模型拓扑结构包括输入层 (input layer) 、隐藏层 (hidden layer) 和输出层 (output layer) 。
人工神经元模型
神经网络中的基本运算单元为人工神经元。其输出为
$$h_w(x)=f(\sum_{i=0}^nw_ix_i)$$
其中,$x_0=1$ 为偏置 (bias) 单元;$f(\cdot)$ 为激励函数。
常用的激励函数有 $sigmoid$ 和 $tanh$。
$$\sigma(x)=\frac{1}{1+\mathrm{e}^{-x}}\in(0,1)$$
$$\tanh(x)=\frac{\mathrm{e}^x-\mathrm{e}^{-x}}{\mathrm{e}^x+\mathrm{e}^{-x}}\in(-1,1)$$
两个函数之间存在线性关系:
$$\tanh(x)=2\sigma(2x)-1$$
这两个函数的一个重要特性是它们都是可微分的,这使得网络能够应用梯度下降算法来进行训练。它们的一阶导数分别为
$$\frac{\mathrm{d}\sigma(x)}{\mathrm{d}x}=\sigma(x)(1-\sigma(x))$$
$$\frac{\mathrm{d}\tanh(x)}{\mathrm{d}x}=1-\tanh(x)^2$$
神经网络类
定义神经网络类 $nn$ 如下:
|
|
前向传播
设$i$、$j$分别为网络中第$l$和第$l+1$层的单元,第$l$层的单元数为$N_l$。将单元$j$的输入记为$u_j$,输出记为$a_j$,从单元$i$到单元$j$连接的权重记为$w_{ij}$,则有
$$u_j=\sum_{i=0}^{N_l}w_{ij}a_i$$
对于隐藏层单元,输出为输入的激励:
$$a_j=f_j(u_j)$$
对于$K$类分类问题$(K>2)$,设置$K$个输出单元,并且采用$softmax$函数归一化得到样本属于第k类的概率:
$$p(C_k)=y_k=a_k=\frac{e^{u_k}}{\sum_{k’=1}^Ke^{u_{k’}}}$$
目标向量$z$是$K$维向量,采用0,1标记所属类别。如对于$K=5$,若正确的类为$C2$,则$z=(0,1,0,0,0)$。目标概率可表达为
$$p(\mathbf{z}|\mathbf{x})=\prod\{k=1}^Ky_k{z_k}$$
采用神经网络进行分类时,将样本指派给概率最大的类。
定义损失函数为目标概率的负对数:
$$L(\mathbf{y},\mathbf{z}) = -\log p(\mathbf{z}|\mathbf{x})=-\sum_{k=1}^Kz_k\ln y_k$$
$nn$类前向传播函数的代码如下:
后向传播
后向传播算法主要应用了偏导的链式法则。首先需要计算损失函数关于输出的偏导,对于$K$类分类问题$(K>2)$
$$\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial y_k} = -\frac{z_k}{y_k}$$
$$\frac{\partial y_{k’}}{\partial u_k}=y_k(\delta_{kk’}-y_{k’})$$
则根据链式法则,有
$$\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial u_k} = \sum_{k’=1}^K\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial y_{k’}}\cdot\frac{\partial y_{k’}}{\partial u_k} = \sum_{k’=1}^K-\frac{z_k}{y_{k’}}\cdot y_k(\delta_{kk’}-y_{k’}) = y_k-z_k$$
定义损失函数关于网络中单元$j$输入的偏导:
$$\delta_j\overset{\mathrm{def}}{=}\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial u_j}$$
设$i$、$j$分别为网络中第$l$和第$l+1$层的单元,第$l$层的单元数为$N_l$。对于隐藏层单元$i$,其$\delta$值可由后一层递归计算得到:
$$\delta_i = \frac{\partial L(\mathbf{y},\mathbf{z})}{\partial a_i}\cdot\frac{\partial a_i}{\partial u_i} = \frac{\partial a_i}{\partial u_i}\sum_{j=1}^{H_{l+1}}\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial u_j}\cdot\frac{\partial u_j}{\partial a_i} = f_i’(u_i)\sum_{j=1}^{H_{l+1}}\delta_jw_{ij}$$
当计算得到所有隐藏层单元的$\delta$值后,可计算损失函数关于网络中权重的偏导:
$$\frac{\partial L(\mathbf{y},\mathbf{z})}{\partial w_{ij}} = \frac{\partial L(\mathbf{y},\mathbf{z})}{\partial u_j}\cdot\frac{\partial u_j}{\partial w_{ij}} = \delta_ja_i$$
$nn$类后向传播函数的代码如下:
网络权重更新
采用梯度下降算法对网络的权重进行更新:
$$\Delta w_{ij} := -\alpha\frac{\partial L}{\partial w_{ij}}$$
$$w_{ij} := w_{ij} + \Delta w_{ij}$$
为了加速算法收敛,可引入$momentum$项。$momentum$项模拟物体运动时的惯性,更新权重时在一定程度上保留之前更新的方向,同时利用当前的梯度调整更新方向。
$$\Delta w_{ij} := m\Delta w_{ij}-\alpha\frac{\partial L}{\partial w_{ij}}$$
$nn$类网络权重更新函数的代码如下:
网络训练和测试
为了更好地管理数据集,定义数据集类$dataset$类,并定义成员函数 next_batch 用于批量取出数据集中的样本。
定义$nn$类的训练和测试函数如下: