人工神经网络(ANN)的公式推导
1. ANN简介
人工神经网络(Artificial Neural Network,ANN)是计算机领域用于处理机器学习问题的强大工具,其广泛应用于回归与分类等问题中,它模拟了生物体神经细胞的运作原理,将一个个具有层次关系,连接关系的人工神经元组成网络结构,通过数学表达的方式模拟神经元之间的信号传递,从而可建立一个具有输入与输出关系、并可通过网络方式可视化的的非线性方程,我们称之为人工神经网络。一般而言,ANN可通过合理的网络结构配置拟合任意非线性函数,因此它也用于处理内部表达较为复杂的非线性系统或黑箱模型。
本文主要针对多维输入,多维输出,多层,并具有全连接结构的神经网络进行模型介绍以及网络权重更新的推导,主要用于解决拟合或回归类问题(暂不适用于分类问题),其网络结构如图所示
除输出层外,每一层都有个偏置节点,并用1表示。
2. ANN网络模型
假设神经网络的层数为K层(K>1),输入层到输出层各层节点个数(不包含偏置节点)分别为为m_0,m_1,m_2,...,m_K,由此定义了输入向量的维度为m_0,输出向量的维度为m_K。网络的每一层输出向量分别表述如下
\begin{split} 输入层:\mathbf{Y}^{(0)}&=[Y_1^{(0)},Y_2^{(0)},...,Y_{m_0}^{(0)}]^T\\ 隐藏层一:\mathbf{Y}^{(1)}&=[Y_1^{(1)},Y_2^{(1)},...,Y_{m_1}^{(1)}]^T\\ 隐藏层二:\mathbf{Y}^{(2)}&=[Y_1^{(2)},Y_2^{(2)},...,Y_{m_2}^{(2)}]^T\\ ...\\ 输出层:\mathbf{Y}^{(K)}&=[Y_1^{(K)},Y_2^{(K)},...,Y_{m_K}^{(K)}]^T \end{split} \\
接下来我们定义每一层的权重矩阵与偏置向量
\begin{split} \mathbf{W}^{(1)}&\in \mathbb{R}^{m_1\times m_0} \qquad &\mathbf{b}^{(1)}&\in\mathbb{R}^{m_1\times 1}\\ \mathbf{W}^{(2)}&\in \mathbb{R}^{m_2\times m_1} \qquad &\mathbf{b}^{(2)}&\in\mathbb{R}^{m_2\times 1}\\ ...\\ \mathbf{W}^{(K)}&\in \mathbb{R}^{m_K\times m_{K-1}} \qquad &\mathbf{b}^{(K)}&\in\mathbb{R}^{m_K\times 1} \end{split} \\
并定义每一层的激活函数分别为f^{(1)},f^{(2)},...,f^{(K)};一般根据不同的应用选择相应的激活函数,每一层所使用激活函数类型可以统一也可不统一;激活函数的类型请参考**激活函数**。 接下来我们通过推导隐藏层一的输出向量的表达式,得出除输入层以外的所有层的通用表达式 对于隐藏层一:
\begin{split} net_i^{(1)}&=\sum_{j=1}^{m_0}W_{i,j}^{(1)}Y_j^{(0)}+b_i^{(1)}, (1\le i\le m_1)\\ \mathbf{net}^{(1)}&=\mathbf{W}^{(1)}\mathbf{Y}^{(0)} + \mathbf{b}^{(1)}\\ \mathbf{net}^{(1)}&=[net_1^{(1)},net_2^{(1)},...,net_{m_1}^{(1)}]^T\\ \mathbf{Y}^{(1)}&=f^{(1)}(\mathbf{net}^{(1)})=[Y_1^{(1)},Y_2^{(1)},...,Y_{m_1}^{(1)}]^T \end{split} \\
这里\mathbf{net}^{(1)}中每个元素表示对输入层向量以及偏置向量的加权和,也可称为第1层(隐藏层1)神经元的输入向量。
因此,对于第k层(k\in\{1,2,...,K\}):
\begin{split} net_i^{(k)}&=\sum_{j=1}^{m_{k-1}}W_{i,j}^{(k)}Y_j^{(k-1)}+b_{i}^{(k)}, (1\le i\le m_k)\\ \mathbf{net}^{(k)}&=\mathbf{W}^{(k)}\mathbf{Y}^{(k-1)}+\mathbf{b}^{(k)}\\ \mathbf{net}^{(k)}&=[net_1^{(k)},net_2^{(k)},...,net_{m_k}^{(k)}]^T\\ \mathbf{Y}^{(k)}&=f^{(k)}(\mathbf{net}^{(k)})=[Y_1^{(k)},Y_2^{(k)},...,Y_{m_k}^{(k)}]^T \end{split} \\
我们通过正向逐层计算,可得到网络中每一层的\mathbf{net}^{(k)}与\mathbf{Y}^{(k)},及每层的输入向量与输出向量,从而也就得到了每个神经元的输入值和输出值。
3. 基于误差反向传播(error back propagation)的网络更新方法
3.1 损失函数与随机梯度下降方法介绍
我们的目标是希望通过调整网络的权重W使得输出和目标有最小的误差,及最小二乘的思想,其损失函数(Loss function)定义为输出向量的均方误差
Loss Function: L = \frac{1}{2}\sum_{i=1}^{m_K}(Y_i^{(K)}-T_i)^2= \frac{1}{2}\sum_{i=1}^{m_K}(\delta_i)^2 \\
其中\delta_i = Y_i^{(K)}-T_i,表示输出向量第i个元素与标签向量第i个元素之间的差值,\frac{1}{2}是为了让后面求导计算更加方便。注:这里的损失函数主要用于解决拟合或回归问题,暂不适用于分类问题,因为分类问题具有不同形式的损失函数。
ANN的网络训练的意图是希望得到一组网络的权重\mathbf{W}使得Loss fuction的值最小(这里\mathbf{W}是所有权重与偏置的集合),因此最传统的方法就是让Loss fuction对\mathbf{W}的导数为0,及
\frac{\partial L}{\partial \mathbf{W}}=\mathbf{0} \\
这里的0被加粗,表明损失函数对所有权重或偏置的导数值都为0。 然而对于复杂的网络,我们很难采用解析的方式求得上式的解或解集,因此采用计算机比较青睐的数值求解法,及采用机器学习领域中最重要的数值求解方法之一**随机梯度下降法**(Stochastic Gradient Descent,SGD),其表达式如下
\mathbf{W}\gets\mathbf{W}-\eta\frac{\partial L}{\partial \mathbf{W}} \\
其中\eta为ANN的学习率,及搜索权重值的步长,\frac{\partial L}{\partial \mathbf{W}}为梯度(Gradient),"-"表示权重的更新方向是朝损失函数减小的方向。 然而,上式只是一种抽象的表达,我们并不能直接利用它来迭代求解ANN各层权重,因此数学家巧妙地研究出了误差的反向转播(error back propagation)方法,及从输出层开始逐层反向计算梯度并逐层更新权重,及
For k\gets K,K-1,...,1
计算: \qquad \frac{\partial L}{\partial \mathbf{W}^{(k)}}, \frac{\partial L}{\partial \mathbf{b}^{(k)}} \\
For k\gets K,K-1,...,1
\begin{split} \mathbf{W}^{(k)}&\gets\mathbf{W}^{(k)}-\eta\frac{\partial L}{\partial \mathbf{W}^{(k)}} \\ \mathbf{b}^{(k)}&\gets\mathbf{b}^{(k)}-\eta\frac{\partial L}{\partial \mathbf{b}^{(k)}} \end{split} \\
因此我们的主要目标便是计算每一层的梯度\frac{\partial L}{\partial \mathbf{W}^{(k)}}与\frac{\partial L}{\partial \mathbf{b}^{(k)}}。为了更好得理解,下面一节首先介绍求解梯度时所需用到得链式求导法则。
3.2 链式求导法则介绍
对于第k层,k\in\{1,2,...,K\},损失函数对该层某个权重W_{i,j}^{(k)}或b_{i}^{(k)}的偏导都可表示为三个偏导数的连乘,如下所示
\begin{split} \left\{ \begin{aligned} \frac{\partial L}{\partial W_{i,j}^{(k)}}&= \frac{\partial L}{\partial Y_i^{(k)}} \frac{\partial Y_i^{(k)}}{\partial net_i^{(k)}} \frac{\partial net_i^{(k)}}{\partial W_{i,j}^{(k)}}= S_i^{(k)} \frac{\partial net_i^{(k)}}{\partial W_{i,j}^{(k)}}& \\ \frac{\partial L}{\partial b_{i}^{(k)}}&= \frac{\partial L}{\partial Y_i^{(k)}} \frac{\partial Y_i^{(k)}}{\partial net_i^{(k)}} \frac{\partial net_i^{(k)}}{\partial b_{i}^{(k)}} = S_i^{(k)} \frac{\partial net_i^{(k)}}{\partial b_{i}^{(k)}}& \end{aligned} \right.&\qquad (1)\\ \end{split}\\ i\in\{1,2,...,m_k\} \qquad j \in \{1,2,...,m_{k-1}\} \\
这里我们另外定义了S_i^{(k)}表示(1)中前两个偏导数的乘积,及
S_i^{(k)}=\frac{\partial L}{\partial Y_i^{(k)}}\frac{\partial Y_i^{(k)}}{\partial net_i^{(k)}}\qquad (2) \\
(1)中的各偏导项的表达式为
\begin{split} \frac{\partial L}{\partial Y_i^{(k)}}&= \left\{ \begin{aligned} (Y_i^{(K)}-T_i)=\delta_i & , & k=K\\ \sum_{l=1}^{m_{k+1}}\frac{\partial L}{\partial net_l^{(k+1)}}\frac{\partial net_l^{(k+1)}}{\partial Y_i^{(k)}} = \sum_{l=1}^{m_{k+1}}\frac{\partial L}{\partial net_l^{(k+1)}}W_{l,i}^{(k+1)}& , & k\ne K \end{aligned} \right.&\qquad (3)\\ \frac{\partial Y_i^{(k)}}{\partial net_i^{(k)}}&=f'^{(k)}(net_i^{(k)}) &\qquad (4)\\ \frac{\partial net_i^{(k)}}{\partial W_{i,j}^{(k)}}&=Y_j^{(k-1)} &\qquad (5)\\ \frac{\partial net_i^{(k)}}{\partial b_{i}^{(k)}}&=1 &\qquad (6)\\ \end{split} \\
下面将根据(1)-(6),从输出层开始,逐层反向推导\frac{\partial L}{\partial W_{i,j}^{(k)}},\frac{\partial L}{\partial b_{i}^{(k)}}以及它们矩阵的表达式。
(1)对于第K层(输出层)
根据(2)(3)(4),我们可以求得S_i^{(K)}
S_i^{(K)}=\delta_i f'^{(K)}(net_i^{(K)}) \qquad (7)\\ \\
然后,\frac{\partial L}{\partial W_{i,j}^{(K)}}和\frac{\partial L}{\partial b_{i}^{(K)}}的表达式为
\begin{split} \frac{\partial L}{\partial W_{i,j}^{(K)}}&=S_i^{(K)} Y_j^{(K-1)} &\qquad (8)\\ \frac{\partial L}{\partial b_{i}^{(K)}}&=S_i^{(K)} &\qquad (9) \end{split} \\
我们已经求出单个分量的求导结果,那么对于整个\mathbf{W}^{(K)}和\mathbf{b}^{(K)}
\begin{split} \frac{\partial L}{\partial \mathbf{W}^{(K)}}&= \left[\begin{array}{lcr} S_1^{(K)} \\ S_2^{(K)} \\ ...\\ S_{m_K}^{(K)} \\ \end{array}\right] [Y_1^{(K-1)},Y_2^{(K-1)},...,Y_{m_{K-1}}^{(K-1)}] &\qquad (10)\\ &=\mathbf{S}^{(K)}\mathbf{Y}^{(K-1)^T}\\ \frac{\partial L}{\partial \mathbf{b}^{(K)}}&=\mathbf{S}^{(K)} &\qquad (11) \end{split} \\
其中
\begin{split} \mathbf{S}^{(K)}&= \left[\begin{array}{lcr} \delta_1 \\ \delta_2 \\ ...\\ \delta_{m_K} \\ \end{array}\right] \odot \left[\begin{array}{lcr} f'^{(K)}(net_1^{(K)}) \\ f'^{(K)}(net_2^{(K)}) \\ ...\\ f'^{(K)}(net_{m_K}^{(K)}) \\ \end{array}\right]\qquad (12)\\ &=f'^{(K)}(\mathbf{net}^{(K)})\odot\mathbf{\delta} \end{split} \\
符号\odot表示尺寸相同的两矩阵逐元素相乘。
(2)当k=K-1时(倒数第二层)
此时式(3)中的\frac{\partial L}{\partial net_l^{(k+1)}}可表示为如下
\begin{split} \frac{\partial L}{\partial net_l^{(K)}}&=\frac{\partial L}{\partial Y_l^{(K)}}\frac{\partial Y_l^{(K)}}{\partial net_l^{(K)}}\\ &=\frac{\partial L}{\partial Y_l^{(K)}}f'^{(K)}(net_l^{(K)})\qquad (13)\\ &=\delta_lf'^{(K)}(net_l^{(K)})\\ &=S_l^{(K)} \end{split} \\
则S_i^{(K-1)}的表达式为
\begin{split} S_i^{(K-1)}&=(\sum_{l=1}^{m_{K}}S_l^{(K)}W_{l,i}^{(K)}) f'^{(K-1)}(net_i^{(K-1)}) \qquad (14)\\ &=(\mathbf{W}_{:,i}^{(K)^T} \mathbf{S}^{(K)}) f'^{(K-1)}(net_i^{(K-1)}) \end{split} \\
则\frac{\partial L}{\partial W_{i,j}^{(K-1)}}与\frac{\partial L}{\partial b_{i}^{(K-1)}}的表达式为
\begin{split} \frac{\partial L}{\partial W_{i,j}^{(K-1)}}&=S_i^{(K-1)}Y_j^{(K-2)}&\qquad (15)\\ \frac{\partial L}{\partial b_{i}^{(K-1)}}&=S_i^{(K-1)}&\qquad (16) \end{split} \\
根据上式,其矩阵表达式为
\begin{split} \frac{\partial L}{\partial \mathbf{W}^{(K-1)}}&= \left[\begin{array}{lcr} S_1^{(K-1)} \\ S_2^{(K-1)} \\ ...\\ S_{m_{K-1}}^{(K-1)} \\ \end{array}\right] [Y_1^{(K-2)},Y_2^{(K-2)},...,Y_{m_{K-2}}^{(K-2)}] &\qquad (17)\\ &=\mathbf{S}^{(K-1)}\mathbf{Y}^{(K-2)^T}\\ \frac{\partial L}{\partial \mathbf{b}^{(K-1)}}&=\mathbf{S}^{(K-1)} &\qquad (18) \end{split} \\
其中
\begin{split} \mathbf{S}^{(K-1)}&= \left[\begin{array}{lcr} \mathbf{W}_{:,1}^{(K)^T} \mathbf{S}^{(K)} \\ \mathbf{W}_{:,2}^{(K)^T} \mathbf{S}^{(K)} \\ ...\\ \mathbf{W}_{:,m_{K-1}}^{(K)^T} \mathbf{S}^{(K)} \\ \end{array}\right] \odot \left[\begin{array}{lcr} f'^{(K-1)}(net_1^{(K-1)}) \\ f'^{(K-1)}(net_2^{(K-1)}) \\ ...\\ f'^{(K-1)}(net_{m_{K-1}}^{(K-1)}) \\ \end{array}\right]\qquad (19)\\ &=f'^{(K-1)}(\mathbf{net}^{(K-1)})\odot (\mathbf{W}^{(K)^T}\mathbf{S}^{(K)}) \end{split} \\
(3)当k=K-2时(倒数第三层)
式(3)中的\frac{\partial L}{\partial net_l^{(k+1)}}可表示为如下
\begin{split} \frac{\partial L}{\partial net_l^{(K-1)}}&=\frac{\partial L}{\partial Y_l^{(K-1)}}\frac{\partial Y_l^{(K-1)}}{\partial net_l^{(K-1)}}\\ &=\frac{\partial L}{\partial Y_l^{(K-1)}}f'^{(K-1)}(net_l^{(K-1)})\\ &=(\sum_{v=1}^{m_{K}}\frac{\partial L}{\partial net_v^{(K)}}\frac{\partial net_v^{(K)}}{\partial Y_l^{(K-1)}}) f'^{(K-1)}(net_l^{(K-1)})\qquad (20)\\ &=(\sum_{v=1}^{m_{K}}S_v^{(K)}W_{v,l}^{(K)})f'^{(K-1)}(net_l^{(K-1)})\\ &=(\mathbf{W}_{:,l}^{(K)^T} \mathbf{S}^{(K)}) f'^{(K-1)}(net_l^{(K-1)})\\ &=S_l^{(K-1)} \end{split} \\
则S_i^{(K-2)}的表达式为
\begin{split} S_i^{(K-2)}&=(\sum_{l=1}^{m_{K-1}}S_l^{(K-1)}W_{l,i}^{(K-1)}) f'^{(K-2)}(net_i^{(K-2)}) \qquad (21)\\ &=(\mathbf{W}_{:,i}^{(K-1)^T} \mathbf{S}^{(K-1)}) f'^{(K-2)}(net_i^{(K-2)}) \end{split} \\
同理可得\frac{\partial L}{\partial W_{i,j}^{(K-2)}}和\frac{\partial L}{\partial b_{i}^{(K-2)}}的表达式为
\begin{split} \frac{\partial L}{\partial W_{i,j}^{(K-2)}}&= S_i^{(K-2)}Y_j^{(K-3)} &\qquad (22)\\ \frac{\partial L}{\partial b_{i}^{(K-2)}}&= S_i^{(K-2)} &\qquad (23)\\ \end{split} \\
同理,其矩阵表达式为
\begin{split} \frac{\partial L}{\partial \mathbf{W}^{(K-2)}}&=\mathbf{S}^{(K-2)}\mathbf{Y}^{(K-3)^T} \qquad &(24)\\ \frac{\partial L}{\partial \mathbf{b}^{(K-2)}}&=\mathbf{S}^{(K-2)} \qquad &(25) \end{split} \\
其中
\mathbf{S}^{(K-2)}=f'^{(K-2)}(\mathbf{net}^{(K-2)})\odot(\mathbf{W}^{(K-1)^T}\mathbf{S}^{(K-1)}) \qquad (26) \\
此时可以发现式(26)与式(19)具有相同的结构,由此我们可以得到任意k下的\mathbf{S}^{(k)},\frac{\partial L}{\partial \mathbf{W}^{(k)}}与\frac{\partial L}{\partial \mathbf{b}^{(k)}}的表达式
\begin{split} \mathbf{S}^{(k)}&= \left\{ \begin{aligned} f'^{(K)}(\mathbf{net}^{(K)})\odot \mathbf{\delta} & , & k=K\\ f'^{(k)}(\mathbf{net}^{(k)})\odot(\mathbf{W}^{(k+1)^T}\mathbf{S}^{(k+1)}) & , & k\ne K \end{aligned} \right.&\qquad (27)\\ \frac{\partial L}{\partial \mathbf{W}^{(k)}}& =\mathbf{S}^{(k)}\mathbf{Y}^{(k-1)^T} &\qquad (28)\\ \frac{\partial L}{\partial \mathbf{b}^{(k)}}& =\mathbf{S}^{(k)} &\qquad (29) \end{split} \\
3.3 权重更新
综上,我们可以得到ANN网络权重更新一次的整个过程
- Step1: 前向计算
\qquadFor k\gets 1,2,...,K
\begin{split} \mathbf{net}^{(k)}&\gets\mathbf{W}^{(k)}\mathbf{Y}^{(k-1)}+\mathbf{b}^{(k)}\\ \mathbf{Y}^{(k)}&\gets f^{(k)}(\mathbf{net}^{(k)}) \end{split} \\
- Step2: 反向逐层梯度计算
\qquadFor k\gets K,K-1,...,1
\begin{split} \mathbf{S}^{(k)}&\gets \left\{ \begin{aligned} f'^{(K)}(\mathbf{net}^{(K)})\odot \mathbf{\delta} & , & k=K\\ f'^{(k)}(\mathbf{net}^{(k)})\odot(\mathbf{W}^{(k+1)^T}\mathbf{S}^{(k+1)}) & , & k\ne K \end{aligned} \right.\\ \frac{\partial L}{\partial \mathbf{W}^{(k)}}& \gets\mathbf{S}^{(k)}\mathbf{Y}^{(k-1)^T} \\ \frac{\partial L}{\partial \mathbf{b}^{(k)}}& \gets\mathbf{S}^{(k)} \end{split} \\
- Step3: 网络参数逐层更新
\qquadFor k\gets K,K-1,...,1
\begin{split} \mathbf{W}^{(k)}&\gets\mathbf{W}^{(k)}-\eta\frac{\partial L}{\partial \mathbf{W}^{(k)}} \\ \mathbf{b}^{(k)}&\gets\mathbf{b}^{(k)}-\eta\frac{\partial L}{\partial \mathbf{b}^{(k)}} \\ \end{split} \\
4. 代码
这里给出一个基于MATLAB(MATLAB的矩阵运算相比较Python更为方便简洁,适用于教学)的简单神经网络代码,用于训练一个8-3-8神经网络,使得输入一个8位单位向量,输出为其本身,从而实现一个类似auto-encoder的功能。这里各层激活函数都采用Sigmoid函数。
% 描述: 训练一个8-3-8的简单神经网络,使得输入等于输出
clear
close all
clc
% 输入层数据为8个8位单位向量
X= [1 0 0 0 0 0 0 0;
0 1 0 0 0 0 0 0;
0 0 1 0 0 0 0 0;
0 0 0 1 0 0 0 0;
0 0 0 0 1 0 0 0;
0 0 0 0 0 1 0 0;
0 0 0 0 0 0 1 0;
0 0 0 0 0 0 0 1];
% 由于要使得输出等于输入,那么标签向量要等于输入向量
Target = X;
% 设置超参数
lr = 0.2; % 学习率
trainNum = 10000; % 训练次数
w1 = rand(3,8); % 初始化权重w1
w2 = rand(8,3); % 初始化权重w2
b1 = rand(3,1); % 初始化偏置 b1
b2 = rand(8,1); % 初始化偏置 b2
% 保存一些训练过程中的变量
loss_history = []; % 训练过程中的损失值历史
for j=1:trainNum
N = size(X,1);
for k=1:N
input = X(:,k); % 得到一个输入向量
target = Target(:,k); % 得到一个目标向量(这里目标向量=输入向量)
% Step1: 前向计算
net1 = w1*input+ b1;
Y1 = 1./(1 + exp(-net1)); % 激活函数为Sigmoid函数
net2 = w2*Y1 + b2;
Y2 = 1./(1 + exp(-net2)); % 输出层的激活函数也设置为Sigmoid函数
% Step2: 反向逐层梯度计算
delta = Y2 - target;
S2 = Y2.*(1-Y2).*delta; % 提示:若Sigmoid函数为s(x), 则s'(x) = s(x)*(1-s(x))
S1 = Y1.*(1-Y1).*(w2'*S2);
dw2 = S2*Y1'; % 得到w2的梯度
dw1 = S1*input'; % 得到w1的梯度
db2 = S2; % 得到b2的梯度
db1 = S1; % 得到b1的梯度
% step3: 网络参数逐层更新
w2 = w2 - lr * dw2;
w1 = w1 - lr * dw1;
b2 = b2 - lr * db2;
b1 = b1 - lr * db1;
loss = 0.5*sum((Y2-target).*(Y2-target)); % 计算损失值
loss_history =[loss_history,loss]; % 保存损失值
end
end
%%%%%%% 训练结束,开始绘图 %%%%%%%%%%%
% 绘制训练过程中loss值曲线
figure(1)
title("Loss curve");
hold on
plot(loss_history)
%最后检查神经网络对原样本的预测准确性:
display('Test');
Y=[];
for i=1:size(X,1)
x=X(:,i);
net1=w1*x+b1;
y1=1./(1+exp(-net1));
net2=w2*y1+b2;
y2=1./(1+exp(-net2));
Y=[Y,y2];
end
Y %输出y
训练结果:
读者可以尝试修改学习率或训练次数,并查看结果。