BackPropagation学习记

前向传播数值,反向传播误差
前向传播数值,反向传播误差

Backpropogation的提出和在神经网络中的使用是在Rumelhart,Hinton & William (1986a),然后在Rumelhart,Hinton & William (1986b)中进行了阐述和推广,但是这个技术被独立地重新发现了很多次,并且有许多先例到1960s。(百度百科上说最先在1974是不严谨的)
Henry J.Kelly在1960年发表在ARS Journal的《Gradient theory of optimal flight paths》和Bryson,Arthur E(他也是现代最优控制理论之父)在1961年“关于数字计算机及其应用的专题研讨会”上的《A gradient method for optimizing multi-stage allocation processes》(次年发表在《哈佛大学学报》)最先推导出基本的连续BP算法。
此后也陆续有人提出一些推导、描述,直到Paul Werbos在1974的论文《Beyond Regression:New Tools for Prediction and Analysis in the Behavioral Sciences》对BP深入分析后首先在美国提出可以将BP用于神经网络。(更多历史细节参加英文版维基百科)

什么是BP算法

Backpropogation(backward propogation of errors),是一种使用gradient descent(梯度下降)有监督学习的ANN(人工神经网络)。给定一个ANN和一个误差函数,此方法计算误差函数对神经网络权重的偏导。

BP神经网络是一种按误差逆传播算法训练的多层前馈网络。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断 调整网络的权值和阈值,使网络的误差平方和最小。

图1 BP全神经网络图(MISO)
图2 BP全神经网络图(MIMO)

网上有很多关于BP算法神经网络的全图,例如上述的图示,输入经过多层神经元后再输出。每个圆圈都是一个神经元。输入数据的是输入层,用x来表示,最终输出结果的是输出层,用y来表示中间的是隐层,隐层至少为一层,没有上限,不过每多一层可能会多花费一些时间。BP网络既可以实现多输入单输出(MISO,见图1),也可以实现多输入多输出(MIMO,见图2)。而且隐层中的每一层所包含的神经元个数可以是不同的。

我看到的网上关于BP算法的推导基本都是一项项来推到的,而且我没看到完全是作为初学者编的一步一步实现程序的例子(可能是我没去细找),但是我不太喜欢网上很多文章要么没有过程,要么过程过多,甚至标上标和下标的,这种做法对于初学者真的很不友好。下面,我尝试使用向量的方法推导整个过程。

图3 BP网络结构图

我自己画了张单个神经元的图片,如下图所示:

图4 单个神经元结构图

