转载请注明出处: http://xiahouzuoxin.github.io/notes/
说好的要笔耕不缀,这开始一边实习一边找工作,还摊上了自己的一点私事困扰,这几个月的东西都没来得及总结一下。这就来记录一下关于CNN、Caffe、Image Sematic Segmentation相关的工作,由于公司技术保密的问题,很多东西没办法和大家详说只能抱歉了。在5月份前,我也是一个DL和CNN的门外汉,自己试着看tutorials、papers、搭Caffe平台、测试CNN Net,现在至少也能改改Caffe源码(Add/Modify Layer)、基于Caffe写个Demo。这里希望把学习的过程分享给那些在门口徘徊的朋友。没法事无巨细,但希望能起到提点的作用!“乍可刺你眼,不可隐我脚”。
一句简单的话描述:“深度学习就是多层的神经网络”。神经网络几十年前就有了,而且证明了“2层(1个隐层)的神经网络可以逼近任意的非线性映射”,意思就是说,只要我的参数能训练好,2层神经网络就能完成任意的分类问题(分类问题就是将不同类通过非线性映射划分到不同的子空间)。但2层神经网络存在的问题是:
如果要逼近非常非常复杂的非线性映射,网络的权值W就会很多,造成Train时候容易出现的问题就是Overfitting。所以大事化小,将复杂问题进行分割,用多层网络来逼近负责的非线性映射,这样每层的参数就少了。自然而然的网络就从2层变成了多层,浅网络(shallow)就变成了深网络(deep)。
但科研界的大牛们会这么傻吗,十几年前会想不到用多层网络来进行非线性映射?看看CNN最早的工作: http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf 那是98年的,Train了一个5层的CNN来进行MINIST数据集的数字图片分类。多层神经网络一直不火我觉得有这么两个原因:
DL只是一个概念而已。对于做图像和视觉的就该一头扎到CNN(Convolutional Neural Netwok),做自然语言的就该投入到RNN(Recurrent Neural Network)。我是做图像的。CNN的学习资料除了上面Ng的tutorial外,还有一个Stanford Li Fei-Fei教授的课程cs231:Convolutional Neural Networks for Visual Recognition,http://cs231n.github.io/convolutional-networks/ 是Notes中一份关于CNN非常详细的资料。
先看看这个热个身:贾扬清:希望Caffe成为深度学习领域的Hadoop,增加点学习的欲望,毕竟现在多少人靠着Hadoop那玩意儿挣着大钱。
接着请认准Caffe官方文档: http://caffe.berkeleyvision.org/ 和Github源码: https://github.com/BVLC/caffe 。毫不犹豫fork一份到自己的Github。然后就是照着INSTALL来Complie和Config Caffe了,值得注意的是,安装OpenCV的时候推荐使用源码安装。
先自己熟悉Caffe的架构,主要参考资料就是官网文档,我自己刚开始的时候也写了个小的ppt笔记:Diving into Caffe.pptx
还有两份不错的Caffe tutorials:
接着就是要自己动手,实打实地分析一个CNN,比如LeNet、AlexNet,自己在纸上画一画,像下面那样
LeNet
AlexNet
搞明白下面这些玩意的作用,没见过的就google一个个的查:
搞明白SGD中weight的更新方法,可以参考Caffe网站上tutorial的Solver部分: http://caffe.berkeleyvision.org/tutorial/solver.html 。
好了,现在试着去跑跑${CAFFE_ROOT}/example
下LeNet的example吧。
当去搜索ICRL、CVPR、ICCV这些最前沿的计算机视觉、机器学习会议的时候,只要是涉及图像相关的深度学习实验,大都是基于Caffe来做的。所以,只要抓住1~2篇popular的paper深入,把论文中的CNN在Caffe上复现了,就能找到一些感觉了。在这期间,下面一些经典论文是至少要读一读的:
具体到用CNN做Sematic Segmentation,利用到全卷积网络,对下面两篇进行了精读,并且都Caffe上复现过并用于分割任务,
下面是几个月前我看过这两篇paper后做得ppt:
这两篇paper有一个共同点,都用到了multiscale的信息(也就是将High Layer的输出与Low Layer的输出进行结合),这对促进分割效果有很大的作用。值得一提的是,在Train multiscale的model时,由于存在反卷积或上采样的操作,Layer相对复杂,很难一大锅扔进去还Trian得很好,所以通常会先Train一个无multiscale的model,再用该model去finetune含multiscale的Net。
src/caffe/layers/conv_layer.cpp
。Caffe里的卷积计算:
Caffe里的卷积操作 Caffe计算卷积时先将图片中用于卷积的每个patch拉成一个行向量,若stride=1,则卷积的patch个数刚好是W*H,则将原图片保存成一个[WxH,KxKxD]矩阵,同理,滤波器也存成一个[M,KxKxD]矩阵,卷积计算只要计算这两个矩阵的乘积,矩阵乘积的工作刚好可以交给BLAS来进行优化,也就是为了用BLAS优化而进行这种转化,从中可以看出,Caffe里的卷积还是挺耗内存空间的!
Layer Type
和layerParameter
,并在caffe.proto中声明该message LayerParameter(protobuf里的message类似C里的struct)上面仅仅是我随手写在草稿纸上的随笔,其实自己看看源码,依葫芦画瓢写一个简单的Layer就知道了。
Caffe提供了好用的接口,包括matlab、C++、Python!由于特殊原因,我不能公开我C++和matlab的Demo源码以及其中的一些后处理技术,暂且只能给大家看一些分割的结果
Sematic Segmentation Result
还有一个视频语义分割的结果,大家看看,热闹热闹就好,
我一开始以为看看各层Layer的输出,能帮助我改进Net,可却发现错了,除了前几层还能看出点明亮或边缘信息外,网络后端Layer的输出压根就没办法理解。extract_featmat.cpp是我基于extract_features.cpp改的一个Caffe tool,放到tools目录下编译就好了,使用方法看help:
void print_help(void) {
LOG(ERROR)<<
"This program takes in a trained network and an input image, and then\n"
" extract features of the input data produced by the net.\n"
"Usage: extract_featmat [options]\n"
" -model [pretrained_net_param]\n"
" -proto [feature_extraction_proto_file]\n"
" -img [rgb_image_name]\n"
" -blobs [extract_feature_blob_name1[,name2,...]],refrence .prototxt with"
" \"blob:\". [all] for all layers. \n"
" -wr_prefix [output_feat_prefix1[,prefix2,...]], conrespond to -blobs\n"
" -wr_root [output_feat_dir], optional, default \"./\", make sure it exist\n"
" -gpu [gpu_id],optional,if not specified,default CPU\n";
}
下面图是一些Layer的输出blob,从结果可以看出,前面的layer还能看到一些边缘信息,后面的layer就完全看不出原图像相关的信息了,
不同Layers的Feature变化
虽然还是个新手,关于搭建CNN,还在慢慢在找感觉。我觉得从两方面:
GoogleNet
VGGNet VGG不是Weight Filter不是非常厚么,卷积操作复杂度就高。而Googlenet通过Inception中1x1的Convolution刚好是为了减少Weight Filter的厚度,我最近一段时间在尝试做的事就是将VGG中的Layer用Googlenet中的Inception的方式去替代,希望至少在时间上有所改进。
Train一个Net的经验也很重要,还是那句话,“数据是燃料”,CNN训练一定要保证足够的数据量(就我现在知道,Train一个深层的全卷积至少要4万张图片以上),否则很容易出现过拟合或者说泛化能力特别差,就像下面那样。下面分别是DatasetSize=4K与DatasetSize=40K同一Net Train出来的Prediction结果。 在训练时,仅从精度上来看,两个Net训练时得到的差距不大,IoU都在90%左右,但实际predict时,4K train出的model是如此的难看!
论Train CNN数据集大小的重要性
function ImageScale(in_dir,out_dir,SIZE)
% Author: zuoxin,xiahou
% SIZE: size when scale ratio=1.0
if ~exist(out_dir,'dir')
mkdir(out_dir);
end
ims = dir(fullfile(in_dir,'*.jpg'));
gts = dir(fullfile(in_dir,'*.bmp'));
N = length(ims);
if N ~= length(gts)
error('NUM(Images)~=NUM(GroudTruth)');
end
for i=1:N
im = imread(fullfile(in_dir,ims(i).name));
gt = imread(fullfile(in_dir,gts(i).name));
% Check for size errors
if ndims(im) < 3
[h,w] = size(im);
k=1;
else
[h,w,k] = size(im);
end
[h_gt,w_gt,k_gt] = size(gt);
if h~=h_gt || w~=w_gt
error('size(im)~=size(gt)');
end
if h>w
ts = h;
else
ts = w;
end
% SIZE = 500;
if h>w
im = imresize(im, [SIZE,w*SIZE/ts]);
gt = imresize(gt, [SIZE,w*SIZE/ts]);
else
im = imresize(im, [h*SIZE/ts,SIZE]);
gt = imresize(gt, [h*SIZE/ts,SIZE]);
end
% Update Size
if ndims(im) < 3
[h,w] = size(im);
k=1;
else
[h,w,k] = size(im);
end
[~,~,k_gt] = size(gt);
if h>w % h>w, padding width (bg:0 fg:1)
pad_ = SIZE-w;
left_pad = floor(pad_/2);
right_pad = ceil(pad_/2);
scale_im = [255*ones(h,left_pad,k), im, 255*ones(h,right_pad,k)];
scale_gt = [255*zeros(h,left_pad,k_gt), gt, 255*zeros(h,right_pad,k_gt)];
else % h<w, padding height
pad_ = SIZE-h;
top_pad = floor(pad_/2);
bot_pad = ceil(pad_/2);
scale_im = [255*ones(top_pad,w,k); im; 255*ones(bot_pad,w,k)];
scale_gt = [255*zeros(top_pad,w,k_gt); gt; 255*zeros(bot_pad,w,k_gt)];
end
% Other Scales
ts = [0.6,1,1.4];
for s = 1:numel(ts)
im = imresize(scale_im,ts(s));
gt = imresize(scale_gt,ts(s));
% padding/crop to SIZExSIZE
if size(im,1) < SIZE % Padding
right_pad = SIZE - size(im,2);
bot_pad = SIZE - size(im,1);
im = [im, 255*ones(size(im,1),right_pad,k)];
im = [im; 255*ones(bot_pad,size(im,2),k)];
gt = [gt, 255*zeros(size(gt,1),right_pad,k_gt)];
gt = [gt; 255*zeros(bot_pad,size(gt,2),k_gt)];
else % Crop
im = im(1:SIZE,1:SIZE,:);
gt = gt(1:SIZE,1:SIZE,:);
end
gt(gt>=128) = 255;
gt(gt<128) = 0;
out_im = fullfile(out_dir,...
[ims(i).name(1:end-4),'_',num2str(ts(s)),'.jpg']);
out_gt = fullfile(out_dir,...
[ims(i).name(1:end-4),'_',num2str(ts(s)),'.bmp']);
imwrite(im,out_im);
imwrite(gt,out_gt);
end
end
end
下面是mirror图片的matlab脚本:
function image_mirror(d)
% mirror all images under @dir directory
% and store the mirrored images under @dir
files=dir(fullfile(d,'*.jpg'));
n=length(files);
for i=1:n
src_file=fullfile(d,files(i).name);
out_file=fullfile(d,[files(i).name(1:end-4),'_mr.jpg']);
if ~exist(out_file,'file') && ~(strcmp(src_file(end-5:end-4),'mr'))
im = imread(src_file);
out=im(:,end:-1:1,:);
% imshow(out);
imwrite(out, out_file);
fprintf('%s\n',fullfile(d,files(i).name));
end
end
对于非分割的问题,比如识别,我觉得在200x200 pixles左右就足够了(看大家都这么用的,这个自己没测试过)。但对于分割的问题,图片的size对分割结果影响还是很大的,用全卷积网络的测试结果:输入图片的size从500x500降低到300x300,IoU果断直接降了3个点,太恐怖了!!但size太大,卷积时间长,所以这就是一个精度和时间的折中问题了。
从CNN最后一层的输出结合前面一些浅层的输出,组成多尺度的信息,参考FCNN和Deeplab的上采样方式。
就如上面的人目标分割的问题,在标定的时候正常就是标定为2分类问题(前景:人,背景:其它)。现在换个角度思考,因为人脸和人体衣服的差异相对比较大,如果在标定的时候标定为同一类(前景),问题是简单了,但学习的时候,要把人脸和衣服框起来的model学鲁棒了感觉还是很困难。而如果将人脸和衣服在标定的时候就标定成不同的类,识别完了,考虑相对位置信息,再将这两类整合为同一类,效果是不是会更好呢?
呃,这一级还没练到,但迟早是要做的,说了“大数据是燃料,GPU是引擎”的,怎么能不懂引擎呢……