轻量级C++神经网络应用库CreativeLus:4、CNN卷积神经网络。案例:(MNIST)手写数字识别。


github资源地址:[Release-x86/x64]

上一篇:轻量级C++神经网络应用库CreativeLus:3、复杂函数逼近。案例:多输入混合逼近。
下一篇:轻量级C++神经网络应用库CreativeLus:5、ResNet残差网咯。案例:(cifar-100)图片分类。


案例4:CNN网络,实现(MNIST)手写数字识别

本案例的任务

经过前面几章的介绍,对CL基本功能应有所了解。CL致力于通过简单的代码,易懂的逻辑,通用化的功能达到快速应用的目的。之前的几个案例,演示功能性居多,实用性不强,但今天,我们就用CL来重现一个经典的CNN案例,用来解决实际应用问题:单色手写数字图片的识别。

  • 本章将通过简单代码构建一个经典CNN网络LeNet-5,并介绍以下核心功能:
  • 方法一:通过结构定义对象的API,构建卷积神经网络的方法;
  • 方法二:通过脚本定义对象,构建卷积神经网络方法;

卷积网络LeNet-5介绍

关于什么是卷积神经网络?相关基础知识,在此不再论述,CSDN百度都有很多的介绍。今天我们通过构建经典的cnn网络LeNet-5来实现Mnist数据集的训练和手写数字图片的识别。特别说明:本章代码中,对原LeNet-5逻辑有一些小修改,取消了原C3、C5层的链接标记。经测试,该修改对模型的训练及识别能力并无实际影响,若一定要使用,可采用在C3、C5层设置Dropout方案替代。

  • 为了礼貌起见,放两张图,供小伙伴们回忆一下cnn网络和LeNet-5。(图片源于网络)

在这里插入图片描述
在这里插入图片描述

准备工作

1、Mnist数据集的获取和预处理