如图4所示,输入我使用x向量来描述,权重w(英文weight的首字母)向量,第一个隐层神经元的输入就是\( \Sigma w_i x_i \),亦即MATLAB中的w中元素乘以x中对应的元素,即w.*x。高中生物学过神经元的激活是需要一个阈值的,这个阈值用\( \theta \)表示,现在有效输入为\( net_i =\Sigma w_i x_i -\theta_i \),输出则为\( y=f(net) \)。为了能让输入和输出扯上关系,这里f需要是一种特殊的函数,常见的有好几种,这里使用sigmoid函数(\( f(x)=\frac{1}{1+e^{-\alpha x}} \),不过\(\alpha\)一般取1,下面为推导简洁,取\(\alpha=1\)),sigmoid的导数和它本身相关(\(f'(x)=f(x)(1-f(x))\))。

同理,将上一层的输出y看作是本层的输入x时,上面又成立。为了写代码方便,我将第一层输入x仍然写为x,第一层权重写为w(1,:),第一层有效输入写作net(1,:),第一层输出写作y(1,:),第二层输入写作y(1,:),第二层权重写为w(2,:),第2层有效输入写作net(2,:),第二层输出写作y(2,:),以此类推。最后一层输入为y(m-1,:),最后一层权重写为w(m,:),最后一层有效输入写作net(m,:),最后一层输出写作y(m,:),而这最后一层输出y(m,:)就是神经网络的实际输出。这个矩阵w就是我们想要的各层权重。

前面说过bp是误差反向传播,那这个误差是怎么定义的,这里使用\( e=\frac{1}{2} (d-y)^2 \)(此处y特指y(m,:)),在MATLAB中描述就是e=1/2*(d-y(m,:).^2。
至于误差反向传播到w,则需要求\( \frac{\partial e}{\partial w_i}\)。
首先求\(\frac{\partial e}{\partial net_m}=(d-y)f(net_m)(1-f(net_m))\),
在MATLAB中描述就是Partial_Derivative_e_net_m=(d-y(m,:)) .*my_sigmoid(net(m,:)).*(1-my_sigmoid(net(m,:)))。
对于\(\frac{\partial e}{\partial w_m}=(d-y)f(net_m)(1-f(net_m))*y_{m-1}\)。
对于\(\frac{\partial e}{\partial w_i}(2\le i \le m-1)\),还需要求\(\frac{\partial net_m}{\partial net_i}\)。

\(\frac{\partial net_{k+1}}{\partial net_k}=w_{k+1}f(net_i)(1-f(net_i))\)。
所以
\(\begin{align}\frac{\partial e}{\partial w_i}&=\frac{\partial e}{\partial net_m}\frac{\partial net_m}{\partial net_i}\frac{\partial net_i}{\partial w_i} \\&=(d-y)f(net_m)(1-f(net_m))…f(net_i)(1-f(net_i))y_{i-1}(2\le i \le m-1)\end{align}\)

\(\begin{align}\frac{\partial e}{\partial w_1}&=\frac{\partial e}{\partial net_m}\frac{\partial net_m}{\partial net_1}\frac{\partial net_1}{\partial w_1}\\&=(d-y)f(net_m)(1-f(net_m))…f(net_1)(1-f(net_1))x\end{align}\)

需要注意的是,BP算法不仅仅只有误差反向传播,还有数值前向传播。即

\(\begin{align} x\stackrel{w_1}{\longrightarrow}net_1\stackrel{}{\longrightarrow}y_1\stackrel{w_2}{\longrightarrow}net_2\stackrel{}{\longrightarrow}y_2…y_i\stackrel{w_i}{\longrightarrow}net_i\stackrel{}{\longrightarrow}y_{i+1}…y_{m-1}\stackrel{w_m}{\longrightarrow}net_m\stackrel{}{\longrightarrow}y_m\end{align} \)

而对于误差

\( \begin{eqnarray} y_m\stackrel{\frac{\partial e}{\partial net_m}}{\longrightarrow} &net_m& \stackrel{\frac{\partial net_m}{\partial net_{m-1}}}{\longrightarrow}&net_{m-1}&… &net_{i+1}&\stackrel{\frac{\partial net_{i+1}}{\partial net_i}}{\longrightarrow}&net_i&…&net_2&\stackrel{\frac{\partial net_2}{\partial net_1}}{\longrightarrow}&net_1 \\&\downarrow\tiny\frac{\partial net_m}{\partial w_m}& &\downarrow\tiny\frac{\partial net_{m-1}}{\partial w_{m-1}}& &\downarrow\tiny\frac{\partial net_{i+1}}{\partial w_{i+1}}& &\downarrow\tiny\frac{\partial net_i}{\partial w_i}& &\downarrow\tiny\frac{\partial net_2}{\partial w_1}& &\downarrow\tiny\frac{\partial net_1}{\partial w_1}& \\ &\Delta w_m& &\Delta w_{m-1}& &\Delta w_{i+1}& &\Delta w_i& &\Delta w_2& &\Delta w_1 & \end{eqnarray} \)

当使用sigmoid作为激活函数时,

\( \frac{\partial e}{\partial net_m}=(d-y_m)f(net_m)(1-f(net_m))\),
\( \frac{\partial net_{i+1}}{\partial net_i}=f(net_i)(1-f(net_i))(1\le i \le m-1)\),
\( \frac{\partial net_i}{\partial w_i}=(d-y_m)f(net_m)(1-f(net_m))\),
\( \begin{align}\frac{\partial e}{\partial w_i}=\begin{cases}&(d-y_m)f(net_m)(1-f(net_m)) &i=m\\ &(d-y_m)f(net_m)(1-f(net_m))…f(net_i)(1-f(net_i))y_i &2\le i \le m-1 \\ &(d-y_m)f(net_m)(1-f(net_m))…f(net_i)(1-f(net_i))…f(net_1)(1-f(net_1))x &i=1\end{cases}\end{align}\)
\(w_{i_{new}}=w_{i_{old}}-\Delta w_i \)

有了上述推导,就可以开始编程了。

代码示例

按照传播方向写的代码例子:

%% BP算法
%作者:zecazi(https://zecazi.com)
%版本:beta
%双隐层拟合sin函数输出
%% 
close all;
clear;
clc;
%% 数值正向传播
n=50;
alpha=0.1;
learning_rate=0.01;
x=0:pi/(n-1):pi;%x为输入向量
d=sin(x);%d为期望输出向量
w_1=rand(1,n);
deta_w_1=zeros(size(w_1));
Theta_1=0.5*ones(1,n);
net_1=w_1.*x-Theta_1;
y_1=my_sigmoid(net_1);
w_2=rand(1,n);
deta_w_2=zeros(size(w_2));
Theta_2=0.5*ones(1,n);
net_2=w_2.*y_1-Theta_2;
y_2=my_sigmoid(net_2);
w_3=rand(1,n);
Theta_3=0.5*ones(1,n);
net_3=w_3.*y_1-Theta_3;
y_3=my_sigmoid(net_3);
%% 误差反向传播
%e_2=1/2*(d-y_2).^2;
%∂y_2/∂net_2=my_sigmoid(net_2)*(1-my_sigmoid(net_2))
%由链式求导法则有
%∂e_2/∂w_2=(d-y_2)*my_sigmoid(net_2)*(1-my_sigmoid(net_2))*y_1
circulation=1000000;
error_sum=zeros(1,circulation);
for i=1:circulation
    deta_w_3=-learning_rate*(d-y_3).*my_sigmoid(net_3).*(1-my_sigmoid(net_3)).*y_2+alpha*deta_w_2;
    w_3=w_3-deta_w_3;
    deta_w_2=-learning_rate*(d-y_3).*my_sigmoid(net_3).*(1-my_sigmoid(net_3)).*w_3.*my_sigmoid(net_2).*(1-my_sigmoid(net_2)).*y_1+alpha*deta_w_1;
    w_1=w_1-deta_w_1;
    deta_w_1=-learning_rate*(d-y_3).*my_sigmoid(net_3).*(1-my_sigmoid(net_3)).*w_3.*my_sigmoid(net_2).*(1-my_sigmoid(net_2)).*my_sigmoid(net_1).*(1-my_sigmoid(net_1)).*x;
    net_1=w_1.*x-Theta_1;
    y_1=my_sigmoid(net_1);
    net_2=w_2.*y_1-Theta_2;
    y_2=my_sigmoid(net_2);
    net_3=w_3.*y_2-Theta_3;
    y_3=my_sigmoid(net_3);
    error_sum(i)=sum(1/2*(d-y_3).^2);
end
%% BP得到的结果
net_1=w_1.*x-Theta_1;
y_1=my_sigmoid(net_1);
net_2=w_2.*y_1-Theta_2;
y_2=my_sigmoid(net_2);
net_3=w_3.*y_2-Theta_3;
y_3=my_sigmoid(net_3);
%% 绘图
figure;
plot(x,d);
axis tight;
hold on;
plot(x,y_3);
legend('期望输出','实际输出');
title('OUTPUT');
hold off;
figure;
plot(error_sum);
title('总误差曲线');
%% 激活函数
%sigmoid函数
function y=my_sigmoid(x)
    alpha=1;
    y=1./(1.+exp(-alpha.*x));
end

可更改层数和循环次数的代码:

%% BP算法
%作者:zecazi(https://zecazi.com)
%版本:beta
%多隐层拟合sin函数输出
%% 
close all;
clear;
clc;
%% 数值正向传播
n=50;
m=input('请输入神经网络层数:');%m至少大于等于2
x=0:pi/(n-1):pi;%x为输入向量
d=sin(x);%d为期望输出向量
%
alpha=0.1;
learning_rate=0.01;
w=rand(m,n);
deta_w=zeros(m,n);
net=zeros(m,n);
y=zeros(m,n);
Theta=0.5*ones(m,n);
net(1,:)=w(1,:).*x-Theta(1,:);
y(1,:)=my_sigmoid(net(1,:));
for i=2:m
    net(i,:)=w(i,:).*y(i-1,:)-Theta(i,:);
    y(i,:)=my_sigmoid(net(i,:));
end
%% 误差反向传播
%e_2=1/2*(d-y_2).^2;
%∂y_2/∂net_2=my_sigmoid(net_2)*(1-my_sigmoid(net_2))
%由链式求导法则有
%∂e_2/∂w_2=(d-y_2)*my_sigmoid(net_2)*(1-my_sigmoid(net_2))*y_1
circulation=input('请输入训练次数:');
error_sum=zeros(1,circulation);
for k=1:circulation
    Partial_Derivative_e_net_m=(d-y(m,:)).*my_sigmoid(net(m,:)).*(1-my_sigmoid(net(m,:)));
    deta_w(m,:)=-learning_rate*Partial_Derivative_e_net_m.*y(m-1,:)+alpha*deta_w(m-1,:);
    Partial_Derivative_net_m_net_i=ones(1,n);
    for i=m-1:-1:2
        Partial_Derivative_net_m_net_i=w(i+1,:).*my_sigmoid(net(i,:)).*(1-my_sigmoid(net(i,:))).*Partial_Derivative_net_m_net_i;       
        deta_w(i,:)=-learning_rate*Partial_Derivative_e_net_m.*Partial_Derivative_net_m_net_i.*y(i-1,:)+alpha*deta_w(i-1,:);
    end
    Partial_Derivative_net_m_net_i=w(2,:).*my_sigmoid(net(1,:)).*(1-my_sigmoid(net(1,:))).*Partial_Derivative_net_m_net_i;
    deta_w(1,:)=-learning_rate*Partial_Derivative_e_net_m.*Partial_Derivative_net_m_net_i.*x;
    for i=1:m
        w(i,:)=w(i,:)-deta_w(i,:);
    end
    net(1,:)=w(1,:).*x-Theta(1,:);
    y(1,:)=my_sigmoid(net(1,:));
    for i=2:m
        net(i,:)=w(i,:).*y(i-1,:)-Theta(i,:);
        y(i,:)=my_sigmoid(net(i,:));
    end
    error_sum(k)=sum(1/2*(d-y(m,:)).^2);
end
%% BP得到的结果
net(1,:)=w(1,:).*x-Theta(1,:);
y(1,:)=my_sigmoid(net(1,:));
for i=2:m
    net(i,:)=w(i,:).*y(i-1,:)-Theta(i,:);
    y(i,:)=my_sigmoid(net(i,:));
end
%% 绘图
figure;
plot(x,d);
axis tight;
hold on;
plot(x,y(m,:));
legend('期望输出','实际输出');
title('OUTPUT');
hold off;
figure;
plot(error_sum);
title('总误差曲线');
%% 激活函数
%sigmoid函数
function y=my_sigmoid(x)
    alpha=1;
    y=1./(1.+exp(-alpha.*x));
end

拟合图像:

  • 将第11行d=sin(x)改为d=rand(size(x)).

单隐层训练一百万次如下:

单隐层训练一百万次误差如下:

如上图所示,虽然是单隐层,但训练一百万次后肉眼已经无法辨别误差,根据MATLAB工作区的数据,此误差为2.9344e-07,非常小,可以认为训练效果非常好。

遇到的问题:

  • 尽管神经网络可以拟合非线性函数,但对于\(y=\sin x(0\le x \le\pi)\)这种函数拟合似乎并不好,且主要是在1附近和0附近时,单纯BP网络并没有实现非常好的拟合。

单隐层训练一百万次图像如下:

单隐层训练误差如下:

19隐层训练一百万次图像如下:

19隐层训练误差如下:

  • 可以看到BP神经网络尽管训练完,在中间部分能实现较好拟合,但是对于最大值和最小值附近拟合程度并不好,目前我并不知原因,推测可能是神经网络陷入了局部最优解。不过增加层数后,对于最小值附近的拟合有明显改善,但是对于最大值附近,仍然未有明显改善。

延伸思考:之前在网上读到,对于凸函数(convex function,是同济版《高等数学》中的上凹函数)。一般而言,凸函数易于优化:可以得出关于收敛到全局最小值的理论保证。而且凸函数非常适合线性分类器。

此处可能是因为选用的sigmoid函数的原因,因为sigmoid函数(\( S(x)=\frac{1}{1+e^{-\alpha x}} \))取值范围为0到1,且达不到0或1。

  • 同理,此程序也不能直接用于\(y=\cos x(0\le x \le\pi)\)的拟合,因为此时y可能取到负值。

参考文献:
1.神经网络算法及MATLAB应用实例(一)
2.Backpropagation for dummies
3.Backpropagation
4.Backpropogation
5.反向传播算法
6.计算机视觉中的深度学习5: 神经网络
7.BP神经网络(原理及MATLAB实现)
8.Backpropagation in Neural Networks: Process, Example & Code
9.凸函数
10.7 Types of Neural Network Activation Functions: How to Choose?
11.BP原理与实现
12.Back Propagation Algorithm in Neural Network
图片引用:
13.神经网络,BP算法的理解与推导
14.机器学习——前馈神经网络
15.使用latex绘制多层神经网络结构图

默认图片
admin
文章: 6

留下评论