輕量級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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章