从双层感知机到BP神经网络的Delta规则

来源:互联网 发布:通过ip添加网络打印机 编辑:程序博客网 时间:2024/04/30 01:43

@(人工智能)

BP神经网络推导–通过误差传递学习内部表达

这是研一学习一门课程的课程实践,当时和另一位实验室小伙伴合作完成BP神经网络的推导以及部分应用,参考的是一本外文书籍(影印版)的部分章节,有我自己翻译的部分,也有自己的理解,整理在一起。

问题的来源

在这之前,通过两层网络结构了解一些概念!
之前学习过两层网络的问题,是一种简单的直接映射网络,只有输入层和输出层,输入模式经过输入层与输出层映射,得到输出映射。但这样的网络没有隐含层,也就没有内部表达,对输出输出的要求也比较严格,必须满足某种条件的映射。虽然有各种限制,两层神经网络也有一定的应用,两层的网络结构通常来完成一些合理的规则化映射,比如与问题(&),规则明显,通过输入输出之间overlap就可以确定。
- 输入模式:网络的输入向量;
- 输出模式:网络的输出向量;
- 输入层:输入向量对应的输入单元组成的单元集合;
- 输出层:输出向量对应的输出单元组成的单元集合;
- 单元:网络的每个节点。

这里的输入模式和输出模式是指的输入向量和输出向量,一个模式也就对应着一个向量,一个模式对也就是对应着对训练数据。

但是由于这种简单的输入输出的映射的限制,对于特定输入到输出的映射是没有能力满足的。特别是当输入输出的相似的结构提供的表达信息非常不同的时候,没有内部表达的神经网络是不能够提供我们需要的唯一映射。一个比较经典的例子是异或问题(XOR),这时候,overlap可能产生多个结果,因此对这样的问题,就需要内部编码,增强网络的映射。

Input Layer OutPut Layer 0 0 0 0 1 1 1 0 1 1 1 0

对于XOR问题,可以修改双层网络来变相求解,但是通用的方式还是添加内部表达。如下表,在输入层添加一个节点:

Input Layer OutPut Layer 0 0 0 0 0 1 0 1 1 0 0 1 1 1 1 0

这都不是重点,重点是XOR问题,两层结构的网络是没办法解决的(除了上面这个添加输入层节点的方式),这就需要有新的网络结构,这样的新的网络有更强的映射能力,解决两层结构的网络不能解决的问题。

内部表达(internal representation)

什么是内部表达?内部表达实际上是将输入层编码的一种表达,直观上理解是将输入层的输入模式映射的一组编码的网络结构(连接及权重),后面的叙述慢慢会体现这一点。
1969年,Minsky 和 Papert 提供了一个某种条件详细的分析,这种条件存在于一个系统中,这个系统能够解决需要的映射。他们通过大量的案例和网络展示了这种这种条件可以解决问题(我们需要的映射)。另一方面,M&P两人同时还指出,若在一个简单的类似于感知机隐藏单元,通过这个隐含单元,输入层的输入模式会被增强,输入模式总是在隐藏单元被重新编码(或者称作内部表达),在这些隐藏层单元中,它们之间的模式的相似性,支持从输入层到输出层的映射。这样的话,如果输入层到隐藏单元之间存在正确的链接,并且这个隐藏单元集合足够大,那么就能找到一个内部表达,使得这样的网络结构满足输入到输出的映射。这里的内部表达其实就是经过输入层到隐藏层之间的网络连接权重,输入模式被重新编码。这就是引入隐藏层的网络结构,如下图所示。
BP神经网络示意图

利用隐藏层解决XOR问题,在文中给出了一个说明,只需添加了只有一个节点的隐藏层。下图有说明:
XOR问题

