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