首先我们下载好本例所用手写数据训练数据集和测试数据集[Mnist:http://yann.lecun.com/exdb/mnist/],他的官网是这样介绍它的:

  • MNIST手写数字数据库(可从本页获得)有60000个示例的训练集和10000个示例的测试集。它是NIST提供的更大集合的子集。数字已被规格化,并集中在一个固定大小的图像中。
  • 它是一个很好的数据库,供那些想尝试在实际数据上学习技术和模式识别方法,同时在预处理和格式化方面花费最少精力的人使用。

在这里插入图片描述

之后使用代码做一下预处理,生成CL样本对集合对象,代码很简单,如下:

#include "CreativeLus.h"
#include "CreativeLusExTools.h"

	string pathdatasrc = ("D:\\Documents\\Desktop\\nndata\\mnist\\");
	
	// 第一步: 由Mnist数据预处理,生成模型训练集和预测集---------------------------------------------
	BpnnSamSets trainSets, testSets;
	if (!trainSets.readFromFile((pathTag + "cnnTrainData.txt").c_str())) {
		StdDataHelper::readDataOfMnist(
			(pathdatasrc + "train-images.idx3-ubyte").c_str(),
			(pathdatasrc + "train-labels.idx1-ubyte").c_str(),
			trainSets, 
			-1, //表示训练输入数据的值区间最小为-1
			1,  //表示训练输入数据的值区间最大为-1
			-0.8, //表示训练输入数据对应的分类标签的值区间最小为-0.8
			0.8, //表示训练输入数据对应的分类标签的值区间最大为0.8
			2 //对样本输入数据做padding=2的数据扩充,使得28x28的原始数据变为32x32
		);
		trainSets.writeToFile((pathTag + "cnnTrainData.txt").c_str());
	}
	if (!testSets.readFromFile((pathTag + "cnnTestData.txt").c_str())) {
		StdDataHelper::readDataOfMnist(
			(pathdatasrc + "t10k-images.idx3-ubyte").c_str(),
			(pathdatasrc + "t10k-labels.idx1-ubyte").c_str(),
			testSets, -1, 1, -0.8, 0.8, 2
		);
		testSets.writeToFile((pathTag + "cnnTestData.txt").c_str());
	}
  • 其中静态StdDataHelper::readDataOfMnist()方法,是包含在头文件"CreativeLusExTools.h"中,实现快速处理Mnist原始数据,并转换成BpnnSamSets数据集的函数,源代码也可根据需要自行调整。
  • 关于LeNet-5网络实现,可参考【卷积神经网络(CNN)的简单实现(MNIST)】,不过要做好思想准备,这是一种纯C基于过程的实现方案,很难修改,很难扩充,代码灵活度不大。光是要构造出网络,代码就麻烦到让人吐血,算法过程代码更是不好理解,对于初学者很不友好。
2、卷积网络组装

基于以上前车之鉴,CL应该有简单明了的建模手段,因此,“8”行代码构建CNN网络LeNet-5的方式就诞生了:(关于 BpnnStructScript 对象使用,详见完整测试代码部分)

	//代码片段
	BpnnStructScript scp =  //构造生成脚本
		{
			{{0,SCP_Conv,{5,5,6},wi[0],bi,transfunc},},  //标准卷积层
			{{0,SCP_Pool,{2,2},{}, {},-1,WC_Average},},  //池化层,均值池化
			{{0,SCP_ConvSep,{1,1},wi[2],bi, transfunc},},//分割卷积层
			{{0,SCP_Conv,{5,5,16},wi[3],bi,transfunc},}, //标准卷积层
			{{0,SCP_Pool,{2,2},{}, {},-1,WC_Average},},  //池化层,均值池化
			{{0,SCP_ConvSep,{1,1},wi[5],bi,transfunc},}, //分割卷积层
			{{0,SCP_Conv,{5,5,120},wi[6],bi,transfunc},},//标准卷积层,本层链接了map=1x1的分割卷积层,即等价于一个全连接层
			{{0,SCP_Fc,{10},wi[7],bi, transfunc},},   //10个输出神经元的全连接层
		};

关于如何做到的?CL是基于对象建模的,按[案例3]介绍的结构定义方式,可以构建任意的自定义的网络,加上些许封装,即可实现通过快速的脚本对象定义,生成复杂的卷积网络(后续还有更复杂的VGG,ResNet等网络,原理均同),这在后续技术附录中介绍。

3、手写数字图片准备
  • 接下来准备10张32x32尺寸的手写数字图片,从0到9,均采用单色bitmap格式(windows位图格式)。
  • 处理图片,采用头文件"CreativeLusExTools.h"中定义的CLBmpHelper::readbmp()静态方法即可(不需要用OpenCV库来处理这么麻烦了)。关于CLBmpHelper::readbmp()可自行查看源码,按需修改。

在这里插入图片描述

  • 读取的数据时记得做一下必要的值域转换:
	//代码片段
	CLBmpHelper::readbmp(str.c_str(), bmpData);
	data.resize(bmpData.size());
	for (size_t j = 0, sj = bmpData.size(); j < sj; j++){
		//将[0,255]的之间的byte值映射转换到[-1,1]的范围内,至于为什么,请自行思考。
		data[j] = bmpData[j] / 255.0 * (1 - (-1)) + (-1); 
	}

完整测试代码

#include <stdio.h>
#include <string>
#include <vector>
#include <map>

#include "CreativeLus.h"
#include "CreativeLusExTools.h"

using namespace cl;

int main() {
	printf("\n\n//案例4:cnn卷积神经网络,实现(Mnist)手写数字识别\n");

	string pathTag = ("D:\\Documents\\Desktop\\example_04_cnn_mnist\\");
	string pathdatasrc = ("D:\\Documents\\Desktop\\nndata\\mnist\\");  //原始Mnist数据目录
	string pathdatabmp = ("D:\\Documents\\Desktop\\nndata\\bmp32x32_0_9\\"); //手写数字图片文件目录

	// 第一步: 由Mnist数据预处理,生成模型训练集和预测集---------------------------------------------
	BpnnSamSets trainSets, testSets;
	if (!trainSets.readFromFile((pathTag + "cnnTrainData.txt").c_str())) {
		StdDataHelper::readDataOfMnist(
			(pathdatasrc + "train-images.idx3-ubyte").c_str(),
			(pathdatasrc + "train-labels.idx1-ubyte").c_str(),
			trainSets, -1, 1, -0.8, 0.8, 2
		);
		trainSets.writeToFile((pathTag + "cnnTrainData.txt").c_str());
	}
	if (!testSets.readFromFile((pathTag + "cnnTestData.txt").c_str())) {
		StdDataHelper::readDataOfMnist(
			(pathdatasrc + "t10k-images.idx3-ubyte").c_str(),
			(pathdatasrc + "t10k-labels.idx1-ubyte").c_str(),
			testSets, -1, 1, -0.8, 0.8, 2
		);
		testSets.writeToFile((pathTag + "cnnTestData.txt").c_str());
	}
	
	Bpnn bp;
	if (bp.readBpnnFormFile((pathTag + "cnn.txt").c_str()) == false) {  //读取已有文件,用于预测,没有就从头开始
		
		// 第二步:自定义卷积网络结构------------------------------
		EBP_TF transfunc = TF_Tanh; //激活函数选Tanh
		BpnnStructDef mod;
#define UseScript 1
#if UseScript < 1
		// 方式一:BpnnStructDef内置API方式,生成卷积网络定义,该方法相比于脚本法,会麻烦些,不过脚本构造法也是封装了BpnnStructDef对象方法实现的。
		UINT dp = 1, wt = 32, ht = 32;
		mod.addOneConvolution(0, 0, wt, ht, { 1,5,5,6 },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (25.0 + 150.0))),Float(sqrt(6.0 / (25.0 + 150.0))) },
			{ IT_Const,0 }, transfunc);
		mod.addOnePooling(1, 0, wt, ht, { 6,2,2 }, WC_Average);
		mod.addOneConvolutionSeparable(2, 0, wt, ht, { 6,1,1 },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (4.0 + 1.0))),Float(sqrt(6.0 / (4.0 + 1.0))) },
			{ IT_Const,0 }, transfunc);
		mod.addOneConvolution(3, 0, wt, ht, { 6,5,5,16 },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (150.0 + 400.0))),Float(sqrt(6.0 / (150.0 + 400.0))) },
			{ IT_Const,0 }, transfunc);
		mod.addOnePooling(4, 0, wt, ht, { 16,2,2 }, WC_Average);
		mod.addOneConvolutionSeparable(5, 0, wt, ht, { 16,1,1 },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (4.0 + 1.0))),Float(sqrt(6.0 / (4.0 + 1.0))) },
			{ IT_Const,0 }, transfunc);
		mod.addOneConvolution(6, 0, wt, ht, { 16,5,5,120 },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (400.0 + 3000.0))),Float(sqrt(6.0 / (400.0 + 3000.0))) },
			{ IT_Const,0 }, transfunc);
		mod.addOneFullConnect(7, 0, wt * ht * 120, 10,
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (120.0 + 10.0))),Float(sqrt(6.0 / (120.0 + 10.0))) },
			{ IT_Const,0 }, transfunc);
