-之前提到的感知机学习法则和LMS算法是为训练单层类似感知机的网络而设计的。对于单层的线性网络来说,误差是网络权值的显式线性函数,它关于网络权值的导数可以轻易得通过计算得到。然而对于多层网络采用非线性的传输函数,网络权值和误差之间的关系更为复杂,反向传播算法是应用于多层网络的学习算法。
本章设计大量的链式求导环节,具体过程容易迷糊。但我认为只要明白了反向传播的思想就好。
实际上,这里将LMS算法中单层网络的最速下降法推广到多层,每一个权值和偏置量的更新方向都沿最终性能指标的负梯度方向,必然带来性能指标对各层的权值和偏置值的求梯度的问题。求梯度的过程伴随着大量的链式求导环节。
由于最后一层的参数与输出结果的关系更接近,更容易求得负梯度方向,同时由于多层网络可以看做为多个单层网络的前后叠加,所以必然会出现从后向前迭代求解负梯度(下降方向)的现象。只不过在具体过程中具体迭代求解的不是负梯度,而是迭代求解一个人为定义的叫做敏感度的量,但它跟负梯度也有着必然关系。
只要理解了这个思路,能应用最后总结的反向传播算法就好。
第十一章 反向传播
多层感知机
上图中画出了三层感知机的示意图,这里只是简单地级联了三个感知机网络。第一网络的输出作为第二个网络的输入,第二个网络的输出又作为第三个网络的输入。每一层可以设置不同数目的神经元甚至不同类型的传输函数。使用上标来表明不同的层号,正如在第二章中提到的那样。
为了标明多层神经网络的结构,有时候我们会采用下面的简化记号,将输入的神经元个数与每层中神经元的个数依次列出。
1. 模式分类
为了说明多层感知机在模式分类中的能力,我们在这里考虑经典的异或问题。异或门的输入/目标输出对如下:
上图描绘了异或问题,可以看出,由于单层感知机只能线性分类的局限性,异或问题中的两个类不是线性可分的,也就无法使用单层感知机进行分类。
两层网络则能够解决异或问题,,事实上,有多种不同的多层解决方案。其中一个是在第一层用两个神经元来构造两个决策边界,第一个决策边界将p1与其他模式区分出来,第二个决策边界再将p4与其他模式区分出来。之后在第二层用逻辑“与”将这两个决策边界结合起来。第一层的每个神经元的决策边界如图所示:
得到的结构为2-2-1的两层神经网络,如下图。右下图给出了最终的决策区域。
2. 函数逼近
到目前为止,讨论的神经网络主要都是用于模式分类的。将神经网络看做函数逼近器同样有启发意义。例如,在控制系统中,目标是找到合适的反馈函数,从而建立测得输出到控制输入的映射。
假设有一个1-2-1的两层神经网络,如下图。第一层的传输函数采用对数-S型
,第二层采用线性函数
。
假设网络中给定的权值和偏置值为:
该网络在输入p在[-2, 2]之间变动时,网络输出a2的变化情况如下图。由图中可以看出,网络的响应有两级台阶组成,每一级分别对应第一层两个对数函数-S型神经元中的一个。
可以通过调整网络参数来改变每一级台阶的形状和位置。两级台阶的中心分别出现在第一层两个神经元各自净输入为零的位置:
每一级台阶的陡峭程度可以通过改变神经网络的权值来调整,以下四张图分别对应了以下参数在一定范围内单独变动时网络的响应。
图a展示了如何通过第一层的网络偏置值确定两级台阶的位置。图b、c展示了如何通过第二层的权值确定两级台阶的坡度。图d展示了如何通过第二层的偏置对网络整体响应进行上下平移。
这从一个方面说明了只要有足够的隐层神经元,就可以用这样的网络逼近任意函数。已经证明:只要有足够多的隐层神经元,一个隐层采用S型传输函数、输出层采用线性传输函数的两层网络几乎可以以任意精度逼近任意函数。
反向传播算法
如前所述,多层网络中前一层的输出作为后一层的输入,该运算用等式表达为:
第一层的神经元接受外部输入:
最后一层神经元的输出作为网络的输出:
三层网络的简化记号如下:
1. 性能指标
多层网络的反向算法是第十章中LMS算法的推广,反向算法与LMS算法一样都使用均方误差作为性能指标。算法同样需要一组反映正确网络行为的样本:
网络的输出都将和目标输出作比较。算法则调整网络的参数以最小化均方误差:
其中x是网络所有权值和偏置值构成的向量
,如果网络有多个输出,则可以推广为:
同LMS算法一样,使用第k次迭代时的误差平方代替误差平方的期望,即使用下式近似代表均方误差:
那么,近似均方误差的最速下降法为:
2. 链式法则
但单层线性网络的偏导数比较容易求得,对于多层网络来说,误差并不是隐层网络权值的显式函数,因此这些导数的计算并不是那么容易。
这里使用复合函数求导中的链式法则来进行计算,链式法则为:
则有:
由于
,则有:
定义
,即
对第m层净输入的第i个元素的变化率(敏感度),则有:
这时梯度下降法可以表示为:
矩阵形式为:
3. 敏感度反向传播
反向传播正是因为计算敏感度
的过程而得名,这个过程描述了一种递归关系,即第m层敏感度是由第m+1层敏感度计算得到的。
为了推导敏感度的递推关系,使用雅可比矩阵:
雅可比矩阵的第i,j项元素计算为:
所以雅克比矩阵可以写为:
其中:
利用矩阵形式的链式法则写出敏感度之间的递归关系:
所以敏感度从网络的最后一层反向传播到第一层:
在最后一层有:
由于
,
则有:
,
写成矩阵形式:
。
这样就可以从最后一层开始,求得所有的敏感度。
4. 总结
综上所述,反向传播算法的步骤为:
将输入传入网络得到输出:
然后,敏感度反向传过网络:
利用最速下降法更新权值和偏置:
批量训练和增量训练
上述的最速下降法引入了“在线训练”或者增量训练方法,也就是说每传过一个样本,网络的权值和偏置值都会更新。我们也可以执行批量训练,先计算完整梯度(即在左右输入都传给网络计算之后),再更新连接权值和偏置值。
例如,假设每个样本出现的概率是一样的,均方误差性能指标可以写为每个样本平方误差梯度的平均:
这个性能指标的总梯度为:
为了实现反向传播算法的批量训练,首先对训练集中所有的样本正向计算实际输出,反向计算敏感度,接着按单个样本梯度的平均以得到总梯度。这样这个批量训练最速下降算法的更新公式为:
使用反向传播
1. 网络结构的选择
只要有足够多的隐层神经元,多层网络几乎可以逼近任意函数,但无法断定,到底需要多少层或者多少神经元。
假设使用1-3-1网络对函数
,在i分别取1、2、4、8时进行近似。可得到近似结果如下图。可以看出i=4时已经达到这个1-3-1网络的能力极限(略有不准确的区域),当i>4时已经不能准确逼近。
假设我们采用不同的网络结构近似函数
,如下图。可以看出只有隐层神经元个数不小于5时,再能比较准确的近似。
我们只能说明一个隐层采用S型神经元、输出层采用线性神经元的
网络的响应由
个S型函数叠加构成。如果要逼近一个具有大量拐点的函数,就需要隐层中具有大量的神经元。
2. 收敛性
由于多层网络的均方误差函数十分复杂,并有许多局部极小值点,使用最速下降法进行优化时最终收敛到的值不一定是全局极小值。为了确保能得到一个最优的解(全局极小值),可以尝试多个不同的初始条件,使函数经由不同的路径下降。
3. 泛化
大多数情况下多层网络由有限个描述网络恰当行为的样本训练:
这个训练集通畅足以代表更大范围内可能 的输入/输出对,网络成功地将它所学到的知识泛化到所有输入/输出的情况。
以下两图中的蓝色曲线分别代表两个网络对同一个函数(黑色曲线)的近似,通过图像可以说明,左侧泛化的不错,右侧泛化的不好。
一个网络要能够泛化,它包含的参数个数应少于训练集中数据点的个数。
神经网络中我们想采用能充分表示训练集的最简单的网络,如果小规模的网络能够胜任就不需要大规模的网络。(称为奥卡姆剃刀原则)
除了使用最简单的网络,还可以采取的办法是在拟合之前停止训练。(后面介绍)
例子——使用反向传播算法对函数进行拟合
使用一个1-2-1网络对函数
进行拟合
使用matlab进行求解:
程序中展现的是对整个训练集学习1000次,对整个训练集学习次数越多,拟合效果越好,如下图:(红色曲线为函数曲线,蓝色为拟合曲线)
clear all
close all
clc;
% 1-2-1
layer = 2; % 层数
input_dim = 1; % 输入向量的维数
nerve1_num = 2; % 第一层神经元个数
nerve2_num = 1; % 第二层神经元个数
%学习率
alpha = 0.1;
% 选定训练集,以0.2的步长等距采样
P = -2:0.2:2; % 输入值
T = object(P); % 目标输出值
% 初始化
W1 = rand(nerve1_num, input_dim); % 第一层的权值矩阵,随机初始化
W2 = rand(nerve2_num, nerve1_num); % 第二层的权值矩阵,随机初始化
B1 = rand(nerve1_num, 1); % 第一层的偏置向量,随机初始化
B2 = rand(nerve2_num, 1); % 第一层的偏置向量,随机初始化
% 将权值矩阵和偏置向量分别放如cell中,方便迭代时调用
W{1} = W1; W{2} = W2;
B{1} = B1; B{2} = B2;
% 确定使用的传输函数
Transfer = [1, 2]; %在这里1代表对数-S型传输函数,2代表线性传输函数
NN = 0;
while NN < 1000
for i = 1:length(P) % 遍历每一个输入
p = P(:, i); % 第i个输入
t = T(:, i); % 第i个输入对应的目标输出
% 由输入正向计算输出
for ii = 1:layer % 遍历每一层
Wi = W{ii};
Bi = B{ii};
transfer = Transfer(ii);
[a, n]= query_output(p, Wi, Bi, transfer); % 第ii层的输出
A{ii} = a; % 存储该层输出
N{ii} = n; % 存储该层净输入
p = a; % 输出作为下一层的输入
end
% 反向计算敏感度
dF = diff_purelin(N{layer});
s = -2 * dF * (t - a); % 计算最后一层的敏感度
S{layer} = s; % 存储敏感度
for ii = 1:layer-1 % 遍历每一层
transfer = Transfer(layer-1);
Ni = N{layer-1};
Wi = W{layer};
s = sensitivities(s, Ni, Wi, transfer);
S{layer-1} = s;
end
% 更新
W{1} = W{1} - alpha * S{1} * p';
B{1} = B{1} - alpha * S{1};
for ii = 2:layer % 遍历每一层
W{ii} = W{ii} - alpha * S{ii} * A{ii-1}';
B{ii} = B{ii} - alpha * S{ii};
end
end
NN = NN + 1;
end
Ptext = -2:0.1:2;
% 由输入正向计算输出
for i = 1:length(Ptext)
p = Ptext(:, i); % 第i个输入
for ii = 1:layer % 遍历每一层
transfer = Transfer(ii);
Wi = W{ii};
Bi = B{ii};
[a, n]= query_output(p, Wi, Bi, transfer); % 第ii层的输出
Atext{ii} = a; % 存储该层输出
Ntext{ii} = n; % 存储该层净输入
p = a; % 输出作为下一层的输入
end
AA(i) = a;
end
plot(Ptext, AA)
hold on
plot(P, T)
% 定义目标函数
function g = object(p)
g = 1 + sin(pi/4 * p);
end
% 定义对数-S型传输函数
function a = logsig(n)
a = 1 ./ (1 + exp(-n));
end
% 定义线性传输函数
function a = purelin(n)
a = n;
end
% 定义对数-S型传输函数的导数
function dF = diff_logsig(n)
a = 1 ./ (1 + exp(-n));
da = (1 - a) .* a;
dF = diag(da, 0);
end
% 定义线性传输函数的导数
function dF = diff_purelin(n)
dF = ones(length(n), length(n));
end
% 每一层由输入正向计算输出
function [a, n] = query_output(p, W, B, transfer)
n = W * p + B;
if transfer == 1
a = logsig(n);
elseif transfer == 2
a = purelin(n);
end
end
% 每一层由反向计算敏感度
function s = sensitivities(s, n, W, transfer)
if transfer == 1
dF = diff_logsig(n);
elseif transfer == 2
dF = ones(length(n), length(n));
end
s = dF * W' * s;
end