深度卷积网络CNN与图像语义分割
- 級別1:DL快速上手
- 級別2:從Caffe著手實踐
- 級別3:讀paper,網絡Train起來
- 級別4:Demo跑起來
- 讀一些源碼玩玩
- 熟悉Caffe接口,寫Demo這是硬功夫
- 分析各層Layer輸出特征
- 級別5:何不自己搭個CNN玩玩
- Train CNN時關于數據集的一些注意事項
- 級別6:加速吧,GPU編程
- 關于語義分割的一些其它工作
說好的要筆耕不綴,這開始一邊實習一邊找工作,還攤上了自己的一點私事困擾,這幾個月的東西都沒來得及總結一下。這就來記錄一下關于CNN、Caffe、Image Sematic Segmentation相關的工作,由于公司技術保密的問題,很多東西沒辦法和大家詳說只能抱歉了。在5月份前,我也是一個DL和CNN的門外漢,自己試著看tutorials、papers、搭Caffe平臺、測試CNN Net,現在至少也能改改Caffe源碼(Add/Modify Layer)、基于Caffe寫個Demo。這里希望把學習的過程分享給那些在門口徘徊的朋友。沒法事無巨細,但希望能起到提點的作用!“乍可刺你眼,不可隱我腳”。
級別1:DL快速上手
UFLDL:?http://deeplearning.stanford.edu/tutorial/
這是stanford Ng老師的教材,也剛好是以CNN為主,Ng老師教材的特色就是簡潔明白。一遍看不懂多看兩遍,直到爛熟于心,順便把里面的Matlab Exercises完成了。
http://deeplearning.net/tutorial/
PRML作者給的Python入門DL的tutorial,基于Theano Framework,有些偏向于RNN的東西。
一句簡單的話描述:“深度學習就是多層的神經網絡”。神經網絡幾十年前就有了,而且證明了“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非常詳細的資料。
級別2:從Caffe著手實踐
先看看這個熱個身:賈揚清:希望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吧。
級別3:讀paper,網絡Train起來
當去搜索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。
級別4:Demo跑起來
讀一些源碼玩玩
caffe.proto
Convolution Layer
CNN里面最重要的運算就是卷積,要知道Caffe是怎么做卷積的,就看看src/caffe/layers/conv_layer.cpp。Caffe里的卷積計算:
Caffe里的卷積操作
Caffe計算卷積時先將圖片中用于卷積的每個patch拉成一個行向量,若stride=1,則卷積的patch個數剛好是W*H,則將原圖片保存成一個[WxH,KxKxD]矩陣,同理,濾波器也存成一個[M,KxKxD]矩陣,卷積計算只要計算這兩個矩陣的乘積,矩陣乘積的工作剛好可以交給BLAS來進行優化,也就是為了用BLAS優化而進行這種轉化,從中可以看出,Caffe里的卷積還是挺耗內存空間的!
SoftmaxLossLayer
所謂的輸出的Loss計算使用的一般是Softmax或Sigmoid的交叉譜,具體可看這里SoftmaxWithLossLayer
DataLayer
- 在caffe.proto中的message LayerParameter部分增加Layer Type和layerParameter,并在caffe.proto中聲明該message LayerParameter(protobuf里的message類似C里的struct)
- 在include的某個頭文件中聲明LayerClass
- 在src/caffe/layers/中新建一個.cpp,實現LayerSetup、Shape、Forward_cpu以及Backward_cpu
- 為建立LayerClass與proto中Layer聲明的關聯,在上面的.cpp中還要使用INSTALL_CLASS(...)和REGISTER_LAYER_CLASS(...)
上面僅僅是我隨手寫在草稿紙上的隨筆,其實自己看看源碼,依葫蘆畫瓢寫一個簡單的Layer就知道了。
熟悉Caffe接口,寫Demo這是硬功夫
Caffe提供了好用的接口,包括matlab、C++、Python!由于特殊原因,我不能公開我C++和matlab的Demo源碼以及其中的一些后處理技術,暫且只能給大家看一些分割的結果:
Sematic Segmentation Result
還有一個視頻語義分割的結果,大家看看,熱鬧熱鬧就好,
height="498" width="510" src="http://xiahouzuoxin.github.io/notes/images/%E6%B7%B1%E5%BA%A6%E5%8D%B7%E7%A7%AF%E7%BD%91%E7%BB%9CCNN%E4%B8%8E%E5%9B%BE%E5%83%8F%E8%AF%AD%E4%B9%89%E5%88%86%E5%89%B2/TG.mp4" frameborder="0" allowfullscreen="" style="margin: 0px; padding: 0px; font-family: Consolas, helvetica, sans-serif, arial, freesans, clean; font-size: 13px; line-height: 20.7999992370605px;">分析各層Layer輸出特征
我一開始以為看看各層Layer的輸出,能幫助我改進Net,可卻發現錯了,除了前幾層還能看出點明亮或邊緣信息外,網絡后端Layer的輸出壓根就沒辦法理解。extract_featmat.cpp是我基于extract_features.cpp改的一個Caffe tool,放到tools目錄下編譯就好了,使用方法看help:
<code class="sourceCode cpp" style="margin: 0px; padding: 0.5em; font-stretch: normal; line-height: normal; font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; color: rgb(51, 51, 51); border: none; display: block; background: rgb(248, 248, 255);"> <span class="dt" style="margin: 0px; padding: 0px; color: rgb(144, 32, 0);">void</span> print_help(<span class="dt" style="margin: 0px; padding: 0px; color: rgb(144, 32, 0);">void</span>) {LOG(ERROR)<<<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"This program takes in a trained network and an input image, and then</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" extract features of the input data produced by the net.</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"Usage: extract_featmat [options]</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -model [pretrained_net_param]</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -proto [feature_extraction_proto_file]</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -img [rgb_image_name]</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -blobs [extract_feature_blob_name1[,name2,...]],refrence .prototxt with"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" </span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">blob:</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">. [all] for all layers. </span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -wr_prefix [output_feat_prefix1[,prefix2,...]], conrespond to -blobs</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -wr_root [output_feat_dir], optional, default </span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">./</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">, make sure it exist</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">" -gpu [gpu_id],optional,if not specified,default CPU</span><span class="ch" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">\n</span><span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">"</span>;}</code>下面圖是一些Layer的輸出blob,從結果可以看出,前面的layer還能看到一些邊緣信息,后面的layer就完全看不出原圖像相關的信息了,
不同Layers的Feature變化
級別5:何不自己搭個CNN玩玩
雖然還是個新手,關于搭建CNN,還在慢慢在找感覺。我覺得從兩方面:
利用已有的網絡,使勁渾身解數找它們的缺點,改進它們
熟讀Googlenet和VGGnet那兩篇paper,兩者的CNN結構如下:
GoogleNet
VGGNet
VGG不是Weight Filter不是非常厚么,卷積操作復雜度就高。而Googlenet通過Inception中1x1的Convolution剛好是為了減少Weight Filter的厚度,我最近一段時間在嘗試做的事就是將VGG中的Layer用Googlenet中的Inception的方式去替代,希望至少在時間上有所改進。
從頭搭建一個CNN用于解決實際問題。一個詞:搭積木。
先搭一個簡單的,比如說就3層:卷積-Pooling-卷積-Pooling-卷積-Pooling,先把這個簡單的網絡訓練以來,效果不好沒關系,我們接著往上加,直到滿意為止。不明白的看看上面的VGG Net,先在ImageNet上Train出一個A網絡,然后增加A的深度(粗體的部分)變成B,再用A去初始化B網絡,就逐級增加網絡深度。 這里面有一個finetune的技巧,那就是用淺層的網絡訓練weight結果去初始化或finetune深層網絡。這也是為什么不直接一開始就搭建深層網絡的原因,前面說過,深度網絡的Train是個非凸問題,是個至今難解決的大問題,網絡初始化對其收斂結果影響很大,finetune就這樣作為Deep Network中一項最重要的tricks而存在了。finetune除了由淺至深逐級初始化幫助收斂外,還有一個作用:將自己的網絡在一個非常非常大的數據集上(現在最大的ImageNet)進行Train,這個Train的結果再拿去作為實際要解決的問題中用于初始化,這樣能增加網絡的泛化能力。 關于更多的問題,現在也是盲人摸象,暫且擱下不提。
Train CNN時關于數據集的一些注意事項
Train一個Net的經驗也很重要,還是那句話,“數據是燃料”,CNN訓練一定要保證足夠的數據量(就我現在知道,Train一個深層的全卷積至少要4萬張圖片以上),否則很容易出現過擬合或者說泛化能力特別差,就像下面那樣。下面分別是DatasetSize=4K與DatasetSize=40K同一Net Train出來的Prediction結果。 在訓練時,僅從精度上來看,兩個Net訓練時得到的差距不大,IoU都在90%左右,但實際predict時,4K train出的model是如此的難看!
論Train CNN數據集大小的重要性
下面是mirror圖片的matlab腳本:
<code class="sourceCode matlab" style="margin: 0px; padding: 0.5em; font-stretch: normal; line-height: normal; font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; color: rgb(51, 51, 51); border: none; display: block; background: rgb(248, 248, 255);">function image_mirror(d) <span class="co" style="margin: 0px; padding: 0px; color: rgb(96, 160, 176); font-style: italic;">% mirror all images under @dir directory</span> <span class="co" style="margin: 0px; padding: 0px; color: rgb(96, 160, 176); font-style: italic;">% and store the mirrored images under @dir</span>files=dir(fullfile(d,<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">'*.jpg'</span>)); n=length(files); for i=<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">1</span>:nsrc_file=fullfile(d,files(i).name);out_file=fullfile(d,[files(i).name(<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">1</span>:end-<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">4</span>),<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">'_mr.jpg'</span>]);if ~exist(out_file,<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">'file'</span>) && ~(strcmp(src_file(end-<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">5</span>:end-<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">4</span>),<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">'mr'</span>))im = imread(src_file);out=im(:,end:-<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">1</span>:<span class="fl" style="float: left; margin: 0px; padding: 0px; color: rgb(64, 160, 112);">1</span>,:);<span class="co" style="margin: 0px; padding: 0px; color: rgb(96, 160, 176); font-style: italic;">% imshow(out);</span>imwrite(out, out_file);fprintf(<span class="st" style="margin: 0px; padding: 0px; color: rgb(64, 112, 160);">'%s\n'</span>,fullfile(d,files(i).name));end end</code>對于非分割的問題,比如識別,我覺得在200x200 pixles左右就足夠了(看大家都這么用的,這個自己沒測試過)。但對于分割的問題,圖片的size對分割結果影響還是很大的,用全卷積網絡的測試結果:輸入圖片的size從500x500降低到300x300,IoU果斷直接降了3個點,太恐怖了!!但size太大,卷積時間長,所以這就是一個精度和時間的折中問題了。
級別6:加速吧,GPU編程
呃,這一級還沒練到,但遲早是要做的,說了“大數據是燃料,GPU是引擎”的,怎么能不懂引擎呢……
關于語義分割的一些其它工作
注:由于“實習上班+實驗室+論文+刷leetcode+私事”占用時間的關系,好不容易抽出一個上午+一個晚上整理了一下,暫時想到這么多,算列個提綱吧,文章中不少具體細節有機會再補充。
總結
以上是生活随笔為你收集整理的深度卷积网络CNN与图像语义分割的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图像分割(Image Segmentat
- 下一篇: Twitter是如何做到每秒处理3000