Caffe學習筆記系列2—基於AlexNet網絡的模型訓練和特徵提取

Caffe學習筆記系列2—基於AlexNet網絡的模型訓練和特徵提取

        本節主要講解AlexNet網絡的模型訓練和特徵提取,主要是利用AlexNet對自己的數據進行訓練。

         在“Caffe學習筆記系列”文件夾中建立“CaffeTest2”文件夾,本節的所有操作在該文件夾進行。

一、模型的訓練

1、  源數據(圖片)準備。首先建立Data文件夾,文件夾裏面再建立子文件夾,子文件夾命名從0開始,用數據類別命名,即用圖片的類別命名;

2、  將圖片名稱轉化成.txt格式,形如“1\41_20170503074032\1_23.jpg(圖片路徑)  1(圖片類別)”。注意我的類別文件夾裏面還建立了文件夾,這是因爲我項目需要,可以不按照這種寫法。提供一段轉化爲該txt格式的代碼,本部分代碼工程在“Caffe學習筆記系列”文件夾—>“CaffeTest2”文件夾—>“getTrainTxt”文件夾中。如下:

#include<vector>

#include<opencv2\opencv.hpp>

#include<iostream>

#include<string>

#include<fstream>

#include<direct.h>

using namespace std;

using namespace cv;

//生成AlexNet訓練的數據格式

//圖片存放的目錄如下:Data\類別\相機號\xx.jpg

//得到的訓練集的txt格式:類別\相機號\xx.jpg 類別

//得到的驗證集的txt格式:類別\相機號\xx.jpg 類別

//注:訓練集和驗證集交錯生成

void main()

{

         stringtrainData = "../../trainData.txt";

         stringvalData = "../../valData.txt";

         ofstreamtrainOut(trainData);

         ofstreamvalOut(valData);

         intnumstart = 0, numend = 73;//類別;

         for(int i = numstart; i < numend; i++)

         {

                   stringmainFolder = "../../Data/";

                   mainFolder= mainFolder + to_string(i);

                  

                   Directorydir;

                   stringexten = "*";

                   booladdPath = true;

                   vector<string>filenames = dir.GetListFolders(mainFolder, exten, addPath);

 

                   for(int j = 0; j < filenames.size(); j++)

                   {

                            vector<string>tmp = dir.GetListFiles(filenames[j], "*.jpg", true);

                            for(int k = 0; k < tmp.size(); k++)

                            {

                                     if(k % 4 != 0)//訓練集:驗證集=4:1

                                               trainOut<< tmp[k] << " " << i << endl;//訓練

                                     else

                                               valOut<< tmp[k] << " " << i << endl;

                            }
                   }
         }
}

3、步驟2中生成的txt格式形如“../../Data/1/41_20170503074032/1_23.jpg 1”,需要將其替換生成如“1\41_20170503074032\1_23.jpg 1”格式;

4、  轉換生成lmdb數據文件。建立convert文本文件,裏面編寫如下代碼:

..\CaffeDev\caffe-master\Build\x64\Release\convert_imageset.exe--resize_width=227 --resize_height=227 Data/ trainData.txt  train_lmdb -backend=lmdb

pause

然後,將其後綴名改爲.bat,運行之即可。生成驗證文件的數據格式時,將上面中的“trainData.txt”改爲“valData.txt”,“train_lmdb”改爲“val_lmdb”。

5、  生成均值文件,編寫如下代碼的mean.bat文件即可,並運行之。

..\CaffeDev\caffe-master\Build\x64\Release\compute_image_mean.exetrain_lmdb mean.binaryproto --backend=lmdb

pause

6、  編寫如下代碼的train.bat文件即可,並運行之。

..\CaffeDev\caffe-master\Build\x64\Release\caffe.exe train--solver=solver.prototxt

pause

7、其餘的.prototxt格式的網絡文件,自己修改一下類別即可。

提示,如果想繼承已有的網絡參數,可以編寫如下的train.bat:

..\CaffeDev\caffe-master\Build\x64\Release\caffe.exetrain --solver=solver.prototxt --weights= bvlc_reference_caffenet.caffemodel

pause

如果想接着上次中斷的地方繼續訓練,可以編寫如下的train.bat:

