1. 程式人生 > >caffe for windows的matlab介面(四):權重和特徵圖視覺化的一個例子

caffe for windows的matlab介面(四):權重和特徵圖視覺化的一個例子

模型讀取

參照三,想實現一個自己影象的視覺化過程:
首先我發現自己訓練出的model沒有deploy檔案。查閱了下:“如果要把訓練好的模型拿來測試新的圖片,那必須得要一個deploy.prototxt檔案,這個檔案實際上和test.prototxt檔案差不多,只是頭尾不相同而也。deploy檔案沒有第一層資料輸入層,也沒有最後的Accuracy層,但最後多了一個Softmax概率層。”

記得我用的是CaffeNet,於是我在E:\CaffeDev\caffe-master\models\bvlc_reference_caffenet目錄下找到了這個檔案,把它拷貝到資料資料夾裡方便查閱和管理。

這是我做實驗的一個資料夾,代表我的資料是處理過的100個人的資料,用12張做訓練,用其中的4張做驗證。當然這種驗證是不太科學的,後續會有改進。這裡只是嘗試用ta做一下視覺化。其中1900的資料夾裡存放了我用第一次較小迭代次數做初始化(微調)的再次訓練得到的準確率為1的model

首先還是讀取模型:

caffe.set_mode_cpu();%設定CPU模式  
model = 'F:/CaffeDev/caffe-master/data/myself8chuli100test4/deploy.prototxt';%模型  
weights ='F:/CaffeDev/caffe-master/data/myself8chuli100test4/1900/caffenet_train_iter_900.caffemodel';%引數  
net=caffe.Net(model,'test');%測試  
net.copy_from(weights); %得到訓練好的權重引數  
net %顯示net的結構  

然後遇到比較嚴肅的一個問題,當進行到把weights copy到net中去的時候,matlab停止工作了。。。比較尷尬,因為我用的就是現有的CaffeNet,怎麼會出現這個問題呢。。。

尷尬的局面

反思了一下想起來我修改過影象的大小引數。。。
原來是輸入256大小,進行了一次227的裁剪。而我是直接輸入128,裁剪也是128.第一次做的時候經驗不足,其實最好是把影象做一個原始大小基礎上的裁剪和變換,這樣準確率應該會有所提高。
最後是原始是分類1000,我的是分類100,在deploy.prototxt裡也要做相應的修改。

修改過後再執行,就沒有問題啦~我們看到了這個模型的結構。突然發現和(三)的net是一個呀。。。暈死。。。看來這篇價值不大,只是重複驗證而已。
這裡寫圖片描述

輸入資料整理

這是我用了標號為1的人的第一隻採集手掌:1_1.bmp
這張影象是經過加強處理的,我們可以很清晰地看到它的紋路,如下圖,(後續還會用採集的原圖,然後用到剪裁和對準,可能也能體現出CNN相比傳統識別方法魯棒性的一個提升。)
可以看出來這樣的影象方向性真的挺不錯噠
過程第一步是先把均值讀進來,原來自帶的例子是讀取了一個自帶的mat均值檔案,開始我想把的二值化的均值檔案image_mean.binaryproto轉換為均值mat檔案,但一直沒找到解決的好辦法。

一個簡單粗暴的解決方案:
pic = imread(‘1_1.bmp’);%讀取圖片

pic = imread('1_1.bmp');%讀取圖片  
pic_mean=mean2(pic)
for i=1:128
       for j=1:128
           for k=1:3
                A(i,j,k)=140.0021;
            end
        end
    end
 B=single(A);
 save my_mean_data B

先說一下過程,
①先把均值讀進來

d = load('F:\CaffeDev\mat2\my_mean_data.mat'); mean_data = d.B;  

②讀取圖片

im = imread('F:\CaffeDev\mat2\1_2.bmp');%讀取圖片  
IMAGE_DIM = 128;%影象將要resize的大小,建議resize為影象最小的那個維度  
CROPPED_DIM = 128;%待會需要把一張圖片crops成十塊,最終softmax求出每一塊可能的標籤  

把黑白輸入影象轉換為彩色:(為了套用官網彩色影象,不出錯的一個無奈之舉···)

function y = gray2rgb( x )
% x is the gray image
% y is the rgb image
d = size(x);
temp = zeros(d(1),d(2),3);
temp(:,:,1) = x;
temp(:,:,2) = x;
temp(:,:,3) = x;
y = temp;
im = gray2rgb( m )
im_data = im(:, :, [3, 2, 1]);  %matlab按照RGB讀取圖片,opencv是BGR,所以需要轉換順序為opencv處理格式  
im_data = permute(im_data, [2, 1, 3]);  % 原始影象m*n*channels,現在permute為n*m*channels大小  
im_data = single(im_data);  % 強制轉換資料為single型別  
im_data = imresize(im_data, [IMAGE_DIM IMAGE_DIM], 'bilinear');  % 線性插值resize影象