#else
		// 方式二:用脚本BpnnStructScript,定义卷积网络
		vector<WiInitDef> wi = {  //权值初始化设置定义
		     //WiInitDef表示描述了权值将如何被初始化:
		     //IT_Uniform表示采用 均值随机分布初始化,
		     //均值分布范围:[-1.0 * sqrt(6.0 / (25.0 + 150.0)) , sqrt(6.0 / (25.0 + 150.0))] 之间。
		     //至于为什么这样设,其中知识涉及到模型调参,是个复杂的过程,在此不做论述。但调参却很重要,
		     //大部分模型训练效果不好都是初值设定不合理造成的。可自行尝试调整为{IT_Uniform,-1.0,1.0}试试效果。
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (25.0 + 150.0))),Float(sqrt(6.0 / (25.0 + 150.0))) },
			{},
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (4.0 + 1.0))),Float(sqrt(6.0 / (4.0 + 1.0))) },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (150.0 + 400.0))),Float(sqrt(6.0 / (150.0 + 400.0))) },
			{},
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (4.0 + 1.0))),Float(sqrt(6.0 / (4.0 + 1.0))) },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (400.0 + 3000.0))),Float(sqrt(6.0 / (400.0 + 3000.0))) },
			{ IT_Uniform,Float(-1.0 * sqrt(6.0 / (120.0 + 10.0))),Float(sqrt(6.0 / (120.0 + 10.0))) },
		};
		BiInitDef bi = { IT_Const,0 };  //阈值初始化设置定义:IT_Const表示以常量0初始化
		InputFilterMap ifm = { //脚本输入map信息描述
			0,  //表示输入数据,对齐的标号。此处,表示从输入样本对的输入向量0位置处开始读取数据
			1,  //输入图片的数据通道为1
			32, //输入图片的数据宽度为32
			32  //输入图片的数据高度为32
		}; 
		BpnnStructScript scp =  //构造生成脚本
		{
			//第一层:第0分组:标准卷积层,卷积核输入通道数由InputFilterMap决定(本例为1),
			//感受野5x5,6个卷积核,移动step=1,用wi[0]描述的初始化方案进行对应的卷积核的权值做初始化,
			//用bi描述方案初始化各个卷积核阈值,该层激活函数选Tanh。(该层效果等价于LeNet-5网络的C1层)
			{{0,SCP_Conv,{5,5,6},wi[0],bi,transfunc},}, 
			
			//第二层:第0分组:池化层,感受野2x2,移动step=2,无权置初始化,无阈值初始化,传递函数为-1
			//表示默认(池化层永远是以PureLin线性函数作为激活函数的,并且权值固定为1),
			//权值链接处理方式取Average即均值模式
			{{0,SCP_Pool,{2,2},{}, {},-1,WC_Average},},
			
			//第三层:第0分组:单通道卷积层,感受野1x1,移动step=1,wi[2]权置初始化,bi阈值初始化,
			//传递函数Tanh。(单通道卷积层,即输入通道永远为1,逐层并排的卷积核扫描的方式。)
			//说明:第二层和第三层组合起来,效果等价于LeNet-5网络的S2层
			{{0,SCP_ConvSep,{1,1},wi[2],bi, transfunc},},
			
			//第四层:第0分组:标准卷积层,卷积核感受野5x5,共16个卷积核(卷积核的输入通道数由上层
			//输出map数自动确定无需人为干预指定),扫描移动step=1。(该层效果等价于LeNet-5网络的C3层)
			{{0,SCP_Conv,{5,5,16},wi[3],bi,transfunc},},
			
			//第五层:第0分组:均值池化层,意义同上
			{{0,SCP_Pool,{2,2},{}, {},-1,WC_Average},},
			
			//第六层:第0分组:单通道卷积层,意义同上
			//说明:第五层和第六层组合起来,效果等价于LeNet-5网络的S4层
			{{0,SCP_ConvSep,{1,1},wi[5],bi,transfunc},},
			
			//第七层:第0分组:标准卷积层,意义同上。(该层效果等价于LeNet-5网络的C5层)
			{{0,SCP_Conv,{5,5,120},wi[6],bi,transfunc},},
			
			//第八层:第0分组:全连接层,输出维度10(即10个神经元),wi[7]权值初始化,bi阈值初始化,
			//激活函数Tanh。(该层效果等价于LeNet-5网络的output层)
			{{0,SCP_Fc,{10},wi[7],b。i, transfunc},},
		};
		mod.addScript( //很重要
			scp, //将构造好的脚本,选入结构定义对象中
			ifm, //放在脚本描述前面的输入层的描述
			true //脚本构造过程中,数据输出显示到控制台
		); 
