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文件夹不予提供。

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