caffe可视化(权重、特征图和loss曲线)

由于要用到matlab接口来读取网络,故在开始介绍caffe可视化前,先看一下D:\caffe\caffe-master\matlab\+caffe\Net.m文件里定义的加载网络等函数,Net.m文件大家可以自行阅读,以下就摘抄几个我们要用到的函数:

(1)Net(varargin)函数

   function self = Net(varargin)
      % decide whether to construct a net from model_file or handle
      if ~(nargin == 1 && isstruct(varargin{1}))
        % construct a net from model_file
        self = caffe.get_net(varargin{:});
        return
      end
      % construct a net from handle
      hNet_net = varargin{1};
      CHECK(is_valid_handle(hNet_net), 'invalid Net handle');
      
      % setup self handle and attributes
      self.hNet_self = hNet_net;
      self.attributes = caffe_('net_get_attr', self.hNet_self);
      
      % setup layer_vec
      self.layer_vec = caffe.Layer.empty();
      for n = 1:length(self.attributes.hLayer_layers)
        self.layer_vec(n) = caffe.Layer(self.attributes.hLayer_layers(n));
      end
      
      % setup blob_vec
      self.blob_vec = caffe.Blob.empty();
      for n = 1:length(self.attributes.hBlob_blobs);
        self.blob_vec(n) = caffe.Blob(self.attributes.hBlob_blobs(n));
      end
      
      % setup input and output blob and their names
      % note: add 1 to indices as matlab is 1-indexed while C++ is 0-indexed
      self.inputs = ...
        self.attributes.blob_names(self.attributes.input_blob_indices + 1);
      self.outputs = ...
        self.attributes.blob_names(self.attributes.output_blob_indices + 1);
      
      % create map objects to map from name to layers and blobs
      self.name2layer_index = containers.Map(self.attributes.layer_names, ...
        1:length(self.attributes.layer_names));
      self.name2blob_index = containers.Map(self.attributes.blob_names, ...
        1:length(self.attributes.blob_names));
      
      % expose layer_names and blob_names for public read access
      self.layer_names = self.attributes.layer_names;
      self.blob_names = self.attributes.blob_names;
            
      % expose bottom_id_vecs and top_id_vecs for public read access
      self.attributes.bottom_id_vecs = cellfun(@(x) x+1, self.attributes.bottom_id_vecs, 'UniformOutput', false);
      self.bottom_id_vecs = self.attributes.bottom_id_vecs;
      self.attributes.top_id_vecs = cellfun(@(x) x+1, self.attributes.top_id_vecs, 'UniformOutput', false);
      self.top_id_vecs = self.attributes.top_id_vecs;
   end

该函数实现加载网络模型和网络权重信息,其中的输入参数varargin需要有:网络模型文件(如deploy.prototxt/train.prototxt等)、网络权重文件(训练时可忽略)和网络模式(TRAIN/TEST)。

(2)layers(self, layer_name)函数

   function layer = layers(self, layer_name)
      CHECK(ischar(layer_name), 'layer_name must be a string');
      layer = self.layer_vec(self.name2layer_index(layer_name));
   end

其中的参数self代表Net,即如果实例化了一个网络,例如net = caffe.Net(...),net就可以调用这些带有self的函数,如net.layers(...)。

参数layer_name是该函数真正的输入参数,代表网络某一层的名字,即网络模型文件中的name关键字后的参数,如下面某一层中的name: "conv1",则要获取该层,即可用net.layers('conv1')来实现:

layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  convolution_param {
    num_output: 96
    kernel_size: 11
    stride: 4
  }
}

matlab接口获取到的layers,里面包含params信息,即该层的权重和偏置信息,如下图:

我们可以通过以下代码来获取该层的权重/偏置数据:

data=net.layers('conv1').params(1).get_data(); %权重
data=net.layers('conv1').params(2).get_data(); %偏置

(3)blobs(self, blob_name)函数

   function blob = blobs(self, blob_name)
      CHECK(ischar(blob_name), 'blob_name must be a string');
      blob = self.blob_vec(self.name2blob_index(blob_name));
   end

该函数实现根据blob名字(blob_name)来获取相应的blob(blob中存储着输入输出数据),例如:

layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  convolution_param {
    num_output: 96
    kernel_size: 11
    stride: 4
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "conv1"
  top: "conv1"
}

通过调用net.blobs('data').get_data()可以获取conv1层的输入,通过net.blobs('conv1').get_data()可以获取relu1层的输出(也即通过conv1层(包含激活函数)后的特征图数据,可用于可视化特征图)。

