1. 程式人生 > >caffe之網路權重視覺化(C++實現),分析網路功能 caffe之網路權重視覺化(C++實現)

caffe之網路權重視覺化(C++實現),分析網路功能 caffe之網路權重視覺化(C++實現)

caffe之網路權重視覺化(C++實現)

2017年05月05日 11:28:12 閱讀數:3083
													<span class="tags-box artic-tag-box">
							<span class="label">標籤:</span>
															<a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;CNN&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=CNN&amp;t=blog" target="_blank">CNN																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;c++&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=c++&amp;t=blog" target="_blank">c++																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;caffe&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=caffe&amp;t=blog" target="_blank">caffe																</a><a data-track-click="{&quot;mod&quot;:&quot;popu_626&quot;,&quot;con&quot;:&quot;opencv&quot;}" class="tag-link" href="http://so.csdn.net/so/search/s.do?q=opencv&amp;t=blog" target="_blank">opencv																</a>
						<span class="article_info_click">更多</span></span>
																				<div class="tags-box space">
							<span class="label">個人分類:</span>
															<a class="tag-link" href="https://blog.csdn.net/zxj942405301/article/category/6900848" target="_blank">Caffe																</a>
						</div>
																							</div>
			<div class="operating">
													</div>
		</div>
	</div>
</div>
<article class="baidu_pl">
	<div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post">
							<div class="article-copyright">
				版權宣告:本文為博主原創文章,未經博主允許不得轉載。					https://blog.csdn.net/zxj942405301/article/details/71194359				</div>
							            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f76675cdea.css">
					<div class="htmledit_views">

       在上一篇部落格中,已經介紹了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介面視覺化的權重是一樣的。