#endif
		mod.writeToFile((pathTag + "cnnStructDef.txt").c_str()); //输出保存结构定义

		//将结构定义对象选入bp模型中
		bp.setStructure(mod);
		//设置必须要的训练和预测数据集		
		bp.setSampSets(trainSets);
		bp.setCorrectRateEvaluationModel(
			0.985, //正确率目标设为98.5%
			&testSets,
			0,
			false,
			CRT_MaxValuePosMatch //采用最大值出现的位置评价标准。(技术原因,详见,[案例2]的附录2)
		);		
		Bpnn::CallBackExample monit;
		BPNN_CALLBACK_MAKE(pMonit, Bpnn::CallBackExample::print);
		//生成网络模型(必须的)
		bp.buildNet(pMonit, &monit);

		// 第三步:开始训练网络---------------------------------------------
		bp.setParam(1.01, 0.0, 0.8); //学习率1.01(此处有意为之,让模型演示自调整学习率的过程)
		bp.openGraphFlag(true);
		bp.setMultiThreadSupport(true); //打开多线程
		bp.setMaxTimes(trainSets.size());
		CLTick tick, tick2;
		bool rt = false; Int epoch = 0;
		while (!rt) {
			rt = bp.train(0, 0, 0, pMonit, &monit);
			printf(("Epoch %d:总耗时:%g秒,本次:%g秒,正确率 = %.2f %%, Er = %g \n\n"), ++epoch, tick.getSpendTime(), tick2.getSpendTime(true),
				bp.getSavedCorrectRate() * 100.0, bp.getEr());
			bp.showGraphParam();
			//bp.showGraphNetStruct(true, 800, 1); //注意:由于卷积网络的神经元很多,网络结构图是无法显示的
		};
		if (rt) {
			//将训练完成的模型输出保存
			bp.writeBpnnToFile((pathTag + "cnn.txt").c_str());
		}
		bp.exportGraphEr((pathTag + "cnnEr.bmp").c_str());
		bp.exportGraphCorrectRate((pathTag + "cnnCr.bmp").c_str());
		bp.detachExtend();
	}

	// 第四步:手写图片带入模型做识别测试---------------------------------------------
	std::vector<int> target{ 0,1,2,3,4,5,6,7,8,9 };
	VLB bmpData;
	VLF data; VLF neuron_output;
	for (auto& i : target) {
		std::string str = pathdatabmp + std::to_string(i) + ".bmp";	//图片路径	
		CLBmpHelper::readbmp(str.c_str(), bmpData); //读取bmp图片数据
		data.resize(bmpData.size()); 
		for (size_t j = 0, sj = bmpData.size(); j < sj; j++)
			data[j] = bmpData[j] / 255.0 * (1 - (-1)) + (-1); //数据从[0,255]向[-1,1]转换
			
		// 模型预测(此处演示采用的是内置数据区做为预测缓存,区别于[案例2]和[案例3]的独立数据区)
		bp.predict(data, &neuron_output); /
		int ret = -1;
		Float _max;

#define listMaxI( var , lst , lstSize ,index ) \
	(var) = (lst)[(index) = 0];\
	for (size_t _ise231=1,_si = (lstSize);_ise231 < _si;_ise231++)\
	if( (var) < (lst)[_ise231]) (var) = (lst)[(index) = _ise231];
#define vectorMaxI( var , lst ,index ) listMaxI(var,lst,(lst).size(),index)

		//找到预测结果向量,最大值出现的位置,与数据分类索引值位置对比,若匹配说明预测分类正确
		vectorMaxI(_max, neuron_output, ret);
		fprintf(stdout, "\n模型预测数字是:  %d ,   正确数字是:  %d  . %s \noutput =[ ", ret, i, ret != i ? "    <<<<<<<预测错误!!!>>>>>>" : "   预测正确");
		Float somaxall = 0;
		for (Uint i = 0; i < bp.outputDimension(); i++)
		{
			cout << std::setprecision(2) << neuron_output[i] << ", ";
			somaxall += exp(neuron_output[i] );
		}
		cout << " ]\nSoftmax=[ ";
		for (Uint i = 0; i < bp.outputDimension(); i++)
		{
			cout << exp(neuron_output[i] ) / somaxall * 100.0 << "%, ";
		}
		cout << " ]\n\n";
	}
	return system("pause");
}

