caffe for windows的matlab接口(四):权重和特征图可视化的一个例子

10 篇文章 0 订阅

模型读取

参照三,想实现一个自己图像的可视化过程:
首先我发现自己训练出的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

发现果真如此,所以池化层到全连接层的确是用了一次映射而非简单的拉成向量

嗯啊,初步成功~~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值