这样的网络的存在,说明了隐藏层和内部表达的求解问题(映射)能力。M& P指出这样一个问题:对于没有隐藏层的网络,存在一个非常简单的对所有问题的学习规则,这个规则可以保证解决所有的映射问题(没有隐藏层的网络),这个规则被称作是感知器收敛规则,也被人称作delta规则,但是在具有隐藏层的神经网络中,没有同样的能力的规则。
对于这样的缺陷,有三个基本的反馈(不太理解这里什么意思):
1. 第一个原因是竞争学习,在竞争学习中,使用简单的非监督学习规则,以致于有用的隐藏层单元发展。尽管这些方法是有前景的,但是没有外部的力量保证隐藏层单元适用于我们需要的映射,而且这些映射已经成熟的。
2. 第二个原因是简单的假设内部表达,虽然有一些先验的根据,看起来这是有根据的。这是在verb learning章节和互动激活单词认知模型的策略,
3. 第三个原因是尝试开发一个学习过程,这个过程学习过程由能力学习一个内部表达,内部表达足够解决当前的任务。在玻尔兹曼机的章节中,有描述类似的过程。正如我们所看到的,这个过程包含随机单元的使用,需要使网络达到两个阶段对等的状态,这限制了网络的同步。当然,其他人也使用了随机单元。

在本章节内容,我们提出了一个可供选择的方式,这种方式采用确定的单元(数),并且仅涉及局部计算,是一个delta规则的清晰的泛化。我们称之为通用的delta规则。出于其他方面的考虑,Parker独立推导了类似的泛化,他称之为学习逻辑(learning logic).Lee Cun也研究了一个粗略的学习框架。下面的内容,首先要推导通用delta规则,然后通过一些仿真的结果来说明delta规则的使用,最后,我们指明这个基础想法的更长远的泛化。

通用Delta规则

这这里提到的学习框架,需要涉及一个输入模式输出模式对的集合(就是训练数据)。系统首先用一个输入向量,输入网络结构,得到一个输出向量;每个输入向量都有一个对应的期望输出向量、或者称作是目标向量;比较实际输出向量与期望输出向量的差别,若没有差别,就不再继续学习;否则,连接的权重修改对应的差值(delta差)。

没有隐藏层节点的网络中的Delta规则

先从简单的开始,没有隐藏层节点的网络结构,Delta规则计算。通用计算公式(1)如下:

公式(1):

Δpωji=η(tpjopj)ipi=ηδpjipi

其中:
- tpj表示模式对p的输出向量(期望输出)的第j个元素;
- opj表示模式对p的输入向量得到实际输出(输入向量的表达)的第j个元素;
- ipi表示输入模式的输入向量的第i个元素;
- δpj是简化(tpjopj)
- Δpωji表示输入层第i个节点到输出层第j个节点连接的权重变化;
- η只是一个常量

Delta规则与梯度下降

有很多方法可以推导这个公式,对于当前的目标是,线性神经元,最小化应用每一个输入输出模式向量,得到所有输出单元的实际输出与期望输出的差的平方和,这是非常有用的。得到这个最小值的方法是求解测量误差的梯度值,对于每个权重,根据Delta规则成比例的改变,这个比例是一个负的常数。为了在权重表面空间下降的最快,在这个权重空间任意点的高度应该与测量误差相等(说的比较绕,其实就是求一个梯度)。

具体步骤如下:

公式(2):

Ep=12j(tpjopj)2

表示对于每个输入输出模式p的测量误差,所有输入输出模式的总误差是E=pEp。神经单元是线性的,因此我们实现Delta规则是对E梯度下降的。继续步骤:
Epωji=δpjipi

这与前面计算的Δpωji是成比例的。本部分假设没有隐藏层单元,因此可以直接计算出相关的梯度。下面,我们用链式规则来分解梯度求解:分解为两部分,误差对实际输出的求导,和实际输出对对应的权重求导。

公式(3):

Epωji=Epopjopjωji

前半部分告诉我们误差是怎么根据第j个单元的实际输出改变的,后半部分告诉我们根据地j个节点的输出,权重要改变多少,这样的话,梯度就比较容易计算了。
公式(4):