结果输出


//案例4:cnn卷积神经网络,实现(Mnist)手写数字识别

//脚本输出
BpnnStructDef create by script: input= [ 0 , 1 x 32 x 32 ]
Layer= 0:
       -> set= 0: upLink= 0, range= [ 0 - 4703 ], size= (4704), map= ( 28 x 28 x 6 ), SCP_Conv
Layer= 1:
       -> set= 0: upLink= 0, range= [ 0 - 1175 ], size= (1176), map= ( 14 x 14 x 6 ), SCP_Pool
Layer= 2:
       -> set= 0: upLink= 0, range= [ 0 - 1175 ], size= (1176), map= ( 14 x 14 x 6 ), SCP_ConvSep
Layer= 3:
       -> set= 0: upLink= 0, range= [ 0 - 1599 ], size= (1600), map= ( 10 x 10 x 16 ), SCP_Conv
Layer= 4:
       -> set= 0: upLink= 0, range= [ 0 - 399 ], size= (400), map= ( 5 x 5 x 16 ), SCP_Pool
Layer= 5:
       -> set= 0: upLink= 0, range= [ 0 - 399 ], size= (400), map= ( 5 x 5 x 16 ), SCP_ConvSep
Layer= 6:
       -> set= 0: upLink= 0, range= [ 0 - 119 ], size= (120), map= ( 1 x 1 x 120 ), SCP_Conv