..\CaffeDev\caffe-master\Build\x64\Release\caffe.exetrain --solver=solver.prototxt --snapshot=caffenet_train_iter_9000.solverstate

pause

二、卷積權重和特徵可視化+提取圖片的FC7層特徵

該部分的具體代碼如下,讀者可以先clone下來跑一遍有個感性認識,然後再深究細節。本部分的代碼工程在“Caffe學習筆記系列”文件夾—>“CaffeTest2”文件夾—>“WeightandFeature”文件夾中。

頭文件featAndweightVisualize.h如下

#pragmaonce

#include<caffe/caffe.hpp>  

#include<opencv2/core/core.hpp>           

#include<opencv2/highgui/highgui.hpp> 

#include<opencv2/imgproc/imgproc.hpp> 

#include<algorithm> 

#include<iosfwd> 

#include<memory>               

#include<string> 

#include<utility> 

#include<vector>

usingnamespace caffe; 

usingnamespace cv;

usingnamespace std;

//===可視化卷積權重及卷積響應,

//===網絡是官方的AlexNet網絡

//===參數模型是在ImageNet數據集上預訓練好的參數

voidshowweight();                  //該卷積權重可視化

void showpicfeat();                  //特徵圖可視化

源文件featAndweightVisualize.cpp如下

#include"featAndweightVisualize.h"
//============權重可視化============
void showweight()

{
         Net<float>net("./AlexNet/deploy.prototxt", TEST);

         net.CopyTrainedLayersFrom("./AlexNet/bvlc_reference_caffenet.caffemodel");

         vector<boost::shared_ptr<Blob<float>> > params = net.params();    //獲取網絡的各個層學習到的參數(權值+偏置) 

         cout << "各層參數的維度信息爲:\n";//打印出該model學到的各層的參數的維度信息

         for (int i = 0;i<params.size(); ++i)

                   cout<< params[i]->shape_string() << endl;

         //對第一個卷積層進行可視化,第一個卷積層"conv1"的維度信息是96*3*11*11,即96個卷積核,每個卷積核是3通道的,每個卷積核尺寸爲11*11    

         //故該卷積層有96個圖,每個圖是11*11的三通道BGR圖像 

         int ii = 0;                                                                             //提取第1層的參數,此時爲conv1層 

         int width =params[ii]->shape(2);                                //寬度,第一個卷積層爲11  

         int height =params[ii]->shape(3);                               //高度,第一個卷積層爲11  

         int num =params[ii]->shape(0);                                  //卷積核的個數,第一個卷積層爲96   

         //將num個圖,放在同一張大圖上進行顯示,此時用OpenCV進行可視化,聲明一個大尺寸的圖片,使之能容納所有的卷積核圖   

         int imgHeight =(int)(1 + sqrt(num))*height;            //大圖的尺寸    

         int imgWidth = (int)(1+ sqrt(num))*width;

         Mat img1(imgHeight,imgWidth, CV_8UC3, Scalar(0, 0, 0));

         //同時,注意到各層的權值,是一個可正可負的實數,而在OpenCV裏的一般圖片,每個像素的值在0~255之間     

         //所以,需要對權值進行歸一化到0~255才能正常顯示 

         float maxValue =-1000, minValue = 10000;

         const float* tmpValue= params[ii]->cpu_data(); //獲取該層的參數,實際上是個一維數組  

         for (int i = 0;i<params[ii]->count(); i++)

         {

                   maxValue =std::max(maxValue, tmpValue[i]);

                   minValue =std::min(minValue, tmpValue[i]);

         }

         //對最終顯示的大尺寸圖片,進行逐個像素賦值 

         int kk = 0;                                                                                     //此時在畫第kk個卷積核 

         for (int y = 0;y<imgHeight; y += height)

         {

                   for (int x =0; x<imgWidth; x += width)

                   {

                            if(kk >= num)

                                     continue;

                            Matroi = img1(Rect(x, y, width, height));

                            for(int i = 0; i<height; i++)

                            {

                                     for(int j = 0; j<width; j++)

                                     {

                                               for(int k = 0; k<3; k++)

                                               {

                                                        floatvalue = params[ii]->data_at(kk, k, i, j);

                                                        //歸一化到0~255 

                                                        roi.at<Vec3b>(i,j)[k] = (value - minValue) / (maxValue - minValue) * 255;  

                                               }

                                     }

                            }

                            ++kk;

                   }

         }

         resize(img1, img1,Size(500, 500));                   //將顯示的大圖,調整爲500*500尺寸     

         imshow("conv1_weight",img1);

         imwrite("conv1_weight.jpg",img1);

         waitKey(0);

}