注:这里无法获取经过激活函数ReLU之前的conv1层的输出数据,因为conv1层和relu1层采用的同一个名字的输出blob(同一名字默认是同一个blob,所以经过ReLU后会覆盖conv1层产生的输出blob;ReLU这种输入输出同名的方式也叫in-place,可以节省内参/显存)

(4)params(self, layer_name, blob_index)函数

   function blob = params(self, layer_name, blob_index)
      CHECK(ischar(layer_name), 'layer_name must be a string');
      CHECK(isscalar(blob_index), 'blob_index must be a scalar');
      blob = self.layer_vec(self.name2layer_index(layer_name)).params(blob_index);
   end

该函数也能实现获取某一层的权重和偏置数据,只是需要两个输入参数layer_name和blob_index,其中blob_index = 1表示权重,blob_index = 2表示偏置。例如net.params('conv1',1).get_data()可以获取conv1层的权重。

(5)forward(self, input_data)函数

   function res = forward(self, input_data)
      CHECK(iscell(input_data), 'input_data must be a cell array');
      CHECK(length(input_data) == length(self.inputs), ...
        'input data cell length must match input blob number');
      % copy data to input blobs
      for n = 1:length(self.inputs)
        if isempty(input_data{n})
          continue;
        end
        self.blobs(self.inputs{n}).set_data(input_data{n});
      end
      self.forward_prefilled();
      % retrieve data from output blobs
      res = cell(length(self.outputs), 1);
      for n = 1:length(self.outputs)
        res{n} = self.blobs(self.outputs{n}).get_data();
      end
   end

该函数用于实现整个网络的前向传播,只需要输入input_data(即data层的输入数据即可);该函数输出的是输出层的blob数据。

注:这里的input_data需要是cell array格式(即元胞数组)。

1.可视化权重

网络的权重数据可以通过matlab接口或python接口(python接口可以参见我写的另一篇博客)来读取,以下网络权重可视化过程采用matlab接口来实现。

主函数代码如下:

clear;
clc;
close all;
addpath('D:\caffe\caffe-master\Build\x64\Release\matcaffe'); %matcaffe路径(windows下,如果为linux,则修改为根目录下的matlab文件夹路径)
caffe.set_mode_cpu(); 
%以test模式加载网络和权重
net=caffe.Net('D:\caffe\caffe-master\models\bvlc_reference_caffenet\deploy.prototxt',...
    'D:\caffe\caffe-master\models\bvlc_reference_caffenet\bvlc_reference_caffenet.caffemodel','test');
%可视化conv1的权重
conv=net.params('conv1',1);  %1为权重,2为偏置(按名字索引)
blob=conv.get_data(); %得到权重数据
visualize(blob,1); %调用可视化函数

其中的visualize()函数如下:

%参数w:权重信息
%参数s:黑边大小
function [] = visualize(w,s)
width = size(w,1);
height = size(w,2);
gw = width+s;  %拓展宽,以便可视化更美观(其实就是每张图像之间添加黑边隔开而已)
gh = height+s;
%归一化
w = w - min(min(min(min(w))));
w = w / max(max(max(max(w)))) * 255;
w = uint8(w);

W=zeros(gh * size(w,3),gw * size(w,4));
for u = 1:size(w,3)
    for v = 1:size(w,4)
        %caffe中的matlab接口的blob是按(width,height,channel,num)顺序存储的
        W(gh * (u - 1) + (1:height), gw * (v - 1) + (1:width)) = w(:,:,u,v)'; %需要转置为正常图像的位置索引
    end
end
W=uint8(W);
figure;
imshow(W);
end

拿conv1层举例,其权重可视化结果如下(由于conv1层有96组卷积核(每组有3个卷积核),故图中的行数为3行,列数为96列,详见models\bvlc_reference_caffenet\deploy.prototxt文件):

2.可视化特征图

主函数代码如下:

clear;
clc;
close all;
addpath('D:\caffe\caffe-master\Build\x64\Release\matcaffe'); %matcaffe路径(windows下,如果为linux,则修改为根目录下的matlab文件夹路径)
caffe.set_mode_cpu(); 
%以test模式加载网络和权重
net=caffe.Net('D:\caffe\caffe-master\models\bvlc_reference_caffenet\deploy.prototxt',...
    'D:\caffe\caffe-master\models\bvlc_reference_caffenet\bvlc_reference_caffenet.caffemodel','test');
image=imread('D:\caffe\caffe-master\examples\images\cat.jpg'); %加载输入图像
figure;
imshow(image);
title('原始图像');

