[已解决] bp神经网络结果误差

[复制链接]
斯文中的败类 发表于 2021-2-6 22:15:45
本帖最后由 斯文中的败类 于 2021-2-7 10:21 编辑

按老师要求用bp神经网络确定抽水试验含水层参数。但所求结果与实际相差较大,各路大神能帮忙看下问题出在哪吗?感激不尽
附件: newinput.txt (127 Bytes, 下载次数: 9)

最佳答案


TouAkira 发表于 2021-2-9 07:40:58
在探讨前,需要搞清楚你的目标,虽然有一些注释的提示但我不确定我对代码的理解就符合你对它的预期。

我的理解是:
①有一组已知数据来自t.txt,它们是某模型的输入变量;该模型另有参数U和参数T;模型的输出为形如newinput.txt中的数据。
②目标是根据实际输出newinput.txt中的数据,拟合得到参数U和参数T的数值。
③实现过程则是,先随机生成一系列的参数U和T,并根据该模型计算得到对应的输出;接着构造一个神经网络,使用前述模型计算输出作为训练的输入,使用参数U和T的数据作为训练的输出,开始进行反馈训练;最后使用经过训练的神经网络,完成目标②。

如果我上面的理解与你的思路一致(理解有偏差的话请指出),那么有几块可能有问题,我对BP神经网络不熟,个人意见未必正确,聊以参考。

首先是你的代码中两次使用 dividerand 函数对数据做分类,不合理。每组U与T的数据(矩阵t的每一列),是与输出数据(矩阵p的每一列)依次对应的,应当按照同一方式进行分组。换句话说,假设输入a, b, c, d,...对应输出A, B, C, D,...随机分配了三组进入训练组(不妨设为a→A,b→B,c→C),那么训练时必须确保输入矩阵的排序bac与输出矩阵BAC的对应顺序是一致的,但两次调用dividerand函数会破坏这种一致性,简单地讲就是训练时的输入矩阵排序可能是bac但输出矩阵成了CBA。具体改法是使用另外的索引组[ IndexA, IndexB, IndexC ],对训练的输入输出矩阵都调用索引来确保输入与输出的匹配。

然后是隐含层节点数目的设置。《MATLAB神经网络43个案例分析》这本书里介绍:
BP神经网络的隐含层节点数对BP神经网络预测精度有较大影响:节点数太少,网络不能很好地学习,需要增加训练次数,训练的精度也受影响;节点数太多,训练时间增加,网络容易过拟合。最佳隐含层节点数选择可参考如下公式:
L < N - 1
L < sqrt( M + N ) + a
L = log2( N )
其中N为输入层节点数,L为隐含层节点数,M为输出层节点数,a为0~10之间的常数

按照这个选择的话,我个人认为你设置的[ 1, 27 ]双隐含层节点数目不合适,我用的[ 5, 5 ]


还有一条就是你给定的参数的区间。我用其他函数拟合可以得到T与U的数值,超出了你代码中给定的区间,即,使用你代码中给定的区间做训练,对于待拟合参数而言,可能会产生类似于“将内插算法外推到适用范围之外”的不确定性。