Layer= 7:
       -> set= 0: upLink= 0, range= [ 0 - 9 ], size= (10), map= ( 1 x 1 x 10 ), SCP_Fc
Script end.
Net construct completed. Neurons: 9586, layers: 8.

//自调节输出
[Waring]: Times= 1'st.The model automatically adjust learning rate ( 1.01 -> 0.233056 ) and retry!
[Waring]: Times= 2'st.The model automatically adjust learning rate ( 0.233056 -> 0.0572928 ) and retry!
[Waring]: Times= 3'st.The model automatically adjust learning rate ( 0.0572928 -> 0.00897293 ) and retry!

//训练过程
Net training epoch completed.
Epoch 1:总耗时:92.6971秒,本次:92.6971秒,正确率 = 97.19 %, Er = 0.0198421

Net training epoch completed.
Epoch 2:总耗时:201.192秒,本次:108.495秒,正确率 = 97.95 %, Er = 0.0201395

Net training epoch completed.
Epoch 3:总耗时:289.006秒,本次:87.8145秒,正确率 = 98.35 %, Er = 0.023344

Net training epoch completed.
Epoch 4:总耗时:431.617秒,本次:142.611秒,正确率 = 98.44 %, Er = 0.0194745

Net training epoch completed with achieve accuracy. CorrectRate(98.53%) >= TagCorrectRate(98.50%)
Epoch 5:总耗时:685.648秒,本次:254.031秒,正确率 = 98.53 %, Er = 0.0151698

//模型预测
模型预测数字是:  0 ,   正确数字是:  0  .    预测正确
output =[ -0.22, -0.79, -0.69, -0.72, -0.77, -0.84, -0.44, -0.86, -0.74, -0.84,  ]
Softmax=[ 16%, 8.9%, 9.8%, 9.5%, 9%, 8.4%, 13%, 8.3%, 9.3%, 8.4%,  ]


模型预测数字是:  1 ,   正确数字是:  1  .    预测正确
output =[ -0.58, 0.59, -0.93, -0.87, -0.88, -0.54, -0.74, -0.67, -0.94, -0.79,  ]
Softmax=[ 9.3%, 30%, 6.6%, 7%, 6.9%, 9.7%, 8%, 8.5%, 6.5%, 7.6%,  ]


模型预测数字是:  2 ,   正确数字是:  2  .    预测正确
output =[ -0.7, -0.48, 0.21, -0.5, -0.96, -0.9, -0.89, -0.78, -0.61, -0.48,  ]
Softmax=[ 8.6%, 11%, 21%, 11%, 6.6%, 7.1%, 7.1%, 8%, 9.4%, 11%,  ]


模型预测数字是:  3 ,   正确数字是:  3  .    预测正确
output =[ -0.83, -0.83, -0.88, 0.86, -0.82, -0.83, -0.88, -0.81, -0.86, -0.88,  ]
Softmax=[ 7%, 7%, 6.7%, 38%, 7%, 7%, 6.7%, 7.1%, 6.8%, 6.7%,  ]