//=========特徵圖可視化=============
void showpicfeat()
{

         string model_file ="./AlexNet/deploy.prototxt";

         string trained_file ="./AlexNet/bvlc_reference_caffenet.caffemodel";

 

         boost::shared_ptr<Net<float>> _net;                                                                     //CNN網絡 

         _net.reset(newNet<float>(model_file, TEST));                                                     //定義一個網絡 

         _net->CopyTrainedLayersFrom(trained_file);                                                         //加載權重 

        

         cout << "網絡中的Blobs名稱爲:\n";                                                                           //打印出一張圖片經過網絡各層產出的各層輸出   

         vector<boost::shared_ptr<Blob<float>> > blobs = _net->blobs();    //得到各層的輸出特徵向量  

         vector<string>blob_names = _net->blob_names();                                                      //各層的輸出向量名字    

         cout <<blobs.size() << " " << blob_names.size() << endl;

         for (int i = 0;i<blobs.size(); i++)

         {

                   cout<< blob_names[i] << " " <<blobs[i]->shape_string() << endl;

         }

         cout << endl;

         Mat _img =imread("./AlexNet/1.jpg");

         Mat img =_img.clone();

         img.convertTo(img,CV_32FC3);                                                                                          //轉爲浮點圖 

         Blob<float>*inputBlob = _net->input_blobs()[0];

         int width =inputBlob->width();                                                                                            //網絡規定的輸入圖片的寬度和高度 

         int height =inputBlob->height();

         resize(img, img,Size(width, height));                                                                       //將測試圖片進行調整大小 

         //=====方法一前傳

         //float* data =inputBlob->mutable_cpu_data();   //將圖片的像素值,複製進網絡的輸入Blob 

         //for (int k = 0;k<3; ++k)

         //{

         //      for (int i = 0; i<height; ++i)

         //      {

         //               for (int j = 0; j<width; ++j)

         //               {

         //                         int index = (k*height +i)*width + j;  //獲取偏移量 

         //                         data[index] =img.at<Vec3f>(i, j)[k];

         //               }

         //      }

         //}

         //vector<Blob<float>*> inputs(1, inputBlob);

         //constvector<Blob<float>* >& outputBlobs = _net->Forward(inputs);

 

         //=====方法二前傳

         _net->input_blobs()[0]->set_cpu_data((float*)img.data);

         conststd::vector<caffe::Blob<float>*>& output_blob_ =_net->Forward(nullptr);

 

 

         //將測試圖片經過第一個卷積層的特徵圖可視化

         string blobName ="conv1";                                                                                                 //取經過第一個卷積層的特徵圖     

         assert(_net->has_blob(blobName));                                                                                  //爲免出錯,我們必須斷言,網絡中確實有名字爲blobName的特徵圖 

         boost::shared_ptr<Blob<float>>  conv1Blob =_net->blob_by_name(blobName);          //1*96*55*55    斷言成功後,按名字返回該 特徵向量 

         cout << "測試圖片的特徵響應圖的形狀信息爲:" << conv1Blob->shape_string() << endl;   //打印輸出的特徵圖的形狀信息 

         //特徵向量是經過了ReLU激活函數的,範圍在0~無窮大,我們爲了可視化,仍然需要歸一化到0~255   


         float maxValue =-10000000, minValue = 10000000;

         const float* tmpValue= conv1Blob->cpu_data();

         for (int i = 0;i<conv1Blob->count(); i++)

         {
                   maxValue =std::max(maxValue, tmpValue[i]);
                   minValue =std::min(minValue, tmpValue[i]);
         }

         width =conv1Blob->shape(3);                                                                                    //響應圖的寬度 

         height =conv1Blob->shape(2);                                                                                            //響應圖的高度     

         int num =conv1Blob->shape(1);                                                                                          //個數    

         int imgHeight =(int)(1 + sqrt(num))*height;

         int imgWidth = (int)(1+ sqrt(num))*width;

         Mat img2(imgHeight,imgWidth, CV_8UC1, Scalar(0));                                      //此時,應該是灰度圖 

         int kk = 0;

         for (int x = 0;x<imgHeight; x += height)

         {
                   for (int y =0; y<imgWidth; y += width)

                   {

                            if(kk >= num)

                                     continue;

                            Matroi = img2(Rect(y, x, width, height));

                            for(int i = 0; i<height; i++)

                            {

                                     for(int j = 0; j<width; j++)

                                     {

                                               floatvalue = conv1Blob->data_at(0, kk, i, j);

                                               roi.at<uchar>(i,j) = (value - minValue) / (maxValue - minValue) * 255;

                                     }

                            }

                            kk++;

                   }

         }

         resize(img2, img2,Size(500, 500));                                                                           //進行顯示   

         imshow("conv1_response",img2);

         imwrite("conv1_response.jpg",img2);

         waitKey(0);

}