再就是模型参数本身的特性,该模型中如果随着T增大而U减小,则输出变化不明显;而随着T增大而U也增大时,则输出变化很明显(这一点可以通过对输出做等高线图来观察),我个人认为这种“不同参数输入却产生相近的输出”的情形,容易导致神经网络过拟合。

  1. close all; clear all; clc; format long; rng default;
  2. NumberOfTests = 120;
  3. newinput = [ 0.73; 1.28; 1.53; 1.72; 1.96; 2.14; 2.28; 2.39; 2.54; 2.77; 2.99; 3.10; 3.20; 3.26; 3.47; 3.68; 3.77; 3.85 ];
  4. t0 = [ 10; 20; 30; 40; 60; 80; 100; 120; 150; 210; 270; 330; 400; 450; 645; 870; 990; 1185 ];
  5. % 随机生成测试和验证数据
  6. T = 0.10 + 0.04 * sort( rand( 1, NumberOfTests ) );
  7. U = 0.0002 + 0.0004 * sort( rand( 1, NumberOfTests ) ); % 对比 U = 0.0002 + 0.0004 * sort( rand( 1, NumberOfTests ) ,'descend' );

  8. %根据公式计算求得输入数据
  9. p = ( -0.577216 - log( 43^2 * repmat( U, [ numel( t0 ), 1 ] ) ./ ( 4 * repmat( T, [ numel( t0 ), 1 ] ) .* repmat( t0, [ 1, numel( T ) ] ) ) ) + ...
  10.     43^2 * repmat( U, [ numel( t0 ), 1 ] ) ./ ( 4 * repmat( T, [ numel( t0 ), 1 ] ) .* repmat( t0, [ 1, numel( T ) ] ) ) ) ./ ( 4 * pi * repmat( T, [ numel( t0 ), 1 ] ) );
  11. t  = [ T; U ];

  12. [ IndexA, IndexB, IndexC ] = dividerand( [ 1 : 1 : NumberOfTests ], 0.7, 0.15, 0.15 );
  13. trainsample.p = p( :, IndexA );
  14. trainsample.t = t( :, IndexA );
  15. valsample.p = p( :, IndexB );
  16. testsample.p = p( :, IndexC );
  17. valsample.t = t( :, IndexB );
  18. testsample.t = t( :, IndexC );

  19. % 数据归一化处理
  20. [ trainsample.P, ps ] = mapminmax( trainsample.p, 0,1 );
  21. [ trainsample.T, ts ] = mapminmax( trainsample.t, 0,1 );

  22. % 初始化网络
  23. net = newff( trainsample.P, trainsample.T, [ 5, 5 ] );

  24. % 设置网络参数
  25. net.trainFcn = 'trainbr';
  26. net.trainParam.epochs = 700;
  27. net.trainParam.show = 50;
  28. net.trainParam.goal = 1E-15;

  29. [ net, tr ] = train( net, trainsample.P, trainsample.T );
  30. Coef_fitnlm = [ 0.120139987855504, 0.000478526125329326 ]; % mdl.Coefficients.Estimate.';
  31. Coef_BP = mapminmax( 'reverse', sim( net, mapminmax( 'apply', newinput, ps ) ), ts ).';
  32. fprintf( '使用函数fitnlm估计得到的参数值为 T = %.8g\tU = %.8g\n', Coef_fitnlm );
  33. fprintf( '使用BP神经网络估计得到的参数值为: T = %.8g\tU = %.8g\n', Coef_BP );
  34. fprintf( '相对误差T = %.8g %%\t相对误差U = %.8g %%\n', 100 * abs( ( Coef_fitnlm - Coef_BP ) ./ Coef_fitnlm ) );
复制代码


使用函数fitnlm估计得到的参数值为 T = 0.12013999        U = 0.00047852613
使用BP神经网络估计得到的参数值为: T = 0.12376677        U = 0.0004899632
相对误差T = 3.0187981 %        相对误差U = 2.3900621 %
回复此楼

2 条回复


TouAkira 发表于 2021-2-9 07:40:58
在探讨前,需要搞清楚你的目标,虽然有一些注释的提示但我不确定我对代码的理解就符合你对它的预期。

我的理解是:
①有一组已知数据来自t.txt,它们是某模型的输入变量;该模型另有参数U和参数T;模型的输出为形如newinput.txt中的数据。
②目标是根据实际输出newinput.txt中的数据,拟合得到参数U和参数T的数值。
③实现过程则是,先随机生成一系列的参数U和T,并根据该模型计算得到对应的输出;接着构造一个神经网络,使用前述模型计算输出作为训练的输入,使用参数U和T的数据作为训练的输出,开始进行反馈训练;最后使用经过训练的神经网络,完成目标②。

如果我上面的理解与你的思路一致(理解有偏差的话请指出),那么有几块可能有问题,我对BP神经网络不熟,个人意见未必正确,聊以参考。

首先是你的代码中两次使用 dividerand 函数对数据做分类,不合理。每组U与T的数据(矩阵t的每一列),是与输出数据(矩阵p的每一列)依次对应的,应当按照同一方式进行分组。换句话说,假设输入a, b, c, d,...对应输出A, B, C, D,...随机分配了三组进入训练组(不妨设为a→A,b→B,c→C),那么训练时必须确保输入矩阵的排序bac与输出矩阵BAC的对应顺序是一致的,但两次调用dividerand函数会破坏这种一致性,简单地讲就是训练时的输入矩阵排序可能是bac但输出矩阵成了CBA。具体改法是使用另外的索引组[ IndexA, IndexB, IndexC ],对训练的输入输出矩阵都调用索引来确保输入与输出的匹配。

然后是隐含层节点数目的设置。《MATLAB神经网络43个案例分析》这本书里介绍:
BP神经网络的隐含层节点数对BP神经网络预测精度有较大影响:节点数太少,网络不能很好地学习,需要增加训练次数,训练的精度也受影响;节点数太多,训练时间增加,网络容易过拟合。最佳隐含层节点数选择可参考如下公式:
L < N - 1
L < sqrt( M + N ) + a
L = log2( N )
其中N为输入层节点数,L为隐含层节点数,M为输出层节点数,a为0~10之间的常数

按照这个选择的话,我个人认为你设置的[ 1, 27 ]双隐含层节点数目不合适,我用的[ 5, 5 ]


还有一条就是你给定的参数的区间。我用其他函数拟合可以得到T与U的数值,超出了你代码中给定的区间,即,使用你代码中给定的区间做训练,对于待拟合参数而言,可能会产生类似于“将内插算法外推到适用范围之外”的不确定性。