先零均值化一下,然後按照deploy和train的prototxt,將這隻手掌crop(分成)十塊,採用的是classification.demo的分割方法,分別取手掌的上下左右四個角以及中心的大小為deploy中提到的128*128大小。這是五個,然後再對圖片翻轉180°;合起來就是代表這隻手掌的十張圖片:

im_data = im_data - mean_data;  % 零均值  
crops_data = zeros(CROPPED_DIM, CROPPED_DIM, 3, 10, 'single');%注意此處是因為prototxt的輸入大小為寬*高*通道數*10  
indices = [0 IMAGE_DIM-CROPPED_DIM] + 1;%獲得十塊每一塊大小與原始影象大小差距,便於crops  
%下面就是如何將一張圖片crops成十塊  
n = 1;  
%此處兩個for迴圈並非是1:indices,而是第一次取indices(1),然後是indices(2),每一層迴圈兩次  
%分別讀取圖片四個角大小為CROPPED_DIM*CROPPED_DIM的圖片  
for i = indices  
    for j = indices  
        crops_data(:, :, :, n) = im_data(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :);%產生四個角的cropdata,1 2 3 4  
        crops_data(:, :, :, n+5) = crops_data(end:-1:1, :, :, n);%翻轉180°來一次,產生四個角的翻轉cropdata,6 7 8 9  
        n = n + 1;  
    end  
end  
center = floor(indices(2) / 2) + 1;  
%以中心為crop_data左上角頂點,讀取CROPPED_DIM*CROPPED_DIM的塊  
crops_data(:,:,:,5) = ...  
    im_data(center:center+CROPPED_DIM-1,center:center+CROPPED_DIM-1,:);  
%與for迴圈裡面一樣,翻轉180°,繞左邊界翻轉  
crops_data(:,:,:,10) = crops_data(end:-1:1, :, :, 5);

視覺化看看長啥樣:

 % im_data = im_data - mean_data;  % 零均值  
crops_data = zeros(CROPPED_DIM, CROPPED_DIM, 3, 10, 'single');%注意此處是因為prototxt的輸入大小為寬*高*通道數*10  
indices = [0 IMAGE_DIM-CROPPED_DIM] + 1;%獲得十塊每一塊大小與原始影象大小差距,便於crops  
%下面就是如何將一張圖片crops成十塊  
n = 1;  
%此處兩個for迴圈並非是1:indices,而是第一次取indices(1),然後是indices(2),每一層迴圈兩次  
%分別讀取圖片四個角大小為CROPPED_DIM*CROPPED_DIM的圖片  
for i = indices  
    for j = indices  
        crops_data(:, :, :, n) = im_data(i:i+CROPPED_DIM-1, j:j+CROPPED_DIM-1, :);%產生四個角的cropdata,1 2 3 4  
        crops_data(:, :, :, n+5) = crops_data(end:-1:1, :, :, n);%翻轉180°來一次,產生四個角的翻轉cropdata,6 7 8 9  
        n = n + 1;  
    end  
end  
center = floor(indices(2) / 2) + 1;  
%以中心為crop_data左上角頂點,讀取CROPPED_DIM*CROPPED_DIM的塊  
crops_data(:,:,:,5) = ...  
    im_data(center:center+CROPPED_DIM-1,center:center+CROPPED_DIM-1,:);  
%與for迴圈裡面一樣,翻轉180°,繞左邊界翻轉  
crops_data(:,:,:,10) = crops_data(end:-1:1, :, :, 5);  


hand_map=zeros(CROPPED_DIM*2,CROPPED_DIM*5,3);%兩行五列展示  
hand_num=0;  
for i=0:1  
    for j=0:4  
        hand_num=hand_num+1  
        hand_map(CROPPED_DIM*i+1:(i+1)*CROPPED_DIM,CROPPED_DIM*j+1:(j+1)*CROPPED_DIM,:)=crops_data(:,:,:,hand_num);  
    end  
end  
imshow(uint8(hand_map))

這裡寫圖片描述

前向計算

res=net.forward({crops_data});  
prob=res{1};  
prob1 = mean(prob, 2);  
[~, maxlabel] = max(prob1); 

這一步完畢以後,整個網路就會充滿引數了,權重,特徵圖均生成完畢,接下來視覺化它們。

特徵圖視覺化

特徵圖提取方法

說一下步驟,首先利用net 中的blob_name函式取出與deploy.prototxt對應的 top 名字,顯示一下看看,

names=net.blob_names  

names = 

    'data'
    'conv1'
    'pool1'
    'norm1'
    'conv2'
    'pool2'
    'norm2'
    'conv3'
    'conv4'
    'conv5'
    'pool5'
    'fc6'
    'fc7'
    'fc8'
    'prob'