Epopj=(tpjopj)=δpj

可以看出第j个单元的输出对误差是简单的线性关系。
对于第j个输出单元,计算如下:

公式(5):

opj=iωijipi

可以计算出后半部分:
opjωji=ipi

带回到公式(3):

公式(6):

Epωji=δpjipi

计算整体误差的梯度:

公式(7):

Eωji=pEpωji

从上面的公式可以总结出,一次模式的表达循环结束后,网络的连接权重ωji是与这个梯度是成比例关系的,因此,delta规则就是求误差E的梯度。实际上,这个规则只有在一次循环中权重不改变时才严格为真。通过每次模式被表达后(每次迭代)调整权重,我们从E的严格意义上的梯度中分离一些扩展。然而,考虑到一个学习率是非常小的时候,这个学习率通常是一个概率常数。这个规则通常是被忽略的,delta规则的实现通常也是在误差和的梯度下降的一个近似。特殊的,若存在一个足够小的学习率,那么delta规则就会找到一组权重最小化误差方程。

前向网络半线性激活函数的delta规则。

前面的内容,介绍了线性激活函数的误差平方和的梯度下降的标准delta规则的实现过程。这样的情景中,没有隐含层节点,误差的形状类似于一个碗状,只有一个最小点,所以,梯度下降算法能够保证找到一个最优解。但是,若添加了隐含层节点,就不容易求解梯度,误差的形状不是向上凹的,这种情况就会存在陷入局部最小值的危险。这一章的主要理论贡献是提出一种高效方式计算梯度。主要的经验贡献是展示在很多的学习任务重,局部最小值的致命问题是不相关的。
在本章节的后半部分,我们展示了如何将通用的Delta股则应用于任意的网络,但是,开始我们限制我们在前向传播网络结构。在这些网络结构中,输入层是底层节点,输出层是顶层节点。中间可能会有很多层中间节点,但是这些隐含层节点必须以比他低的层节点的输出作为输入,并将他的输出作为其上一层节点的输入。给定一个输入向量,输出向量是根据这个输入向量迭代前向传递的。

如下图是一个模拟,表示隐含层与输出层的部分连接示意图:

Alt text

(用Visio画的,生成图片后不怎么清晰了)

假设在l层,第j个神经元的总的输入定义如下:

公式(8):

netpj=iwjiopi

其中opi表示在l-1层网络的第i个神经元的输出,也就是第j个神经元的其中一个输入。

那么根据使用激活函数f(),这个j个节点的输出就是这样:

公式(9):

opj=f(netpj)

根据delta规则,权值的变化与误差对权值的微分应该是成负相关的线性比例关系,比如:

公式(10):

Δpωji - Epωji

然后分解:

公式(11):

Epωji = Epnetpjnetpjωji

其中后半部分根据上面的公式(8)其计算公式,可以分解:

公式(12):

netpjωji = ωjikωjkopk=opi

下面定义(公式(11)前半部分):

公式(13):

δpj=Epnetpj

带入到公式(11)中:

公式(14):

 - Epωji = δpjopi

可以看出,根据梯度下降,权重的变化应该是:

公式(15):

Δpωji = ηδpjopi

对于每个神经元ujδpj应该是什么?怎么计算呢?

这个δpj实际上是神经元的残差,通过递归的方式,从输出层依次向后传递,也就是误差回传。

根据链式法则,定义的δpj分解:

公式(16):

δpj=Epnetpj = Epopjopjnetpj

后半部分计算

其中后半部分可以根据公式(9)计算得到:

公式(17):

opjnetpj=fj(netpj)

前半部分计算

后半部分计算完成后,前半部分的计算需要分为两种情况:
第一:当前神经元是输出层,其中的经验损失比较容易就能计算出来,整体的结果也就比较容易计算出来:

公式(18):

Ep=12j(tpjopj)2

公式(19):

Epopj=(tpjopj)=δpj

