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