轻量级C++神经网络应用库CreativeLus:3、复杂函数逼近。案例:多输入混合逼近。
作者:互联网
github资源地址:[Release-x86/x64]
上一篇:轻量级C++神经网络应用库CreativeLus:2、分类问题。案例:空间点在平面上2分类。
下一篇:轻量级C++神经网络应用库CreativeLus:4、CNN卷积神经网络。案例:(MNIST)手写数字识别。
案例3:复杂函数逼近
本章介绍以下几个主要内容,本章内容非常重要:
1、创建自定义结构的神经网络;
2、模型自调整介绍(不是调参);
3、自定义过程监控和输出。
本例问题描述
为表现神经网络的强大逼近能力,我们将【案例1:简单sin函数逼近】的问题升级,通过复杂的多输入多输出逼近问题,了解神经网络运作模式。
命题如下:
定义维度为4的输入向量
X={x1,x2,x3,x4},其中xi∈[−π,π]
xi取定义域内的随机值,通过映射Γ(X),得到维度为4的输出向量Y,即
Y=Γ(X)={y1,y2,y3,y4}
={sin(x4),cos(x3),min(0.1ex2,1),min(1/(1+e(−x1)),1)},其中yi∈[−1,1]。
从[−π,π]的随机输入到[−1,1]范围的交叉输出,形成训练集。构造适当的神经网络,通过训练找到映射关系Γ,并通过随机输入验证模型有效性
映射Γ的真实关系,代码片段如下:
const Float rangA = -ConstPi * 1.0, rangB = ConstPi * 1.0;
Float x4 = 0, x1 = 0, x2 = 0, x3 = 0, y4 = 0, y1 = 0, y2 = 0, y3 = 0;
#define XData3 x1 = rand_f_a_b(rangA,rangB),x2 = rand_f_a_b(rangA,rangB),x3 = rand_f_a_b(rangA,rangB),x4 = rand_f_a_b(rangA,rangB)
#define YData3 y1 = sin(x4),y2 = cos(x3),y3 = min(exp(x2)*0.1,1),y4 = min((1.0/(1.0+exp(-x1))),1)
1、“创造性决定一切”,设计你自己的神经网络
话不多说,我们先看一下,为了本案的问题寻找逼近映射关系,我们“设计”了如下图的网络模型(5层,4输入,4输出模型结构)来解决上述问题。
- 作为处理逼近问题,勉强叫做设计,并把结构搞成这样,显然有点刻意了,但是为了演示,还是尽量做得复杂一点,美观一点。如果你也厌倦了单调的,千篇一律的模型结构。那接下来就是你大展身手的时候了,这一切都将源自于你的“创造力”。
- 图片是由CL自动生成的,可用鼠标左键和中建放大和缩小显示位置。
- 可以观察到,本模型我们用到了4种不同的激活函数:Sigmoid,Tanh,PReLu,LeakyReLu(关于以上4个函数的功能,请自行百度,在此不再论述),其次,每个神经元作为一个节点表示,链接着上一层(左侧的),又被下一层神经元链接(右侧的),自左向右传递。神经元的链接也并不是规律的,而是有选择性的链接。CL是面向对象建模,每个神经元都是一个独立个体,它知道自己是什么药做什么。
- 下图,是带权重渲染的网络结构图:
- 带权重的网络结构局部:
- 如果你认为做一个这样模型要花很多功夫,那你就错了。完成这样一个作品,你只需要“5”行代码即可。构造代码如下,其中大量的注释,介绍使用BpnnStructDef定义网络结构的方法。类BpnnStructDef是CL构建自定义网络的关键媒介。(本案例,完整代码,详见本章附录1)
///////////////////////////////////////////////////////////////////////////////
// 自定义网络:
// 概念说明:CL是对神经元面向对象建模的,每个神经元都应该知道自己是什么,有什么功能,到底链接了谁(类似生物神经元树突),传递到神经元核心的数据如何处理,如何激活传递处理后的数据等等。
// 以下通过显示的定义 组装 BpnnStructDef结构,完成网络结构的自定义(这也是实现自定义网络结构的必要方法,很重要)
// 其中最小定义单元为一个BSB结构:BSB,他描述了在本层内,同一种(即所有属性都完全相同)的神经元的属性即定义,
// BSB结构描述了:本组内该种相同神经元的个数,传递函数类型,链接哪些上层节点(这很重要),对上层链接的节点的传入数据的处理方式(例如:线性相加、取Max、取Min、取均值,这些在卷积网络CNN池化操作中用到),
// 权值及阈值初始化方案,是否采用共享权值(在卷积网络CNN中用到)。
BpnnStructDef mod = {
// 构造第一层(该层直接接纳输入数据的各个维度):
// 以第一个BSB为例,它表达了,该组内有6个相同神经元,传递函数为PRelu,由于没有定义对上层的链接描述,所以本组采用对上层全连接(若有需要也可自行指定链接);
// 而本层内有4个组,每个组的神经元数量各不相同,所以本层的总神经元数量为各组神经元数量相加,即第一层有6+4+6+4=20个神经元,到此本层定义完毕;
{BSB(6,TF_PRelu),BSB(4,TF_LeakyRelu,{}),BSB(6,TF_PRelu),BSB(4,TF_LeakyRelu,{})},
// 构造第二层:总计4组(4个BSB结构),以第1组(BSB)为例:该组共4个神经元,传递函数为Tanh,指定对上一层链接的编号为{1,3,5,7,9}的神经元
// 注意上层链接编号是从1开始计数的,而不是0,特别注意;若没有指定了上层链接编号(类似第一层的情况),将采用对上层全连接方式。
{BSB(4,TF_Tanh,{1,3,5,7,9}),BSB(6,TF_Sigmoid,{2,4,6,8,10}),BSB(4,TF_Tanh,{11,13,15,17,19}),BSB(6,TF_Sigmoid,{12,14,16,18,20})},
// 第三层(根据需求自定义,方法同上,此处只做演示)
{BSB(6,TF_PRelu,{1,2,3,4,5,6}),BSB(8,TF_LeakyRelu,{7,8,9,10,11,12,13,14}),BSB(6,TF_PRelu,{15,16,17,18,19,20})},
// 第四层(根据需求自定义,方法同上,此处只做演示)
{BSB(4,TF_Tanh,{1,3,5,7,9}),BSB(6,TF_Sigmoid,{2,4,6,8,10}),BSB(4,TF_Tanh,{11,13,15,17,19}),BSB(6,TF_Sigmoid,{12,14,16,18,20})},
// 最后一层(输出层,该层必须被定义):由于我们的问题,输出维度必须为4,所以输出层构造的总神经元数量,也必须为4(和BSB个数无关,只和本层神经元总数有关),对上层也采用全连接
{BSB(4,TF_PRelu)},
};// 至此,我们完成一个4输出,5层网络层的神经网络结构定义。后续只需要将该mod选入bp中,通过buildNet即可生成自定义网络。
// 结论:以简单的BSB单元构造网络,通过自由灵活的组合,可实现很多复杂的网络结构,后续将看到类似:标准及非标卷积层,softmax分类器,Inception结构,残差网络ResNet等均可以构造。
///////////////////////////////////////////////////////////////////////////////
2、“知错就改”,模型自调学习率,修复计算
本案代码中,我们不小心把学习率设得太大了。
Float learnRate = 100.1; // 学习率设为100太大,详见附录1:完整代码
Float tagEr = 0.0001, movment = 0.8;
bp.setParam(learnRate, tagEr, movment);
模型训练过程梯度爆炸。但是,没关系,CL自动将过大的学习率调整到合适的初始大小,并且在调整前,弹出如下提示窗,征询用户意见,自行选择是否调整还是退出。若该提示框出现后,用户一直等待,则10秒后,系统自动关闭提示按No继续执行。
- 说明:如果模型训练过程中经过多次调整,仍然检查到数据异常(最多检查15次),则将不会继续调整和重置,而是抛出数据异常。
- 本例输出调整过程,经过监控器输出到控制台,效果如下:
[Waring]: Times= 1'st.The model automatically adjust learning rate ( 100.1 -> 29.0672 ) and retry!
[Waring]: Times= 2'st.The model automatically adjust learning rate ( 29.0672 -> 6.89407 ) and retry!
[Waring]: Times= 3'st.The model automatically adjust learning rate ( 6.89407 -> 1.01794 ) and retry!
[Waring]: Times= 4'st.The model automatically adjust learning rate ( 1.01794 -> 0.115428 ) and retry!
[Waring]: Times= 5'st.The model automatically adjust learning rate ( 0.115428 -> 0.0279845 ) and retry!
3、“过程信息输出”,定义自己的过程监控器
案例1、案例2中我们使用了,一个内置的过程监控器,他提供回调函数指针给需要监控的过程,主要监控buildNet构造网络和train训练网络两个耗时的过程。
//代码片段
Bpnn::CallBackExample bk; //Bpnn::CallBackExample是一个实现了回显功能的监控器
BPNN_CALLBACK_MAKE(vcb2, Bpnn::CallBackExample::print);//BPNN_CALLBACK_MAKE是一个构造监控回调函数指针的宏
bp.buildNet(vcb2, &bk); //监控器传入方法,监控构造过程
bp.train(0, 0, 0, vcb2, &bk);//监控训练过程
- 快速自定义一个监控器,有多种方法。如:构造静态、全局、lambda表达式、或类成员函数等方法均可实现。类Bpnn::CallBackExample即采用了类成员函数作为回调的方案,实现控台输出回显效果。
- 下面演示通过lambda表达式快速构造监控器的方法:
// 方式1:
// 定义静态函数指针Bpnn::CbFunStatic pMonit,通过lambda快速创建一个监视器
Bpnn::CbFunStatic pMonit = [](Int c, Int max, PCStr inf) {
// c表示当前信息编号id,max表示总信息数,inf为信息字符串;
// 当c < 0 时候,代表传入一个提示信息;当c和max都>=0时,表示传入了一个过程信息;
if (c < 0) {
cout << endl << "[Info ]" << inf;
}
else {
if (c == 1) { cout << endl << "[Start]" << inf; }
else if (c == max) { cout << endl << "[Done ]" << inf; }
}
};
bp.buildNet(pMonit);//监控回调指针传入
bp.train(0, 0, 0, pMonit);
- 输出效果:其中带中括号 [ ] 的部分即为监控器输出信息效果。
//案例3:多输入混合逼近
[Start]Net is building hide layer.
[Done ]Net is building hide layer.
[Info ]Net construct completed. Neurons: 84, layers: 5.
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Info ][Waring]: Times= 1'st.The model automatically adjust learning rate ( 100.1 -> 14.6937 ) and retry!
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Info ][Waring]: Times= 2'st.The model automatically adjust learning rate ( 14.6937 -> 3.87114 ) and retry!
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Info ][Waring]: Times= 3'st.The model automatically adjust learning rate ( 3.87114 -> 1.05257 ) and retry!
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Info ][Waring]: Times= 4'st.The model automatically adjust learning rate ( 1.05257 -> 0.115818 ) and retry!
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Info ][Waring]: Times= 5'st.The model automatically adjust learning rate ( 0.115818 -> 0.0183949 ) and retry!
[Start]Net is checking share link range.
[Done ]Net is checking share link range.
[Start]Net is training.
[Done ]Net is training.
[Info ]Net training epoch completed.
Epoch 1:总耗时:27.1134秒,本次:27.1134秒,CorrectRate = 55.07 %, Er = 6.78812e-05
[Start]Net is training.
[Done ]Net is training.
[Info ]Net training epoch completed.
Epoch 2:总耗时:42.0107秒,本次:14.8973秒,CorrectRate = 66.40 %, Er = 9.02233e-05
[Start]Net is training.
[Done ]Net is training.
[Info ]Net training epoch completed.
Epoch 3:总耗时:55.4777秒,本次:13.467秒,CorrectRate = 70.40 %, Er = 7.65581e-05
github资源地址:[Release-x86/x64]
上一篇:轻量级C++神经网络应用库CreativeLus:2、分类问题。案例:空间点在平面上2分类。
下一篇:轻量级C++神经网络应用库CreativeLus:4、CNN卷积神经网络。案例:(MNIST)手写数字识别。
附录:
附1:完整代码
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include "CreativeLus.h"
using namespace cl;
int main() {
printf("\n\n//案例3:多输入混合逼近\n");
string pathTag = ("D:\\Documents\\Desktop\\example_02_multi_approach\\"); //输出目录
const Float rangA = -ConstPi * 1.0, rangB = ConstPi * 1.0;
Float x4 = 0, x1 = 0, x2 = 0, x3 = 0, y4 = 0, y1 = 0, y2 = 0, y3 = 0;
#define XData3 x1 = rand_f_a_b(rangA,rangB),x2 = rand_f_a_b(rangA,rangB),x3 = rand_f_a_b(rangA,rangB),x4 = rand_f_a_b(rangA,rangB)
#define YData3 y1 = sin(x4),y2 = cos(x3),y3 = min(exp(x2)*0.1,1),y4 = min((1.0/(1.0+exp(-x1))),1)
#define LDataIn3 x1,x2,x3,x4
#define LDataOut3 y1,y2,y3,y4
Bpnn bp;
Float learnRate = 100.1;
Float tagEr = 0.0001, movment = 0.8;
if (!bp.readBpnnFormFile((pathTag + "model.txt").c_str())) { //读取模型结果文件,若存在就不再训练了,直接预测(在此仅作演示,非必须)
// 第一步:构造数据集-------------------------------
BpnnSamSets sams;
BpnnSamSets tags;
size_t nPtSi = 3000;
for (size_t i = 0; i < nPtSi; i++) {
XData3; YData3; sams.addSample({ LDataIn3 }, { LDataOut3 });
XData3; YData3; tags.addSample({ LDataIn3 }, { LDataOut3 });
}
sams.writeToFile((pathTag + "sams.txt").c_str());
tags.writeToFile((pathTag + "tags.txt").c_str());
//第二部:构造网络------------------------------
///////////////////////////////////////////////////////////////////////////////
// 自定义网络:
// 概念说明:CL是对神经元面向对象建模的,每个神经元都应该知道自己是什么,有什么功能,到底链接了谁(类似生物神经元树突),传递到神经元核心的数据如何处理,如何激活传递处理后的数据等等。
// 以下通过显示的定义 组装 BpnnStructDef结构,完成网络结构的自定义(这也是实现自定义网络结构的必要方法,很重要)
// 其中最小定义单元为一个BSB结构:BSB他描述了在本层内,同一种(即所有属性都完全相同)的神经元的定义,
// BSB描述了:本组内该种相同神经元的个数,传递函数类型,链接哪些上层节点(这很重要),对上层链接的节点的传入数据的处理方式(例如:线性相加、取Max、取Min、取均值,这些在卷积网络CNN池化操作中用到),
// 权值及阈值初始化方案,是否采用共享权值(在卷积网络CNN中用到)。
BpnnStructDef mod = {
// 构造第一层(该层直接接纳输入数据的各个维度):
// 以第一个BSB为例,它表达了,该组内有6个相同神经元,传递函数为PRelu,由于没有定义对上层的链接描述,所以本组采用对上层全连接(若有需要也可自行指定链接);
// 而本层内有4个组,每个组的神经元数量各不相同,所以本层的总神经元数量为各组神经元数量相加,即第一层有6+4+6+4=20个神经元,到此本层定义完毕;
{BSB(6,TF_PRelu),BSB(4,TF_LeakyRelu,{}),BSB(6,TF_PRelu),BSB(4,TF_LeakyRelu,{})},
// 构造第二层:总计4组(4个BSB结构),以第1组(BSB)为例:该组共4个神经元,传递函数为Tanh,指定对上一层链接的编号为{1,3,5,7,9}的神经元
// 注意上层链接编号是从1开始计数的,而不是0,特别注意;若没有指定了上层链接编号(类似第一层的情况),将采用对上层全连接方式。
{BSB(4,TF_Tanh,{1,3,5,7,9}),BSB(6,TF_Sigmoid,{2,4,6,8,10}),BSB(4,TF_Tanh,{11,13,15,17,19}),BSB(6,TF_Sigmoid,{12,14,16,18,20})},
// 第三层(根据需求自定义,方法同上,此处只做演示)
{BSB(6,TF_PRelu,{1,2,3,4,5,6}),BSB(8,TF_LeakyRelu,{7,8,9,10,11,12,13,14}),BSB(6,TF_PRelu,{15,16,17,18,19,20})},
// 第四层(根据需求自定义,方法同上,此处只做演示)
{BSB(4,TF_Tanh,{1,3,5,7,9}),BSB(6,TF_Sigmoid,{2,4,6,8,10}),BSB(4,TF_Tanh,{11,13,15,17,19}),BSB(6,TF_Sigmoid,{12,14,16,18,20})},
// 最后一层(输出层,该层必须被定义):由于我们的问题,输出维度必须为4,所以输出层构造的总神经元数量,也必须为4(和BSB个数无关,只和本层神经元总数有关),对上层也采用全连接
{BSB(4,TF_PRelu)},
};// 至此,我们完成一个4输出,5层网络层的神经网络结构定义。后续只需要将该mod选入bp中,通过buildNet即可生成自定义网络。
// 结论:以简单的BSB单元构造网络,通过自由灵活的组合,可实现很多复杂的网络结构,后续将看到类似:标准及非标卷积层,softmax分类器,Inception结构,残差网络ResNet等均可以构造。
///////////////////////////////////////////////////////////////////////////////
mod.writeToFile((pathTag + "NnStructDef.txt").c_str());//结构定义可输出,保存为文本文件,可自行打开查看内容
bp.setName("多输入混合逼近");
bp.setSampSets(sams);
bp.setCorrectRateEvaluationModel(0.985, &tags,tags.size() / 4,true);
bp.setStructure(mod); //注意:将结构定义对象在buildNet前选入模型,设置模型构造方案(很重要)
//{{{
bp.setLayer(3, 28); //由于使用了BpnnStructDef自定义网络结构,该方法设置的层和节点数据将会失效并忽略(在此仅作演示,并无实际意义)
bp.setTransFunc(TF_Sigmoid, TF_PRelu); //由于使用了BpnnStructDef自定义网络结构,该方法设置的各层传递函数将会失效并忽略(在此仅作演示,并无实际意义)
//}}}
bp.setSampleBatchCounts(40, true); //设定一批次样本数量,采用随机抽取
bp.setParam(learnRate, tagEr, movment);
bp.setMultiThreadSupport(true); //采用多线程加速。(CPU要求最低为4核)
bp.setMaxTimes(sams.size() * 20);
bp.openGraphFlag(true);
Bpnn::CallBackExample bk;
BPNN_CALLBACK_MAKE(vcb2, Bpnn::CallBackExample::print);//构造监控回调函数指针
bp.buildNet(vcb2, &bk);
bp.exportGraphNetStruct((pathTag + "NnStruct.bmp").c_str());//构造网络后,输出网络结构到bmp图片文件
CLTick tick, tick2, tick3;
bool rt = false; Int epoch = 0;
while (!rt) {
rt = bp.train(0, 0, 0, vcb2, &bk);
printf(("Epoch %d:总耗时:%g秒,本次:%g秒,CorrectRate = %.2f %%, Er = %g \n\n"), ++epoch, tick.getSpendTime(), tick2.getSpendTime(true),
bp.getSavedCorrectRate() * 100.0, bp.getEr());
if (epoch == 1 || rt == true || tick3.getSpendTime() > 20.0) {
bp.showGraphParam(0); //当为0显示全部数据
bp.showGraphNetStruct(true, 800, 1);
tick3.timingStart();
}
};
if (rt) { //将训练完成的模型输出
bp.writeBpnnToFile((pathTag + "model.txt").c_str()); //保存模型
}
bp.exportGraphNetStruct((pathTag + "NnStructDetail.bmp").c_str(), true);
bp.exportGraphEr((pathTag + "NnEr.bmp").c_str()); // 输出保存损失曲线图
bp.exportGraphCorrectRate((pathTag + "NnCr.bmp").c_str()); // 输出保存正确率曲线图
bp.detachExtend();//释放训练扩展占用内存
}
VLF threadBuf;
bp.makeIndependentDataBuf(threadBuf);//构造独立数据区
for (size_t i = 0; i < 5; i++)
{
XData3; YData3; VLF vIn = { LDataIn3 }; VLF vtag = { LDataOut3 };VLF vout;
Float Er = bp.predictWithIndependentData(threadBuf.data(), vIn, &vout, &vtag);
printf(" \n输入值 = { ");
for (size_t i = 0; i < vIn.size(); i++)
printf("%f, ", vIn[i]);
printf(" } \n预测值 = { ");
for (size_t i = 0; i < vout.size(); i++)
printf("%f, ", vout[i]);
printf(" } \n真实值 = { ");
for (size_t i = 0; i < vtag.size(); i++)
printf("%f, ", vtag[i]);
printf(" } \n损失 = %f, %s %g %s\n\n", Er, Er <= tagEr ? "<=":">", tagEr, Er < tagEr ? " 预测正确!" : " 误差过大,预测失败!!!");
}
return system("pause");
}
附2:训练过程输出
//案例3:多输入混合逼近
Net construct completed. Neurons: 84, layers: 5.
[Waring]: Times= 1'st.The model automatically adjust learning rate ( 100.1 -> 29.0672 ) and retry!
[Waring]: Times= 2'st.The model automatically adjust learning rate ( 29.0672 -> 6.89407 ) and retry!
[Waring]: Times= 3'st.The model automatically adjust learning rate ( 6.89407 -> 1.01794 ) and retry!
[Waring]: Times= 4'st.The model automatically adjust learning rate ( 1.01794 -> 0.115428 ) and retry!
[Waring]: Times= 5'st.The model automatically adjust learning rate ( 0.115428 -> 0.0279845 ) and retry!
Net training epoch completed.
Epoch 1:总耗时:16.1289秒,本次:16.1289秒,CorrectRate = 83.60 %, Er = 4.15041e-05
Net training epoch completed.
Epoch 2:总耗时:23.3815秒,本次:7.25264秒,CorrectRate = 94.00 %, Er = 3.22477e-05
Net training epoch completed.
Epoch 3:总耗时:29.8695秒,本次:6.48801秒,CorrectRate = 96.93 %, Er = 2.45293e-05
Net training epoch completed.
Epoch 4:总耗时:36.3577秒,本次:6.4882秒,CorrectRate = 97.73 %, Er = 4.07435e-05
Net training epoch completed with achieve accuracy. CorrectRate(98.53%) >= TagCorrectRate(98.50%)
Epoch 5:总耗时:38.3055秒,本次:1.94778秒,CorrectRate = 98.53 %, Er = 2.6788e-05
输入值 = { 1.435037, 0.907074, 0.424992, -0.815886, }
预测值 = { -0.728387, 0.911280, 0.252407, 0.806843, }
真实值 = { -0.728333, 0.911042, 0.247706, 0.807685, }
损失 = 0.000011, <= 0.0001 预测正确!
输入值 = { 2.043433, -0.558324, 1.185989, -0.567262, }
预测值 = { -0.535745, 0.376521, 0.057957, 0.885794, }
真实值 = { -0.537325, 0.375381, 0.057217, 0.885282, }
损失 = 0.000002, <= 0.0001 预测正确!
输入值 = { -0.058070, 2.371945, -1.143344, 0.415305, }
预测值 = { 0.406086, 0.411661, 0.995162, 0.485440, }
真实值 = { 0.403469, 0.414554, 1.000000, 0.485487, }
损失 = 0.000019, <= 0.0001 预测正确!
输入值 = { 0.931005, -1.908752, -1.386756, -2.818051, }
预测值 = { -0.318844, 0.182217, 0.014959, 0.718451, }
真实值 = { -0.317927, 0.183003, 0.014827, 0.717279, }
损失 = 0.000001, <= 0.0001 预测正确!
输入值 = { 2.984331, -1.796760, -2.753757, -0.121948, }
预测值 = { -0.118123, -0.923810, 0.018038, 0.951676, }
真实值 = { -0.121646, -0.925730, 0.016584, 0.951861, }
损失 = 0.000009, <= 0.0001 预测正确!
请按任意键继续. . .
标签:CreativeLus,自定义,逼近,bp,轻量级,TF,Net,BSB,神经元 来源: https://blog.csdn.net/carlclouder/article/details/104124092