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

来源:互联网 发布:java 转二进制 编辑:程序博客网 时间:2024/06/18 10:02

       在上一篇博客中,已经介绍了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接口可视化的权重是一样的。





1 0
原创粉丝点击