load('D:\caffe\caffe-master\matlab\+caffe\imagenet\ilsvrc_2012_mean.mat'); %加载均值文件mean_data(均值文件大小为256*256*3)
%%
%以下将输入的图像格式改为matcaffe的输入格式,即(width,height,channel,num)
%先进性RGB转为BGR(适合opencv的格式)
image=image(:,:,[3,2,1]);
%再将width和heigth互换
image=permute(image,[2,1,3]);
image=single(image); %转化为单精度,以便减去有小数的均值
%将图片缩放到均值文件的大小
image=imresize(image,[size(mean_data,1),size(mean_data,2)],'bilinear'); %采用双线性插值
image=image-mean_data; %减去均值
image=imresize(image,[227,227],'bilinear'); %网络输入为10*3*227*227(详见deploy.prototxt)
%由于每次输入网络的num=10,故复制9遍image当做最终的输入(在可视化的时候只取num中第一维,其余9维均一样)
kimage=cat(4,image,image,image,image,image); %按照num这一维度合并
pimage=cat(4,kimage,kimage);
input={pimage}; %matcaffe中的forward函数的输入为cell array(元胞数组),故转化为cell array

%%
%前向传播
score=net.forward(input);

%%
%获取最终的识别结果
score=score{1,1}; %提取出元胞数组中是数据为一般的矩阵
score=mean(score,2); %由于前面输入的num=10是10张一模一样的图,故score的大小为1000*10,且每列都是一样的,故直接按列求均值
[~,maxlabel]=max(score); %得到的maxlabel=282,表示是猫

%%
%以下可视化特征图
data=net.blobs('conv1').get_data();
visualize_feature(data,1);

注:主函数中的去均值操作和是否或者输入数据,都需要根据自己的需求进行修改(即输入数据部分的代码需要自行修改);获取最终识别结果的代码可有可无,也需要根据自己的需求进行修改。

其中的visualize_feature()函数如下:

%参数w:权重信息
%参数s:黑边大小
function visualize_feature(w,s)
width = size(w,1);
height = size(w,2);
gw = width+s;  %拓展宽,以便可视化更美观(其实就是每张图像之间添加黑边隔开而已)
gh = height+s;

channel=size(w,3);
cq=ceil(sqrt(channel));
W=zeros(gh*cq,gw*cq);
for u=1:cq
    for v=1:cq
        temp=zeros(height,width);
        if(((u-1)*cq+v)<=channel)
            temp=w(:,:,(u-1)*cq+v,1)';
            temp=temp-min(min(temp));
            temp=temp/max(max(temp))*255;
        end
        W(gh*(u-1)+(1:height),gw*(v-1)+(1:width))=temp;
    end   
end
W=uint8(W);
figure;
imshow(W);
end

可视化结果举例如下(输入数据是examples\images\cat.jpg;可视化输出数据conv1 blob,即relu1层的输出特征图):

3.可视化loss曲线

caffe无法直接实时绘制loss曲线(需要进一步用python接口或matlab接口编程实现实时绘制),但可以在训练完后根据保存的日志文件来绘制loss曲线。这里只介绍后者,实时绘制待以后有时间了研究下。

caffe的tools\extra文件夹里有自带的绘制一系列曲线的文件plot_training_log.py.example,但该工具只能一幅图像上绘制一条曲线,无法将训练和测试过程中的两条loss曲线绘制在同一幅图上,这样就导致无法很直观判断训练过程是否出现过拟合和欠拟合,所以这次介绍利用matlab接口来在同一幅图像上绘制两条loss曲线。

由于这段时间放假回家了,但自己的笔记本上没装linux系统,所以采用windows下的caffe版本进行讲解,当然会嵌入linux下的代码(只是这些代码没有经过测试,大家要是发现有错误或运行报错的话,及时告知哈,等回去了,我再用实验室的机子测试一下)。

这里拿caffe中的mnist例子进行说明如何可视化loss曲线,首先需要执行训练过程来得到训练日志,大家根据自己编译的caffe位置进行修改下面执行训练代码中的路径(mnist数据集下载,已经转为lmdb格式):

d:
cd ./caffe/caffe-master
D:\caffe\caffe-master\Build\x64\Release\caffe.exe train --solver=./examples/mnist/lenet_solver.prototxt >mnist.log 2>&1 &

如下图:

代码中的>是windows下的重定向(是1>的简略版,功能是一样的),2>&1表示将标准错误重定向到标准输出,最后的&表示训练过程在后台执行,所以如上图所示是不会显示训练过程中的各种输出的。

将任务放入后台有如下好处:

(1)有意或无意关闭终端时,不会停止训练(对linux而言是这样的,但windows下仍旧会停止训练)

