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()