然後利用blob呼叫get_data()函式獲取我們需要的特徵圖的值。注意,每一層的特徵圖是四維,看看前三層的特徵圖大小:

>>size(net.blobs(names{1}).get_data())

ans =

   128   128     3    10

>> size(net.blobs(names{2}).get_data())

ans =

    30    30    96    10

>> size(net.blobs(names{3}).get_data())

ans =

    15    15    96    10

結合deploy中每一層的卷積核大小以及步長,利用 (當前層特徵圖大小 - 卷積核大小) / 步長+1=下一層特徵圖大小,可以推匯出每一個featuremap 的前兩維,第三個維度代表的是卷積核個數,featuremap {2}到featuremap {3}是池化了。第四個維度代表最開始輸入了十張圖

部分視覺化方法

這一部分針對指定的第crop_num張影象在第map_num層進行視覺化。注意,這一部分的視覺化包含池化層等。

function [  ] = feature_partvisual( net,mapnum,crop_num )  
names=net.blob_names;  
featuremap=net.blobs(names{mapnum}).get_data();%獲取指定層的特徵圖  
[m_size,n_size,num,crop]=size(featuremap);%獲取特徵圖大小,長*寬*卷積核個數*通道數  
row=ceil(sqrt(num));%行數  
col=row;%列數  
feature_map=zeros(m_size*row,n_size*col);  
cout_map=1;  
for i=0:row-1  
    for j=0:col-1  
        if cout_map<=num  
            feature_map(i*m_size+1:(i+1)*m_size,j*n_size+1:(j+1)*n_size)=(mapminmax(featuremap(:,:,cout_map,crop_num),0,1)*255)';  
            cout_map=cout_map+1;  
        end  
    end  
end  
imshow(uint8(feature_map))  
str=strcat('feature map num:',num2str(cout_map-1));  
title(str)  
end  

呼叫方法:

mapnum=1;%第幾層的feature☆☆☆☆☆☆☆☆  
crop_num=1;%第幾個crop的特徵圖☆☆☆☆☆☆☆☆  
feature_partvisual( net,mapnum,crop_num )

中間有個處理細節是歸一化然後乘以255,是避免featuremap的數值過小,或者有負數,導致特徵圖一片漆黑;在下面的權重視覺化方法採取的是另一種處理。
讀者可以更改”☆”標誌的行中的數值去提取不同crop影象的不同層特徵圖。
第一層特徵圖
第二層的featuremap

全部視覺化

這一部分視覺化每一張輸入圖片在指定卷積層的特徵圖,按照每一行為儲存圖片的特徵圖為圖例。

function [  ] = feature_fullvisual( net,mapnum )  
names=net.blob_names;  
featuremap=net.blobs(names{mapnum}).get_data();%獲取指定層的特徵圖  
[m_size,n_size,num,crop]=size(featuremap)%獲取特徵圖大小,長*寬*卷積核個數*圖片個數  
row=crop;%行數  
col=num;%列數  
feature_map=zeros(m_size*row,n_size*col);  
for i=0:row-1  
    for j=0:col-1  
        feature_map(i*m_size+1:(i+1)*m_size,j*n_size+1:(j+1)*n_size)=(mapminmax(featuremap(:,:,j+1,i+1),0,1)*255)';  
    end  
end  
figure  
imshow(uint8(feature_map))  
str=strcat('feature map num:',num2str(row*col));  
title(str)  
end  

呼叫方法:

mapnum=2;%第幾層的feature☆☆☆☆☆☆☆☆  
feature_fullvisual( net,mapnum )  

第二層,基本看不清楚,十張輸入圖片,每一張都有96個特徵圖,不好顯示

卷積核可視化

卷積核可視化中,採用的畫素放大方法與特徵圖的不一樣。特徵圖中採用歸一化mapminmax到(0,1)範圍,然後乘以255,;在下面卷積核的視覺化中採用(x-最小值)/最大值*255的方法去放大畫素。讀者可根據自己喜好決定。

權重提取方法

通過net 的layer_names 函式能夠獲取deploy.txt 對應的name 的名稱,每一個name的blob對應兩個值,分別是權重和偏置,提取方法如下:
【注】貌似僅僅卷積核能夠獲取到權重,池化層倒是沒有權重,全連線部分也是有權重的,但是沒什麼意義

layers=net.layer_names;  
convlayer=[];  
for i=1:length(layers)  
    if strcmp(layers{i}(1:3),'con')%僅僅卷積核能獲取到權重  
        convlayer=[convlayer;layers{i}];  
    end  
end  
w=cell(1,length(convlayer));%儲存權重  
b=cell(1,length(convlayer));%儲存偏置  
for i=1:length(convlayer)  
    w{i}=net.layers(convlayer(i,:)).params(1).get_data();  
    b{i}=net.layers(convlayer(i,:)).params(2).get_data();  
