之前在 http://blog.csdn.net/fengbingchun/article/details/50987185?中仿照Caffe中的examples實現對手寫數字進行識別,這里詳細介紹下其執行流程并精簡了實現代碼,使用Caffe對MNIST數據集進行train的文章可以參考 ?http://blog.csdn.net/fengbingchun/article/details/68065338 :
1.???先注冊所有層,執行layer_factory.hpp中類LayerRegisterer的構造函數,類LayerRegistry的AddCreator和Registry靜態函數;關于Caffe中Layer的注冊可以參考: http://blog.csdn.net/fengbingchun/article/details/54310956?
2.??指定執行mode是采用CPU還是GPU;
3.???指定需要的.prototxt和.caffemodel文件:注意此處的.prototxt文件(lenet_train_test_.prototxt)與train時.prototxt文件(lenet_train_test.prototxt)在內容上的差異。.caffemodel文件即是train后最終生成的二進制文件lenet_iter_10000.caffemodel,里面存放著所有層的權值和偏置。lenet_train_test_.prototxt文件內容如下:
name: "LeNet" # net名
layer { # memory required: (784+1)*4=3140
? name: "data" # layer名字
? type: "MemoryData" # layer類型,Data enters Caffe through data layers,read data directly from memory
? top: "data" # top名字, shape: 1 1 28 28 (784)
? top: "label" ?# top名字, shape: 1 (1) #感覺并無實質作用,僅用于增加一個top blob,不可去掉
? memory_data_param { # 內存數據參數
? ? batch_size: 1 # 指定待識別圖像一次的數量
? ? channels: 1 # 指定待識別圖像的通道數
? ? height: 28 # 指定待識別圖像的高度
? ? width: 28 # 指定待識別圖像的寬度
? }
? transform_param { # 圖像預處理參數
? ? scale: 0.00390625 # 對圖像像素值進行scale操作,范圍[0, 1)
? }
}
layer { # memory required: 11520*4=46080
? name: "conv1" # layer名字
? type: "Convolution" # layer類型,卷積層
? bottom: "data" # bottom名字
? top: "conv1" # top名字, shape: 1 20 24 24 (11520)
? param { # Specifies training parameters
? ? lr_mult: 1 # The multiplier on the global learning rate
? }
? param { # Specifies training parameters
? ? lr_mult: 2 # The multiplier on the global learning rate
? }
? convolution_param { # 卷積參數
? ? num_output: 20 # 輸出特征圖(feature map)數量
? ? kernel_size: 5 # 卷積核大小(卷積核其實就是權值)
? ? stride: 1 # 滑動步長
? ? weight_filler { # The filler for the weight
? ? ? type: "xavier" # 權值使用xavier濾波
? ? }
? ? bias_filler { # The filler for the bias
? ? ? type: "constant" # 偏置使用常量濾波
? ? }
? }
}
layer { # memory required: 2880*4=11520
? name: "pool1" # layer名字
? type: "Pooling" # layer類型,Pooling層
? bottom: "conv1" # bottom名字
? top: "pool1" # top名字, shape: 1 20 12 12 (2880)
? pooling_param { # pooling parameter,pooling層參數
? ? pool: MAX # pooling方法:最大值采樣
? ? kernel_size: 2 # 濾波器大小
? ? stride: 2 # 滑動步長
? }
}
layer { # memory required: 3200*4=12800
? name: "conv2" # layer名字
? type: "Convolution" # layer類型,卷積層
? bottom: "pool1" # bottom名字
? top: "conv2" # top名字, shape: 1 50 8 8 (3200)
? param { # Specifies training parameters
? ? lr_mult: 1 # The multiplier on the global learning rate
? }
? param { # Specifies training parameters
? ? lr_mult: 2 # The multiplier on the global learning rate
? }
? convolution_param { # 卷積參數
? ? num_output: 50 # 輸出特征圖(feature map)數量
? ? kernel_size: 5 # 卷積核大小(卷積核其實就是權值)
? ? stride: 1 # 滑動步長
? ? weight_filler { # The filler for the weight
? ? ? type: "xavier" # 權值使用xavier濾波
? ? }
? ? bias_filler { # The filler for the bias
? ? ? type: "constant" # 偏置使用常量濾波
? ? }
? }
}
layer { # memory required: 800*4=3200
? name: "pool2" # layer名字
? type: "Pooling" # layer類型,Pooling層
? bottom: "conv2" # bottom名字
? top: "pool2" # top名字, shape: 1 50 4 4 (800)
? pooling_param { # pooling parameter,pooling層參數
? ? pool: MAX # pooling方法:最大值采樣
? ? kernel_size: 2 # 濾波器大小
? ? stride: 2 # 滑動步長
? }
}
layer { # memory required: 500*4=2000
? name: "ip1" # layer名字
? type: "InnerProduct" # layer類型,全連接層
? bottom: "pool2" # bottom名字
? top: "ip1" # top名字, shape: 1 500 (500)
? param { # Specifies training parameters
? ? lr_mult: 1 # The multiplier on the global learning rate
? }
? param { # Specifies training parameters
? ? lr_mult: 2 # The multiplier on the global learning rate
? }
? inner_product_param { # 全連接層參數
? ? num_output: 500 # 輸出特征圖(feature map)數量
? ? weight_filler { # The filler for the weight
? ? ? type: "xavier" # 權值使用xavier濾波
? ? }
? ? bias_filler { # The filler for the bias
? ? ? type: "constant" # 偏置使用常量濾波
? ? }
? }
}
# ReLU: Given an input value x, The ReLU layer computes the output as x if x > 0 and?
# negative_slope * x if x <= 0. When the negative slope parameter is not set,
# it is equivalent to the standard ReLU function of taking max(x, 0).
# It also supports in-place computation, meaning that the bottom and
# the top blob could be the same to preserve memory consumption
layer { # memory required: 500*4=2000
? name: "relu1" # layer名字
? type: "ReLU" # layer類型
? bottom: "ip1" # bottom名字
? top: "ip1" # top名字 (in-place), shape: 1 500 (500)
}
layer { # memory required: 10*4=40
? name: "ip2" # layer名字
? type: "InnerProduct" # layer類型,全連接層
? bottom: "ip1" # bottom名字
? top: "ip2" # top名字, shape: 1 10 (10)
? param { # Specifies training parameters
? ? lr_mult: 1 # The multiplier on the global learning rate
? }
? param { # Specifies training parameters
? ? lr_mult: 2 # The multiplier on the global learning rate
? }
? inner_product_param {
? ? num_output: 10 # 輸出特征圖(feature map)數量
? ? weight_filler { # The filler for the weight
? ? ? type: "xavier" # 權值使用xavier濾波
? ? }
? ? bias_filler { # The filler for the bias
? ? ? type: "constant" # 偏置使用常量濾波
? ? }
? }
}
layer { # memory required: 10*4=40
? name: "prob" # layer名字
? type: "Softmax" # layer類型
? bottom: "ip2" # bottom名字
? top: "prob" # top名字, shape: 1 10 (10)
}
# 占用總內存大小為:3140+46080+11520+12800+3200+2000+2000+40+40=80820
lenet_train_test_.prototxt可視化結果( http://ethereon.github.io/netscope/quickstart.html?)如下圖:
train時lenet_train_test.prototxt與識別時用到的lenet_train_test_.prototxt差異:
(1)、數據層:訓練時用Data,是以lmdb數據存儲方式載入網絡的,而識別時用MemoryData方式直接從內存載入網絡;
(2)、Accuracy層:僅訓練時用到,用以計算test集的準確率;
(3)、輸出層Softmax/SoftmaxWithLoss層:訓練時用SoftmaxWithLoss,輸出loss值,識別時用Softmax輸出10類數字的概率值。
4.???創建Net對象并初始化,有兩種方法:一個是通過傳入string類型(.prototxt文件)參數創建,一個是通過傳入NetParameter參數;
5.???調用Net的CopyTrainedLayersFrom函數加載在train時生成的二進制文件.caffemodel即lenet_iter_10000.caffemodel,有兩種方法,一個是通過傳入string類型(.caffemodel文件)參數,一個是通過傳入NetParameter參數;
6.???獲取Net相關參數在后面識別時需要用到:
(1)、通過調用Net的blob_by_name函數獲得待識別圖像所要求的通道數、寬、高;
(2)、通過調用Net的output_blobs函數獲得輸出blob的數目及大小,注:這里輸出2個blob,第一個是label,count為1,第二個是prob,count為10,即表示數字識別結果的概率值。
7.???開始進行手寫數字識別:
(1)、通過opencv的imread函數讀入圖像;
(2)、根據從Net中獲得的需要輸入圖像的要求對圖像進行顏色空間轉換和縮放;
(3)、因為MNIST train時,圖像為前景為白色,背景為黑色,而現在輸入圖像為前景為黑色,背景為白色,因此需要對圖像進行取反操作;
(4)、將圖像數據傳入Net,有兩種方法:一種是通過MemoryDataLayer類的Reset函數,一種是通過MemoryDataLayer類的AddMatVector函數傳入Mat參數;
(5)、調用Net的ForwardPrefilled函數進行前向計算;
(6)、輸出識別結果,注,前向計算完返回的Blob有兩個,第二個Blob中的數據才是最終的識別結果的概率值,其中最大值的索引即是識別結果。
8.???通過lenet_train_test_.prototxt文件分析各層的權值、偏置和神經元數量,共9層:
(1)、data數據層:無權值和偏置,神經元數量為1*1*28*28+1=785;
(2)、conv1卷積層:卷積窗大小為5*5,輸出特征圖數量為20,卷積窗種類為20,輸出特征圖大小為24*24,可訓練參數(權值+閾值(偏置))為 20*1*5*5+20=520,神經元數量為1*20*24*24=11520;
(3)、pool1降采樣層:濾波窗大小為2*2,輸出特征圖數量為20,濾波窗種類為20,輸出特征圖大小為12*12,可訓練參數(權值+偏置)為1*20+20=40,神經元數量為1*20*12*12=2880;
(4)、conv2卷積層:卷積窗大小為5*5,輸出特征圖數量為50,卷積窗種類為50*20,輸出特征圖大小為8*8,可訓練參數(權值+偏置)為50*20*5*5+50=25050,神經元數量為1*50*8*8=3200;
(5)、pool2降采樣層:濾波窗大小為2*2,輸出特征圖數量為50,濾波窗種類為50,輸出特征圖大小為4*4,可訓練參數(權值+偏置)為1*50+50=100,神經元數量為1*50*4*4=800;
(6)、ip1全連接層:濾波窗大小為1*1,輸出特征圖數量為500,濾波窗種類為500*800,輸出特征圖大小為1*1,可訓練參數(權值+偏置)為500*800*1*1+500=400500,神經元數量為1*500*1*1=500;
(7)、relu1層:in-placeip1;
(8)、ip2全連接層:濾波窗大小為1*1,輸出特征圖數量為10,濾波窗種類為10*500,輸出特征圖大小為1*1,可訓練參數(權值+偏置)為10*500*1*1+10=5010,神經元數量為1*10*1*1=10;
(9)、prob輸出層:神經元數量為1*10*1*1+1=11。
精簡后的手寫數字識別測試代碼如下:
int mnist_predict()
{caffe::Caffe::set_mode(caffe::Caffe::CPU);const std::string param_file{ "E:/GitCode/Caffe_Test/test_data/model/mnist/lenet_train_test_.prototxt" };const std::string trained_filename{ "E:/GitCode/Caffe_Test/test_data/model/mnist/lenet_iter_10000.caffemodel" };const std::string image_path{ "E:/GitCode/Caffe_Test/test_data/images/" };// 有兩種方法可以實例化net// 1. 通過傳入參數類型為std::stringcaffe::Net<float> caffe_net(param_file, caffe::TEST);caffe_net.CopyTrainedLayersFrom(trained_filename);// 2. 通過傳入參數類型為caffe::NetParameter//caffe::NetParameter net_param1, net_param2;//caffe::ReadNetParamsFromTextFileOrDie(param_file, &net_param1);//net_param1.mutable_state()->set_phase(caffe::TEST);//caffe::Net<float> caffe_net(net_param1);//caffe::ReadNetParamsFromBinaryFileOrDie(trained_filename, &net_param2);//caffe_net.CopyTrainedLayersFrom(net_param2);int num_inputs = caffe_net.input_blobs().size(); // 0 ??const boost::shared_ptr<caffe::Blob<float> > blob_by_name = caffe_net.blob_by_name("data");int image_channel = blob_by_name->channels();int image_height = blob_by_name->height();int image_width = blob_by_name->width();int num_outputs = caffe_net.num_outputs();const std::vector<caffe::Blob<float>*> output_blobs = caffe_net.output_blobs();int require_blob_index{ -1 };const int digit_category_num{ 10 };for (int i = 0; i < output_blobs.size(); ++i) {if (output_blobs[i]->count() == digit_category_num)require_blob_index = i;}if (require_blob_index == -1) {fprintf(stderr, "ouput blob don't match\n");return -1;}std::vector<int> target{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };std::vector<int> result;for (auto num : target) {std::string str = std::to_string(num);str += ".png";str = image_path + str;cv::Mat mat = cv::imread(str.c_str(), 1);if (!mat.data) {fprintf(stderr, "load image error: %s\n", str.c_str());return -1;}if (image_channel == 1)cv::cvtColor(mat, mat, CV_BGR2GRAY);else if (image_channel == 4)cv::cvtColor(mat, mat, CV_BGR2BGRA);cv::resize(mat, mat, cv::Size(image_width, image_height));cv::bitwise_not(mat, mat);// 將圖像數據載入Net網絡,有2種方法boost::shared_ptr<caffe::MemoryDataLayer<float> > memory_data_layer =boost::static_pointer_cast<caffe::MemoryDataLayer<float>>(caffe_net.layer_by_name("data"));// 1. 通過MemoryDataLayer類的Reset函數mat.convertTo(mat, CV_32FC1, 0.00390625);float dummy_label[1] {0};memory_data_layer->Reset((float*)(mat.data), dummy_label, 1);// 2. 通過MemoryDataLayer類的AddMatVector函數//std::vector<cv::Mat> patches{mat}; // set the patch for testing//std::vector<int> labels(patches.size());//memory_data_layer->AddMatVector(patches, labels); // push vector<Mat> to data layerfloat loss{ 0.0 };const std::vector<caffe::Blob<float>*>& results = caffe_net.ForwardPrefilled(&loss); // Net forwardconst float* output = results[require_blob_index]->cpu_data();float tmp{ -1 };int pos{ -1 };fprintf(stderr, "actual digit is: %d\n", target[num]);for (int j = 0; j < 10; j++) {printf("Probability to be Number %d is: %.3f\n", j, output[j]);if (tmp < output[j]) {pos = j;tmp = output[j];}}result.push_back(pos);}for (auto i = 0; i < 10; i++)fprintf(stderr, "actual digit is: %d, result digit is: %d\n", target[i], result[i]);fprintf(stderr, "predict finish\n");return 0;
}
?
測試結果如下:
GitHub:https://github.com/fengbingchun/Caffe_Test
---------------------?
作者:fengbingchun?
來源:CSDN?
原文:https://blog.csdn.net/fengbingchun/article/details/69001433?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
---------------------?
作者:fengbingchun?
來源:CSDN?
原文:https://blog.csdn.net/fengbingchun/article/details/69001433?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!