模型预测数字是:  4 ,   正确数字是:  4  .    预测正确
output =[ -0.88, -0.79, -0.81, -0.91, 0.63, -0.75, -0.65, -0.7, -0.77, -0.87,  ]
Softmax=[ 6.9%, 7.6%, 7.5%, 6.7%, 32%, 7.9%, 8.8%, 8.3%, 7.8%, 7%,  ]


模型预测数字是:  5 ,   正确数字是:  5  .    预测正确
output =[ -0.85, -0.83, -0.77, -0.81, -0.76, 0.81, -0.77, -0.79, -0.84, -0.82,  ]
Softmax=[ 6.8%, 7%, 7.4%, 7.1%, 7.4%, 36%, 7.4%, 7.2%, 6.9%, 7%,  ]


模型预测数字是:  6 ,   正确数字是:  6  .    预测正确
output =[ -0.91, -0.85, -0.86, -0.83, -0.87, -0.73, 0.88, -0.77, -0.77, -0.84,  ]
Softmax=[ 6.3%, 6.7%, 6.7%, 6.9%, 6.6%, 7.6%, 38%, 7.3%, 7.3%, 6.8%,  ]


模型预测数字是:  7 ,   正确数字是:  7  .    预测正确
output =[ -0.75, -0.29, -0.94, -0.61, -0.75, -0.85, -0.83, 0.36, -0.82, -0.83,  ]
Softmax=[ 8.2%, 13%, 6.7%, 9.4%, 8.1%, 7.4%, 7.5%, 25%, 7.6%, 7.5%,  ]


模型预测数字是:  8 ,   正确数字是:  8  .    预测正确
output =[ -0.85, -0.82, -0.83, -0.73, -0.88, -0.81, -0.76, -0.76, 0.77, -0.78,  ]
Softmax=[ 6.9%, 7.1%, 7.1%, 7.8%, 6.7%, 7.2%, 7.5%, 7.5%, 35%, 7.4%,  ]


模型预测数字是:  9 ,   正确数字是:  9  .    预测正确
output =[ -0.87, -0.84, -0.68, -0.81, -0.8, -0.92, -0.84, -0.25, -0.64, -0.21,  ]
Softmax=[ 8.1%, 8.3%, 9.8%, 8.6%, 8.7%, 7.6%, 8.3%, 15%, 10%, 16%,  ]

请按任意键继续. . .
  • 正确率曲线:

在这里插入图片描述

  • 误差曲线:(始终未收敛,原因,详见附录2)
    在这里插入图片描述

  • 控台输出结果:
    在这里插入图片描述在这里插入图片描述

  • 模型预测效果还是很不错的。可调整参数,增加手写图片,尝试其他的训练方法等,再观察模型表现。


github资源地址:[Release-x86/x64]

上一篇:轻量级C++神经网络应用库CreativeLus:3、复杂函数逼近。案例:多输入混合逼近。
下一篇:轻量级C++神经网络应用库CreativeLus:5、ResNet残差网咯。案例:(cifar-100)图片分类。


附录:几个发散性思维问题

  • 1、因为使用了Tanh作为激活函数,其值域在[-1,1],所以我们将原始数据映射到值域区间[-1,1],否则正确率永远无法收敛。
  • 2、虽然模型正确率收敛,但误差曲线却始终混乱并不收敛,原因是:采用了【位置型评价】后,模型除了关心主维度(即最大值出现的位置的维度)数据之外,并未对其他维度数据过多关心,甚至放弃了其他维度的数据的逼近和拟合(事实上模型预测,并不需要关心这些数据是否接近真实值,模型只关心出现极值的维度的位置,即可实现主分类判断,解决主分类问题)。而误差计算采用的了均方差算法,对每一个输出维度都做考量,当预测正确出现时,非主要维度(即非极值维度)的实际值可能与真实值相去甚远,所以均方误差始终很大,得不到收敛。
  • 可以在模型第2、第6层设置Dropout方式,代替LeNet-5在C3和C5层的链接矩阵效果。经过测试该正则化手段效果并不好。
//代码片段
//每一100次训练步幅,更新一次dropout网络。设定dropout层为第2、第6层,dropout剪除率15%
bp.setDropout(trainSets.size() / 600, { DRP(2,0.15),DRP(6,0.15) });
bp.train()
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值