分享一份之前上神经网络课上布置的练习题Demo。参数什么的可能在不同电脑上需要有所调整,还望用餐愉快。
设计实现模糊控制规则为T = int((e+ec)/2)的模糊神经网络控制器,其中输入变量e和ec的变化范围分别是:e = int[-2, 2],ec = int[-2, 2]。网络设计的目标误差为0.001。
输入输出数据根据题意列出:
P=[-2 -2 -2 -2 -2 -1 -1 -1 -1 -1 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2; -2 -1 0 1 2 -2 -1 0 1 2 -2 -1 0 1 2 -2 -1 0 1 2 -2 -1 0 1 2 ];
T=[-2 -2 -1 -1 0 -2 -1 -1 0 0 -1 -1 0 0 1 -1 0 0 1 1 0 0 1 1 2];
因此输入为25组样本,需要输入节点数为2,输出节点数仅需1即可。
由于输入输出节点数很少,因此优先考虑单层隐含层神经网络。即网络结构初步定为2-S1-1,隐含层采用S型正切激活函数,输出层采用线性激活函数,隐含层节点数待定。
确定隐含层节点数需要通过对S1取不同值的训练情况来分析,得出较好的S1作为确定的节点数。在此之前,需要大致初始化一个学习速率 lr。取最大训练次数epochs=10000 ;目标误差goal=0.001。
网络初始化及训练的MATLAB代码:
clear;
ec=[-2 -1 0 1 2];
%输入矢量,输入节点为2
P=[-2 -2 -2 -2 -2 -1 -1 -1 -1 -1 ...
0 0 0 0 0 1 1 1 1 1 2 2 2 2 2;
-2 -1 0 1 2 -2 -1 0 1 2 -2 -1 ...
0 1 2 -2 -1 0 1 2 -2 -1 0 1 2];
%输出矢量
T=[-2 -2 -1 -1 0 -2 -1 -1 0 0 -1 ...
-1 0 0 1 -1 0 0 1 1 0 0 1 1 2];
[R, Q]=size(P);
[S2, Q]=size(T);
%初始化网络结构和参数
%隐含层节点数,需要修改调整的参数
%输出层采用线性激活函数,隐含层采用S型正切函数
S1=10;
W1=rands(S1,R);
B1=rands(S1,1);
W2=rands(S2,S1);
B2=rands(S2,1);
%创建两层前向回馈网络,梯度下降法
net=newff(minmax(P),[S1,1],{
'tansig','purelin'},...
'traingd');
%初始化训练次数
net.trainParam.epochs=10000;
net.trainParam.goal=0.001;
%调整学习速率
net.trainParam.lr=0.03;
%训练网络,tr是训练参数
[net,tr]= train(net,P,T);
Y = sim(net,P);
%查看训练时间
t=tr.time(end)
%计算均方差
SSE = perform(net,T,Y)
得到的训练数据如下表所示:
表 1 取 lr = 0.030 时的网络训练记录
S1 | 误差平方和 | 训练次数 | 时间(秒) | 误差过程记录 |
---|---|---|---|---|
8 | 0.0328 | 10000 | 22.136 | 有收敛趋势 |
9 | 0.0436 | 10000 | 23.076 | 有收敛趋势 |
10 | 0.0334 | 10000 | 25.421 | 曲线平滑,有收敛趋势 |
11 | 0.0211 | 10000 | 23.254 | 曲线有波动,有收敛趋势 |
12 | 0.0039 | 10000 | 21.759 | 前期下降快,后期收敛很慢 |
13 | 0.0093 | 10000 | 24.266 | 梯度变化部分不均匀 |
即使隐含层节点已经相对较多的情况下效果依然不够理想,但并未出现振荡及发散现象。推测lr取值较小导致,因而收敛速度较慢,因此适当增大lr的取值,新得到的训练结果如表2所示:
表 2 取 lr = 0.060 时的网络训练记录
S1 | 误差平方和 | 训练次数 | 时间(秒) | 误差过程记录 |
---|---|---|---|---|
6 | 0.0257 | 10000 | 27.169 | 曲线较平滑,有收敛趋势 |
7 | 0.0211 | 10000 | 29.476 | 曲线较平滑,有收敛趋势 |
8 | 0.0036 | 10000 | 28.869 | 有下降收敛趋势 |
9 | 0.0147 | 10000 | 26.687 | 曲线有点波动,有收敛趋势 |
10 | 0.0031 | 10000 | 26.638 | 下降较快,曲线非常平滑 |
11 | 0.0010 | 7425 | 20.199 | 下降很快,曲线平滑 |
12 | 0.0027 | 10000 | 27.884 | 有下降收敛趋势,曲线平滑 |
13 | 0.0010 | 8996 | 25.801 | 曲线光滑但后期收敛慢 |
15 | 0.0010 | 4976 | 14.838 | 下降很快,梯度变化部分不均匀 |
18 | 0.0010 | 869 | 5.766 | 下降很快,梯度变化均匀 |
由表2可看出,随着S1的增加,误差梯度下降加快;但同时每次迭代的计算量增加,整体训练时间可能会增加。当S1=8,10~18几乎都能达到目标误差同时误差下降很快。但是,当S1取值过大会造成网络的泛化性能变差。根据误差过程记录、误差平方和以及时间综合考虑,选取S1=11。
在确定较好的S1后,针对不同的lr取值,分别对网络进行重新训练,得到表3的记录。
表 3 不同lr 取值的网络训练记录
lr | 误差平方和 | 训练次数 | 时间(秒) | 误差过程记录 |
---|---|---|---|---|
0.03 | 0.0019 | 10000 | 19.592 | 曲线轻微波动下降 |
0.05 | 0.0031 | 10000 | 20.167 | 曲线下降平滑 |
0.055 | 0.0184 | 10000 | 21.780 | 曲线下降平滑 |
0.06 | 0.001 | 7424 | 15.520 | 曲线快速下降且平滑 |
0.062 | 0.001 | 7080 | 20.119 | 曲线快速下降,有些波动 |
0.065 | 0.0043 | 10000 | 23.912 | 误差直线下降且快速 |
0.08 | 0.0011 | 10000 | 20.851 | 曲线快速下降且平滑 |
0.1 | 0.001 | 8744 | 18.965 | 曲线快速下降,有一定幅度波动 |
0.2 | 0.001 | 5824 | 17.865 | 出现高尖刺和振荡现象 |
0.3 | —— | —— | —— | 直接发散 |
由表3可以看出,学习速率的增加可以使梯度下降步长变大,能够更快到达误差目标;但当学习速率过大时,会出现在极小值周围来回跳跃,即产生振荡现象,甚至发散,难以满足期望目标。综合误差、时间和记录因素考虑,取lr=0.06。
使用自适应学习速率仅需将newff.m中参数换为’traingd’换为’traingda’即可。此时,可以设置初始学习速率为lr=0.03。得到结果为:
t = 8.0160;
SSE = 9.9666e-04;
Epochs = 3564;
与上表中最好结果相比,自适应学习速率训练时间更短,同时也省去了为确定合适的学习速率而进行大量调参训练的时间。但是误差曲线呈锯齿状下降,属于算法本身的缘故。
保持之前所有参数不变,通过不断变换newff的参数使用不同算法进行训练,并与标准梯度下降法进行分析比较,本次进行对比的算法有:附加动量法,自适应学习速率法,BFGS拟牛顿法和Levenberg-Marquardt法,训练结果如下表:
表 4 不同改进算法的网络训练记录
函数 | 算法 | 误差平方和 | 训练次数 | 时间(秒) |
---|---|---|---|---|
traingd | 标准梯度下降法 | 0.0276 | 10000 | 21.8730 |
traingda | 自适应学习速率法 | 0.00099666 | 3564 | 8.0160 |
traingdm | 附加动量法 | 0.0139 | 10000 | 19.442 |
trainbfg | BFGS拟牛顿法 | 0.00097212 | 92 | 0.599 |
trainlm | Levenberg-Marquardt法 | 0.00024548 | 7 | 0.258 |
从表中的记录可以看出,尽管自适应学习速率法已经在原有算法基础上减少了一定的训练时间,但BFGS拟牛顿法和Levenberg-Marquardt法的表现惊人,在本人电脑上几乎眨眼之间便完成了训练,同时误差精度也是几个算法中最高的,尤其是后者,如此的优化实在令人佩服。附加动量法相比于标准的梯度下降法在误差精度和训练时间上也有一定的改善,但跟其他算法相比实在逊色不少。
根据上面的分析确定了神经网络的结构为2-11-1的单层前向神经网络,隐含层采用S型正切激活函数,输出层采用线性激活函数。训练得到的神经网络使用插值法作为测试集输入验证其性能。
训练点数组的取值范围为int[-2, 2],因此,测试点采用每间隔0.5取一个点的方式进行插值,输出规则与之前一致向下取整。
测试集输入为一个2×81的矩阵,期望输出为1×81的行向量(太长了就不写了)。代码如下:
%样本插值进行测试
P1 = zeros();
T1 = zeros();
e1 = -2:0.5:2;
ec1 = -2:0.5:2;
for i=1:9
for j=1:9
r = (i-1)*9+j;
P1(1,r)=e1(i);
P1(2,r)=ec1(j);
T1(r)=floor((e1(i)+ec1(j))/2);
end
end
P1 %查看输入
T1 %查看输出
A = sim(net,P1)
error = perform(net,T1,A) %计算误差
最后计算出的误差为:error = 0.0520。通过观察比对期望和实际输出,发现在计算某些插值点时,实际输出会围绕在x.5周围,导致整体误差偏大。