end  

提取完畢以後觀察一下每一層的權重維度,發現也是四維,顯示一下前三個卷積核的維度:

size(w{1})

ans =

    11    11     3    96

>> size(w{2})

ans =

     5     5    48   256

>> size(w{3})

ans =

     3     3   256   384

前兩個維度不說了,卷積核的大小,第三個維度代表卷積核的左邊,也就是上一層的特徵圖的個數(對應前面說的每一個通道對應不同卷積核),第四個維度代表每一個通道對應的卷積核個數(也就是卷積核右邊下一層的特徵圖的個數)。

部分視覺化方法

那麼我們視覺化也是可選的,需要選擇哪一個特徵圖對應的卷積核,視覺化方法如下:

function [  ] = weight_partvisual( net,layer_num ,channels_num )  
layers=net.layer_names;  
convlayer=[];  
for i=1:length(layers)  
    if strcmp(layers{i}(1:3),'con')  
        convlayer=[convlayer;layers{i}];  
    end  
end  
w=net.layers(convlayer(layer_num,:)).params(1).get_data();  
b=net.layers(convlayer(layer_num,:)).params(2).get_data();  
w=w-min(min(min(min(w))));  
w=w/max(max(max(max(w))))*255;  

weight=w(:,:,channels_num,:);%四維,核長*核寬*核左邊輸入*核右邊輸出(核個數)  
[kernel_r,kernel_c,input_num,kernel_num]=size(w);  
map_row=ceil(sqrt(kernel_num));%行數  
map_col=map_row;%列數  
weight_map=zeros(kernel_r*map_row,kernel_c*map_col);  
kernelcout_map=1;  
for i=0:map_row-1  
    for j=0:map_col-1  
        if kernelcout_map<=kernel_num  
            weight_map(i*kernel_r+1+i:(i+1)*kernel_r+i,j*kernel_c+1+j:(j+1)*kernel_c+j)=weight(:,:,:,kernelcout_map);  
            kernelcout_map=kernelcout_map+1;  
        end  
    end  
end  
figure  
imshow(uint8(weight_map))  
str1=strcat('weight num:',num2str(kernelcout_map-1));  
title(str1)  

end  

呼叫方法

layer_num=1;%想看哪一個卷積核對應的權重☆☆☆☆☆☆☆☆☆☆  
channels_num=1;%想看第幾個通道對應的卷積核  
weight_partvisual( net,layer_num ,channels_num ) 

看看效果:
第一個卷積層的卷積核:
第二個卷積層的卷積核:

5.3、全部視覺化

將指定卷積層對應的每一個特徵圖的全部卷積核畫出

function [  ] = weight_fullvisual( net,layer_num  )  
layers=net.layer_names;  
convlayer=[];  
for i=1:length(layers)  
    if strcmp(layers{i}(1:3),'con')  
        convlayer=[convlayer;layers{i}];  
    end  
end  

weight=net.layers(convlayer(layer_num,:)).params(1).get_data();%四維,核長*核寬*核左邊輸入*核右邊輸出(核個數)  
b=net.layers(convlayer(layer_num,:)).params(2).get_data();  
weight=weight-min(min(min(min(weight))));  
weight=weight/max(max(max(max(weight))))*255;  

[kernel_r,kernel_c,input_num,kernel_num]=size(weight);  
map_row=input_num;%行數  
map_col=kernel_num;%列數  
weight_map=zeros(kernel_r*map_row,kernel_c*map_col);  
for i=0:map_row-1  
    for j=0:map_col-1  
        weight_map(i*kernel_r+1+i:(i+1)*kernel_r+i,j*kernel_c+1+j:(j+1)*kernel_c+j)=weight(:,:,i+1,j+1);  
    end  
end  
figure  
imshow(uint8(weight_map))  
str1=strcat('weight num:',num2str(map_row*map_col));  
title(str1)  

end  

第一層:
第二層

全連線討論

①首先提取出pool5的特徵圖大小

>>A=net.blobs('pool5').get_data();  
>>size(A)  
ans =

     3     3   256    10

可以發現對於每一個輸入圖片(總共十張)都有256個3*3大小的特徵圖。預先計算一下256*3*3=2304
②然後提取出fc6的特徵圖大小

>>C=net.blobs('fc6').get_data();  
>>size(C)  
ans =

        4096          10

然後發現pool5到fc6的連線並不是簡單的拉成一維向量,而是利用了一個2304*4096的權重去將pool5的特徵對映到fc6的單元中
③驗證一下是否如所想的對映方法, 只需要看看pool5到fc6的權重大小即可

>>D=net.layers('fc6').params(1).get_data();  
>> size(D) 
ans =

        2304        4096

發現果真如此,所以池化層到全連線層的確是用了一次對映而非簡單的拉成向量

嗯啊,初步成功~~