再就是模型参数本身的特性,该模型中如果随着T增大而U减小,则输出变化不明显;而随着T增大而U也增大时,则输出变化很明显(这一点可以通过对输出做等高线图来观察),我个人认为这种“不同参数输入却产生相近的输出”的情形,容易导致神经网络过拟合。

  1. close all; clear all; clc; format long; rng default;
  2. NumberOfTests = 120;
  3. newinput = [ 0.73; 1.28; 1.53; 1.72; 1.96; 2.14; 2.28; 2.39; 2.54; 2.77; 2.99; 3.10; 3.20; 3.26; 3.47; 3.68; 3.77; 3.85 ];
  4. t0 = [ 10; 20; 30; 40; 60; 80; 100; 120; 150; 210; 270; 330; 400; 450; 645; 870; 990; 1185 ];
  5. % 随机生成测试和验证数据
  6. T = 0.10 + 0.04 * sort( rand( 1, NumberOfTests ) );
  7. U = 0.0002 + 0.0004 * sort( rand( 1, NumberOfTests ) ); % 对比 U = 0.0002 + 0.0004 * sort( rand( 1, NumberOfTests ) ,'descend' );

  8. %根据公式计算求得输入数据
  9. p = ( -0.577216 - log( 43^2 * repmat( U, [ numel( t0 ), 1 ] ) ./ ( 4 * repmat( T, [ numel( t0 ), 1 ] ) .* repmat( t0, [ 1, numel( T ) ] ) ) ) + ...
  10.     43^2 * repmat( U, [ numel( t0 ), 1 ] ) ./ ( 4 * repmat( T, [ numel( t0 ), 1 ] ) .* repmat( t0, [ 1, numel( T ) ] ) ) ) ./ ( 4 * pi * repmat( T, [ numel( t0 ), 1 ] ) );
  11. t  = [ T; U ];

  12. [ IndexA, IndexB, IndexC ] = dividerand( [ 1 : 1 : NumberOfTests ], 0.7, 0.15, 0.15 );
  13. trainsample.p = p( :, IndexA );
  14. trainsample.t = t( :, IndexA );
  15. valsample.p = p( :, IndexB );
  16. testsample.p = p( :, IndexC );
  17. valsample.t = t( :, IndexB );
  18. testsample.t = t( :, IndexC );

  19. % 数据归一化处理
  20. [ trainsample.P, ps ] = mapminmax( trainsample.p, 0,1 );
  21. [ trainsample.T, ts ] = mapminmax( trainsample.t, 0,1 );

  22. % 初始化网络
  23. net = newff( trainsample.P, trainsample.T, [ 5, 5 ] );

  24. % 设置网络参数
  25. net.trainFcn = 'trainbr';
  26. net.trainParam.epochs = 700;
  27. net.trainParam.show = 50;
  28. net.trainParam.goal = 1E-15;

  29. [ net, tr ] = train( net, trainsample.P, trainsample.T );
  30. Coef_fitnlm = [ 0.120139987855504, 0.000478526125329326 ]; % mdl.Coefficients.Estimate.';
  31. Coef_BP = mapminmax( 'reverse', sim( net, mapminmax( 'apply', newinput, ps ) ), ts ).';
  32. fprintf( '使用函数fitnlm估计得到的参数值为 T = %.8g\tU = %.8g\n', Coef_fitnlm );
  33. fprintf( '使用BP神经网络估计得到的参数值为: T = %.8g\tU = %.8g\n', Coef_BP );
  34. fprintf( '相对误差T = %.8g %%\t相对误差U = %.8g %%\n', 100 * abs( ( Coef_fitnlm - Coef_BP ) ./ Coef_fitnlm ) );
复制代码


使用函数fitnlm估计得到的参数值为 T = 0.12013999        U = 0.00047852613
使用BP神经网络估计得到的参数值为: T = 0.12376677        U = 0.0004899632
相对误差T = 3.0187981 %        相对误差U = 2.3900621 %
回复此楼

斯文中的败类 发表于 2021-2-9 14:31:43
TouAkira 发表于 2021-2-9 07:40
在探讨前,需要搞清楚你的目标,虽然有一些注释的提示但我不确定我对代码的理解就符合你对它的预期。

我的 ...

非常感谢版主这么认真的回答,我也在其他很多网站提出我的疑惑,没有一个人回答。:(

  显然版主对于代码的理解是完全正确的。其次:①:我用dividerand函数在matlab上进行了验证,发现之前计算的数据没有完全的一一对应 √
②:隐含层节点数我也在其它论文中有看过公式,也 √  ③:给定参数的区间,我是根据“基于非稳定流抽水试验的BP神经网络确定含水层参数研究”这篇论文确定的,这点的话我也不是很明确。

最后 由于我对Matlab还没那么熟悉,对于代码还需要花些时间去理解。就先用版主的代码进行了验证,答案符合要求预期。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

相关帖子
热门教程
站长推荐
快速回复 返回顶部 返回列表