得到的总体的结果与标准Delta规则一样:

公式(20):

δpj=(tpjopj)fj(netpj)

第二:当前神经元属于中间的隐含层,比如下图所示:
Alt text

神经元j所在的层对下一层,也就是神经元k所在的层都有“贡献”,也就是神经元j的输出对下一层的所有神经元k的误差都有贡献,所以,前半部分的分解如下:
公式(21):

Epopj=kEpnetpknetpkopj=kEpnetpkopjiωkiopi=kEpnetpkωkj=kδpkωkj

公式很长,从左到右逐一分解就可以明白了,因为隐含层的经验误差不容易计算,所以将隐含层的经验误差转换为用输出层的经验误差计算,也就是将输出层的经验误差反向传递。

将公式(21)带回到上面的公式:

公式(22):

δpj=(tpjopj)kδpkωkj

其中δpk表示当前层的下一层的误差,如果只有三层的话,当前层是隐含层,那么下一层就是输出层。

小节

回顾整个过程,Delta规则有三个公式:
1. 标准的Delta规则:Δpωji = ηδpjopi
2. 当前层是输出层,delta的计算公式:δpj=(tpjopj)fj(netpj)
3. 当前层是隐含层,delta的计算公式:δpj=kδpkωkjfj(netpj)

广义的Delta规则是用来计算有隐含层的神经网络的权重变化,总体上看,可以分为两个阶段:
1. 输入模式p,向前计算,对于每个神经元计算出实际输出;
2. 递归实现误差回传,先计算输出层的误差,然后计算倒数第二层的误差,依次计算。直到每一层。

用MATLAB实现一个简单的BP神经网络

function [w1,w2] = MyBP3(tr_data,tr_result,nin,nh,nout,alphain)%%%  tr_data   training data%  tr_result   training result%  nin  the number of input unit%  nh   the number of hiden unit%  nout the number of output unit%  alphain learning rate %% init[nm,nl] = size(tr_data);      %nm is the number of trainning pattern%nl is the length of every pattern%nin = nlinput   = tr_data;            %put the training data to inputtarget  = tr_result;          %put the training result to targetnInput  = nin;nHidden = nh;nOutput = nout;xalpha  = [];ycount  = [];plotDrawStyle10={   struct('color',[1,0,0],'lineStyle','-'),...    struct('color',[0,1,0],'lineStyle','--'),...    struct('color',[0,0,1],'lineStyle',':'),...    struct('color',[0,0,0],'lineStyle','-'),...%    struct('color',[1,1,0],'lineStyle','-'),...%yellow    struct('color',[1,0,1],'lineStyle','--'),...%pink    struct('color',[0,1,1],'lineStyle',':'),...    struct('color',[0.5,0.5,0.5],'lineStyle','-'),...%gray-25%    struct('color',[136,0,21]/255,'lineStyle','--'),...%dark red    struct('color',[255,127,39]/255,'lineStyle',':'),...%orange    struct('color',[0,162,232]/255,'lineStyle','-'),...%Turquoise    };threshold = 0.01;t = 0;%% training model% for alpha = 0.1 : 0.1 : 0.2    alpha = alphain;    % weights    wHI = rand(nInput,nHidden);    wOH = rand(nHidden,nout);    oH     = zeros(nHidden,1);    output = zeros(nOutput,1);    o = zeros(nOutput,1);    errortt = zeros(nm,1);    outputGeneralError = zeros(nOutput,1);    hiddenGeneralError = zeros(nHidden,1);    t = t + 1;    count = 1; %the number of iteration    while count < 100        %loop for training        cc = 1;        while cc <= nm  %loop for patterns            for i = 1 : nOutput  % loop for output                o(i)=target(cc,i);            end            %% output of each unit in hidden layers which is stored in array oH(j)            for i = 1 : nInput     %loop for input                x(i)=input(cc, i);            end            %compute the input of hidden            for i = 1 : nHidden                s=0;                for j = 1 : nInput                    s = s + wHI(j,i) * x(j);                end                oH(i) = sigmf(s, [1,0]);            end            %%  one output of the output layer which is stored in 'output'            for i =1 : nOutput                s=0;                for j = 1 : nHidden                    s=s + wOH(j,i) * oH(j);                end                output(i) = sigmf(s, [1,0]);            end            %%            %errort = 0.5 * ((o(l)-output)^2);            e = 0;            for i =1 : nOutput                e = e +(o(i) - output(i))^2;            end            errort = 0.5 * e;            errortt(cc)=sqrt(e);            %修正隐层到输出层连接权值            for i =1 :nOutput                outputGeneralError(i) = (o(i)-output(i))*output(i)*(1-output(i));                for j=1 : nHidden                    wOH(j,i) = wOH(j,i) + alpha * outputGeneralError(i) * oH(j);                end            end            %修正输入层到中间层的权值            for j = 1 : nHidden                for i =1:nOutput                    hiddenGeneralError(j) = outputGeneralError(i) * wOH(j,i) * oH(j) * (1-oH(j));                end            end            for i = 1 : nInput                for j = 1 : nHidden                    wHI(i,j) = wHI(i,j) + alpha * hiddenGeneralError(j) * x(i);                end            end            cc=cc+1;        end        %计算count一次后的误差        tmp = 0;        for i = 1 : nm            tmp = tmp + errortt(i);        end        tmp = tmp / nm;        error(count) = tmp;        %判断是否小于误差精度        if (error(count) < threshold);            break;        end        count=count+1;    end%     xalpha(end+1) = alpha;%     ycount(end+1) = count-1;%     p = 1 : count-1;%     hold on, plot(p, error(p), plotDrawStyle10{t}); xlabel('Iteration'); ylabel('Error');%     tmpName{t} = ['[' sprintf('%f', alpha) ']'];%     disp(wOH);%     disp(wHI);    w1 = wHI;    w2 = wOH;% end% legend(tmpName);%figure;%plot(xalpha, ycount); xlabel('学习率alpha'); ylabel('Iteration');end