頭文件getAlexNetFeature.h如下

#pragma once

#include <iostream>

#include <caffe/caffe.hpp> 

#include <opencv2/core/core.hpp> 

#include <opencv2/highgui/highgui.hpp> 

#include <opencv2/imgproc/imgproc.hpp> 

#include <algorithm> 

#include <iosfwd> 

#include <memory> 

#include <string> 

#include <utility> 

#include <vector> 

#include <cmath> 

#include "head.h"

using namespace caffe;

using namespace cv;

using namespace std;

//對AlexNet進行微調,獲取FC7層特徵4096維
class AlexNetFeat                                                                              //提取網絡最後一層全連接層(FC7)特徵 
{

private:

         boost::shared_ptr<Net<float>> _net;                      //CNN網絡 

         Mat mean_image;

         //求向量的模長 

         double getMold(constvector<double>& vec)         

         {

                   int n =vec.size();

                   double sum =0.0;

                   for (int i =0; i<n; ++i)

                            sum+= vec[i] * vec[i];

                   returnsqrt(sum);

         }

         //求兩個向量的餘弦相似度

         doublegetSimilarity(const vector<double>& lhs, constvector<double>& rhs)

         {

                   const doubleeps = 0.000001;

                   int n =lhs.size();

                   assert(n ==rhs.size());

                   double tmp =0.0;

                   for (int i =0; i<n; ++i)

                            tmp+= lhs[i] * rhs[i];

                  

                   doublelhsMold = getMold(lhs);

                   doublerhsMold = getMold(rhs);

                   if (lhsMold< eps || rhsMold < eps)

                            return0.0;

                  

                   return tmp /(lhsMold*rhsMold);

         }

public:

         AlexNetFeat(conststring& model_file, const string& trained_file, const string&mean_file); //構造函數,初始化網絡

         doublegetSimilarity(const Mat& lhs, const Mat& rhs);                                                                                    //比較兩個圖像的餘弦相似度 

         vector<double>getLastLayerFeatures(const Mat& _img);                                                                                       //求一張圖片經過最後一層(fc7)的特徵向量 

};

void getAlexNetFeature();

源文件getAlexNetFeature.cpp如下

#include"getAlexNetFeature.h"
//構造函數,初始化網絡
AlexNetFeat::AlexNetFeat(const string& model_file, conststring& trained_file, const string &mean_file)
{

         _net.reset(newNet<float>(model_file, TEST));                         //定義一個網絡 

         _net->CopyTrainedLayersFrom(trained_file);                             //加載權重 

         //===採用自己訓練得到的均值文件mean.binaryproto

         //CHECK_EQ(_net->num_inputs(),1) << "Network should have exactly one input.";

         //CHECK_EQ(_net->num_outputs(),1) << "Network should have exactly one output.";

 

         //Blob<float>*input_layer = _net->input_blobs()[0];

         //int num_channels_ =input_layer->channels();

         //CHECK(num_channels_== 3) << "Input layer should have 3 channels.";

         //Size input_geometry_= cv::Size(input_layer->width(), input_layer->height());

         //BlobProtoblob_proto;

         //ReadProtoFromBinaryFileOrDie(mean_file.c_str(),&blob_proto);

         ////把BlobProto 轉換爲Blob<float>類型

         //Blob<float>mean_blob;

         //mean_blob.FromProto(blob_proto);

         //CHECK_EQ(mean_blob.channels(),num_channels_) << "Number of channels of mean file doesn't matchinput layer.";

         ////把三通道的圖片分開存儲,三張圖片按順序保存到channels中

         //std::vector<cv::Mat>channels;

         //float* data =mean_blob.mutable_cpu_data();

         //for (int i = 0; i <num_channels_; ++i)

         //{

         //      cv::Mat channel(mean_blob.height(),mean_blob.width(), CV_32FC1, data);

         //      channels.push_back(channel);

         //      data += mean_blob.height() *mean_blob.width();

         //}

         ////重新合成一張圖片

         //cv::Mat mean;

         //cv::merge(channels,mean);

         //cv::Scalarchannel_mean = cv::mean(mean);

         //mean_image =cv::Mat(input_geometry_, mean.type(), channel_mean);

         //===採用官方訓練得到的均值文件mean.bmp

         mean_image =imread(mean_file);//不採用微調時的均值文件

}

