caffe之网络权重可视化(C++实现)

       在上一篇博客中,已经介绍了caffe用训练好的model对一副测试图片进行分类的c++实现过程。

       今天,我们来看对一个训练好的model,用c++进行model的网络权值的可视化,比如看看网络中的第一个卷积层conv1、第一个卷积层pool1到底学到的是什么特征。

       正如上一篇博客结尾处说的,网络权值的可视化,是不需要网络的前向传播就能看到的,因为学到的参数已经事实存在了,只要加载这个model,把相关的参数提取出来就ok啦,所以,为了简单起见,这里就不在classification.cpp里去添加代码实现,而是在一个新的cpp文件里实现这个功能(当然,最好是可以将权值可视化功能封装在classification.cpp这个文件里,这样子的话,该文件就提供了分类、可视化等功能,同理,还可以将提取某一层特征向量,特征图可视化也集成在这个文件里,这样的话对以后的需求就能方便的使用了,比如讲第一个全连接层的特征向量提取出来,再送进更精准的分类器(如SVM)去分类,而只把cnn当成特征提取器);

不失一般性,我们借用caffe中已经训练好的模型caffenet(2012年的alexnet的修改版),对该model进行可视化

// File: test.cpp
#include <caffe/caffe.hpp>    //为了能正常编译,需要引入caffe的头文件
#include <opencv2/core/core.hpp>            //这三行是为了引用opencv
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <algorithm>
#include <iosfwd>
#include <memory>                  //使用c++智能指针,必须包含该目录
#include <string>
#include <utility>
#include <vector>
using namespace caffe;  // NOLINT(build/namespaces)
using namespace cv;
using namespace std;

int main(){
   //初始化一个网络,网络结构从caffenet_deploy.prototxt文件中读取,TEST表示是测试阶段
   Net<float> net("caffenet_deploy.prototxt",TEST); 
   net.CopyTrainedLayersFrom("caffenet.caffemodel");   //读取已经训练好的model的参数
   vector<shared_ptr<Blob<float> > > params=net.params();    //获取网络的各个层学习到的参数(权值+偏置)

   //打印出该model学到的各层的参数的维度信息
   cout<<"各层参数的维度信息为:\n";
   for(int i=0;i<params.size();++i)
       cout<<params[i]->shape_string()<<endl;
   
   return 0;
}

此时,我们需要编译该文件,假设该文件取名test.cpp,则执行以后命令编译该文件

g++ -o netapp.bin test.cpp  `pkg-config --libs --cflags opencv` -I ~/caffe/include/ -I ~/caffe/src/ -I ~/caffe/build/src/ -I /usr/local/cuda-8.0/include/ -L ~/caffe/build/lib/ -lprotobuf -lcaffe -lglog -lboost_system
解释下这条指令,前面几个是g++编译cpp文件的命令,`pkg-config --libs --cflags` 是引用opencv库,前三个 -I(大写的i)表示指定三个包含目录(因为会引用很多caffe里的文件),第四个-I,是指定用cuda的包含目录,-L表示该文件需要引用的库目录,而接下来的四个-l 则表示具体链接的四个动态库了。(如果你没有GPU,只要用cpu来运行的话,则删除第四个-I,以及在命令中,加一条 -D CPU_ONLY就可以正常编译了)

好了,此时已经编译成功,生成了netapp.bin可执行文件,我们再把caffenet的deploy.prototxt和caffenet.model文件放在这个目录下,运行  ./netapp.bin,打印出一系列blog后,会输出以下信息:

96 3 11 11 (34848)
96 (96)
256 48 5 5 (307200)
256 (256)
384 256 3 3 (884736)
384 (384)
384 192 3 3 (663552)
384 (384)
256 192 3 3 (442368)
256 (256)
4096 9216 (37748736)
4096 (4096)
4096 4096 (16777216)
4096 (4096)
1000 4096 (4096000)
1000 (1000)

我们都知道alexnet模型的网络结构,由数据层、5个卷积层+池化层+ReLU层交叉、3个全连接层组成,而这个网络需要学习的参数(权重+偏置),则只有5个卷积层+3个全连接层(池化层和Relu层是不用学习权重参数的),从打印的信息来看,分别是第一个卷积层权重参数的维度,第一个卷积层偏置参数的维度;第一个卷积层权重参数的维度,第一个卷积层偏置参数的维度;.......以此类推,刚好16行,没层输出2行,所以一共8层(5卷积+3全连接)。 

所以,我们可以对某一个层的权重进行可视化了,继续往test.cpp文件中添加代码

//对第一个卷积层进行可视化,第一个卷积层"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;
        Mat roi=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++){					
                    float value=params[ii]->data_at(kk,k,i,j);
                    roi.at<Vec3b>(i,j)[k]=(value-minValue)/(maxValue-minValue)*255;   //归一化到0~255
		}
            }
         }
        ++kk;
    }
}
resize(img1,img1,Size(500,500));   //将显示的大图,调整为500*500尺寸	
imshow("conv1",img1);              //显示	
waitKey(0); 

此时,第一个卷积层的96个权重图如上所示,可以看出,这个跟用python或者matlab接口可视化的权重是一样的。





猜你喜欢

转载自blog.csdn.net/zxj942405301/article/details/71194359
今日推荐