写一个小的测试,验证XOR问题:

%% Example-1 XOR problem%%%%% init environmentclose all;    % close all windowclear all;    % clear all dataclc;          % clear the command window%% setupinput=[0 0; 0 1; 1 0; 1 1];    % the training datatarget=[0;1;1;0];              % the training resultnInput  = 2;   % the number of input unitnHidden = 4;   % the number of hidden unitnOutput = 1;   % the number of output unitalpha   = 0.2; % learning rate%% training the BP neural network[w1,w2] = MyBP3(input,target,nInput,nHidden,nOutput,alpha);   % get the weights of three layers%% test the BP network testdata = [1 0];    % the test data%%  calculate the output of hidden unithiddendata = zeros(nHidden);  % the output of hidden unitfor i =1:nHidden    for j =1:nInput        hiddendata(i) = hiddendata(i) + testdata(j) * w1(j,i);    end    hiddendata(i) =  sigmf(hiddendata(i), [1,0]);end%%  calculate the last output of output unitoutputdata = zeros(nOutput,1);  % the output of output unitfor i =1:nOutput    for j=1:nHidden        outputdata(i) = outputdata(i) + hiddendata(j) * w2(j,i);    end    outputdata(i) = sigmf(outputdata(i),[1,0]);end%% print out the test data disp(['The result of XOR problem [ 1 ,0 ]:',num2str(outputdata)]);

上面两个程序是比较简单的根据Delta规则实现的BP神经网络,网络结构只有三层,当然,熟悉了Delta 规则,也就能写出4层或者更多层的网络,但是多层网络也会有新的问题,这个就是深度学习要解决的问题。比如卷积神经网络,实际上在完全读懂Delta规则之前,我是先熟悉了卷积神经网络,反过来,又重新熟悉了一下BP。

0 0
原创粉丝点击