//比較兩個圖像的餘弦相似度 
double AlexNetFeat::getSimilarity(const Mat& lhs, const Mat&rhs)
{

         vector<double>feat1, feat2;

         feat1 =getLastLayerFeatures(lhs);

         feat2 =getLastLayerFeatures(rhs);

         returnstd::max<double>(0, getSimilarity(feat1, feat2));

}

//求一張圖片經過最後一層(fc7)的特徵向量 
vector<double> AlexNetFeat::getLastLayerFeatures(constMat& _img)
{

         Blob<float>*inputBlob = _net->input_blobs()[0];

         int width =inputBlob->width();                                                                                                               //網絡規定的輸入圖片的寬度和高度 

         int height =inputBlob->height();

         Mat temp_img =_img.clone();

         temp_img.convertTo(temp_img,CV_32FC3);                                                                                             //轉爲浮點圖                

         resize(temp_img,temp_img, Size(width, height));                                                                 //將測試圖片進行調整大小 
 
         Mat temp_mean =mean_image.clone();

         temp_mean.convertTo(temp_mean,CV_32FC3);

         resize(temp_mean,temp_mean, Size(width, height), 0.0, 0.0, CV_INTER_LINEAR);

         Mat img;

         subtract(temp_img,temp_mean, img);

         //Mat img =temp_img.clone();

         float* data =inputBlob->mutable_cpu_data();                                                                        //將圖片的像素值,複製進網絡的輸入Blob 

         for (int k = 0;k<3; ++k)

         {

                   for (int i =0; i<height; ++i)

                   {

                            for(int j = 0; j<width; ++j)

                            {

                                     intindex = (k*height + i)*width + j;                                                         //獲取偏移量 

                                     data[index]= img.at<Vec3f>(i, j)[k];

                            }

                   }

         }

         vector<Blob<float>*> inputs(1, inputBlob);

         constvector<Blob<float>* >& outputBlobs = _net->Forward(inputs);                     //進行前向傳播,並返回最後一層的blob 

 

         boost::shared_ptr<caffe::Blob<float>>layerData = _net->blob_by_name("fc7");//4096維向量

         const float* pstart =layerData->cpu_data();

         vector<double>result;

         for (int i = 0; i <layerData->count(); i++)

         {

                   result.push_back(pstart[i]);

         }

         return result;

}

void getAlexNetFeature()
{

         string model_file ="./AlexNet/deploy.prototxt";
         string trained_file ="./AlexNet/bvlc_reference_caffenet.caffemodel";
         string mean_file ="./AlexNet/mean.bmp";
         AlexNetFeatalexnetfeat(model_file, trained_file, mean_file);
         Mat _img =imread("./AlexNet/1.jpg");
         vector<double>fc7feat=alexnetfeat.getLastLayerFeatures(_img);
}

主函數main.cpp如下:

#include"featAndweightVisualize.h"
#include"getAlexNetFeature.h"
int main()
{

         showweight();
         showpicfeat();
         getAlexNetFeature();
}


conv1權重可視化

     

                             conv1響應可視化

提示:本部分的代碼工程在“Caffe學習筆記系列”文件夾—>“CaffeTest2”文件夾中。

 代碼鏈接如下:https://pan.baidu.com/s/1LoS32b7YDILW4i2phVUPHg 密碼:s3hd

注:本部分的訓練數據集Data文件夾不予提供。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章