(2)可以用一个终端干更多的事(windows仍旧需要等训练结束了,才能接着输入其他命令)

(3)可以在其他终端(远程)查看训练进度(实时查看),而不必要回到当前的终端

上面的第三条优点的实现需要在另外的终端输入以下代码查看训练过程(windows下和linux下一样的命令):

tail -f mnist.log

如下图所示(图上的训练内容是实时显示的):

如果显示tail不是内部或外部命令,也不是可运行的程序或批处理文件。则需要下载tail.exe,然后解压将里面的tail.exe放在C:\Windows\System32下即可。

如果是linux的话,在cd到相应目录下后,只需要输入以下代码:

./build/tools/caffe train --solver=./examples/mnist/lenet_solver.prototxt 2>&1 mnist.log &

注:日志文件保存在当前终端上显示的目录,比如上图中的D:\caffe\caffe-master下。

在得到训练日志文件后,我们利用matlab来实现loss曲线的可视化,代码如下:

clear;
clc;
close all;
trainlog = 'D:\caffe\caffe-master\mnist.log'; %加载日志文件
train_interval = 100; %对应于solver.prototxt中的display
test_interval = 500; %对应于solver.prototxt中的test_interval
%%
%提取loss数据
%先提取训练过程的loss
[~,soutput] = dos(['type ',trainlog,' | grep ','"Train net output #0"',' | awk ','"{print $11}"']);

train_loss = str2num(soutput); %字符串转化为数字
nn = 1:length(train_loss);
train_index = (nn - 1) * train_interval;
%再提取测试过程的loss
[~,soutput] = dos(['type ',trainlog,' | grep ','"Test net output #1"',' | awk ','"{print $11}"']);
test_loss = str2num(soutput);
mm = 1:length(test_loss);
test_index = (mm - 1) * test_interval;

%%
%绘制loss曲线
figure;
plot(train_index,train_loss); %绘制训练过程随着迭代次数的loss曲线
hold on
plot(test_index,test_loss); %绘制测试过程随迭代次数的loss曲线

grid on
xlabel('迭代次数');
ylabel('loss');
title('loss曲线');
legend('训练loss','测试loss');

代码中的dos()是matlab调用dos命令的函数,里面的['type ',trainlog,' | grep ','"Test net output #1"',' | awk ','"{print $11}"']是字符串拼接操作,注意在windows下使用awk的命令为awk "{print $11}",不是linux下的awk '{print $11}',这个区别一定要注意(血一样的教训,调这个不是bug的bug花了好长时间)。

如果出现awk或grep不是内部或外部命令,也不是可运行的程序或批处理文件。请下载awk,解压后将bin文件夹下的awk.exe放入C:\Windows\System32目录下;请下载grep,并按照,按照路径随意(比如我是默认安装的,即C:\Program Files (x86)\GnuWin32路径),安装完毕后,将C:\Program Files (x86)\GnuWin32\bin路径(修改自己的对应路径)加入系统的path,如下图所示:

然后重启电脑即可。

当然在linux下无需安装上述软件,可以直接运行,但需要修改一下下面两句代码:

(1)

[~,soutput] = dos(['type ',trainlog,' | grep ','"Train net output #0"',' | awk ','"{print $11}"']);

改为

[~,soutput] = dos(['cat ',trainlog,' | grep ','"Train net output #0"',' | awk ',''{print $11}'']);

(2)

[~,soutput] = dos(['type ',trainlog,' | grep ','"Test net output #1"',' | awk ','"{print $11}"']);

改为

[~,soutput] = dos(['cat ',trainlog,' | grep ','"Test net output #1"',' | awk ',''{print $11}'']);

其中的$11中的数字11是由于loss= 2.3165中的2.3165(我们要提取的数据)是该行的第11个字符串(字符串间用空格隔开),举个例子如下(大家可以自行数一下2.3165在那行的第几个字符串处):

I0811 20:57:26.300038  8380 solver.cpp:404]     Test net output #0: accuracy = 0.1116
I0811 20:57:26.300038  8380 solver.cpp:404]     Test net output #1: loss = 2.35696 (* 1 = 2.35696 loss)
I0811 20:57:26.300038  8380 solver.cpp:228] Iteration 0, loss = 2.3165
I0811 20:57:26.300038  8380 solver.cpp:244]     Train net output #0: loss = 2.3165 (* 1 = 2.3165 loss)

执行matlab,结果如下图:

注:如需转载,请注明本博客链接。

猜你喜欢

转载自blog.csdn.net/qq_21368481/article/details/81592494
今日推荐