图像检索:layer选择与fine-tuning性能提升验证
這個世界上肯定有另一個我,做著我不敢做的事,過著我想過的生活。一個人逛街,一個人吃飯,一個人旅行,一個人做很多事。極致的幸福,存在于孤獨的深海。在這樣日復一日的生活里,我逐漸和自己達成和解。
作為遷移學習的一種,finetune能夠將general的特征轉變為special的特征,從而使得轉移后的特征能夠更好的適應目標任務,而圖像檢索最根本的問題,仍在于如何在目標任務上獲得更好的特征表達(共性與可區分性)。一種很自然的方式便是在特定的檢索任務上,我們對imageNet學得的general的特征通過finetune的方式,使得表達的特征能夠更好的適應我們的檢索任務。在End-to-end Learning of Deep Visual Representations for Image Retrieval和?Collaborative Index Embedding for Image Retrieval中已經很清楚的指出,通過基本的classification loss的finetune的方式,能夠較大幅度的提高檢索的mAP。因此,在本篇博文中,小白菜針對檢索,主要整理了下面四個方面的內容:
- CNN網絡中哪一層最適合于做圖像檢索
- 基于pre-trained模型做圖像檢索幾種典型的特征表示方法
- 抽取網絡任意層的特征
- 數據增強(Data Augmentation)
- VGGNet16網絡模型fine-tuning實踐
在采用深度學習做檢索的時候,上面四方面的問題和知識基本都回避不了,因此,小白菜以為,掌握這四方面的內容顯得非常有必要。
特征表達layer選擇
在AlexNet和VGGNet提出伊始,對于檢索任務,小白菜相信,在使用pre-trained模型抽取特征的時候,我們最最自然想到的方式是抽取全連接層中的倒數第一層或者倒數第二層的特征,這里說的倒數第一層或者倒數第二層并沒有具體指明是哪一層(fcx、fcx_relux、fcx_dropx),以VggNet16網絡為例,全連接層包含兩層,fc6和fc7,因此我們很自然想到的網絡層有fc6、fc6_relu6、fc7、fc7_relu7甚至fc6_drop6和fc7_drop7(后面會說明fc6_drop6和fc6_relu6是一樣的,以及fc7_drop7和fc7_relu7也是一樣的),所以即便對于我們最最自然最最容易想到的方式,也面臨layer的選擇問題。為此,我們以VGGNet16網絡為例,來分析CNN網絡的語義層(全連接層)選擇不同層作為特征做object retrieval的mAP的影響。
小白菜選取fc6、fc6_relu6、fc7、fc7_relu7這四層語義層的特征,在Oxford Building上進行實驗,評價指標采用mAP,mAP的計算采用Oxford Building提供的計算mAP代碼compute_ap.cpp,下表是fc6、fc6_relu6、fc7、fc7_relu7對應的mAP。
| fc7_relu7 | 44.72% | 1.11% | 41.08% |
| fc7 | 45.03% | 19.67% | 41.18% |
| fc6_relu6 | 43.62% | 23.0% | 43.34% |
| fc6 | 45.9% | 19.47% | 44.78% |
從上表可以看到,直接采用pre-trained模型抽取語義層的特征,在Oxford Building上取得的結果在45%左右,同時我們還可以看出,選取fc6、fc6_relu6、fc7、fc7_relu7對結果的影響并不大。這個結果只能說非常的一般,在基于pre-trained模型做object retrieval的方法中,比如Cross-dimensional Weighting for Aggregated Deep Convolutional Features、Particular object retrieval with integral max-pooling of CNN activations以及What Is the Best Practice for CNNs Applied to Visual Instance Retrieval?指出,選用上層的語義層其實是不利于object retrieval,因為上層的語義層丟失了object的空間信息,并且從實驗的角度說明了選取中間層的特征更利于object retrieval。
實際上,在選取中間層來表達特征的過程中,我們可以去掉全連接層,從而使得我們可以擺脫掉輸入圖像尺寸約束(比如224*224)的約束,而保持原圖大小的輸入。通常,圖像分辨率越大,對于分類、檢測等圖像任務是越有利的。因而,從這一方面講,選取上層的全連接層作為特征,并不利于我們的object retrieval任務。一種可能的猜想是,上層全連接層的語義特征,應該更適合做全局的相似。
雖然中間層更適合于做object retrieval,但是在選用中間層的feature map作為raw feature的時候,我們面臨的一個主要問題是:如何將3d的tensor轉成一個有效的向量特征表示?下面小白菜主要針對這一主要問題總結幾種典型的特征表示方法,以及對中間層特征選擇做一些探討與實驗。
基于pre-trained模型做Object Retrieval幾種典型的特征表示
SUM pooling
基于SUM pooling的中層特征表示方法,指的是針對中間層的任意一個channel(比如VGGNet16, pool5有512個channel),將該channel的feature map的所有像素值求和,這樣每一個channel得到一個實數值,N個channel最終會得到一個長度為N的向量,該向量即為SUM pooling的結果。
AVE pooling
AVE pooling就是average pooling,本質上它跟SUM pooling是一樣的,只不過是將像素值求和后還除以了feature map的尺寸。小白菜以為,AVE pooling可以帶來一定意義上的平滑,可以減小圖像尺寸變化的干擾。設想一張224224的圖像,將其resize到448448后,分別采用SUM pooling和AVE pooling對這兩張圖像提取特征,我們猜測的結果是,SUM pooling計算出來的余弦相似度相比于AVE pooling算出來的應該更小,也就是AVE pooling應該稍微優于SUM pooling一些。
MAX pooling
MAX pooling指的是對于每一個channel(假設有N個channel),將該channel的feature map的像素值選取其中最大值作為該channel的代表,從而得到一個N維向量表示。小白菜在flask-keras-cnn-image-retrieval中采用的正是MAX pooling的方式。
from?Day 2 Lecture 6 Content-based Image Retrieval
上面所總結的SUM pooling、AVE pooling以及MAX pooling,這三種pooling方式,在小白菜做過的實驗中,MAX pooling要稍微優于SUM pooling、AVE pooling。不過這三種方式的pooling對于object retrieval的提升仍然有限。
MOP pooling
MOP Pooling源自Multi-scale Orderless Pooling of Deep Convolutional Activation Features這篇文章,一作是Yunchao Gong,此前在搞哈希的時候,讀過他的一些論文,其中比較都代表性的論文是ITQ,小白菜還專門寫過一篇筆記論文閱讀:Iterative Quantization迭代量化。MOP pooling的基本思想是多尺度與VLAD(VLAD原理可以參考小白菜之前寫的博文圖像檢索:BoF、VLAD、FV三劍客),其具體的pooling步驟如下:
from?Multi-scale Orderless Pooling of Deep Convolutional Activation Features
Overview of multi-scale orderless pooling for CNN activations (MOP-CNN). Our proposed feature is a concatenation of the feature vectors from three levels: (a)Level 1, corresponding to the 4096-dimensional CNN activation for the entire 256256image; (b) Level 2, formed by extracting activations from 128128 patches and VLADpooling them with a codebook of 100 centers; (c) Level 3, formed in the same way aslevel 2 but with 64*64 patches.
具體地,在L=1的尺度下,也就是全圖,直接resize到256256的大小,然后送進網絡,得到第七層全連接層4096維的特征;在L=2時,使用128128(步長為32)的窗口進行滑窗,由于網絡的圖像輸入最小尺寸是256256,所以作者將其上采樣到256256,這樣可以得到很多的局部特征,然后對其進行VLAD編碼,其中聚類中心設置為100,4096維的特征降到了500維,這樣便得到了50000維的特征,然后將這50000維的特征再降維得到4096維的特征;L=3的處理過程與L=2的處理過程一樣,只不過窗口的大小編程了64*64的大小。
作者通過實驗論證了MOP pooling這種方式得到的特征一定的不變性。基于這種MOP pooling小白菜并沒有做過具體的實驗,所以實驗效果只能參考論文本身了。
CROW pooling
對于Object Retrieval,在使用CNN提取特征的時候,我們所希望的是在有物體的區域進行特征提取,就像提取局部特征比如SIFT特征構BoW、VLAD、FV向量的時候,可以采用MSER、Saliency等手段將SIFT特征限制在有物體的區域。同樣基于這樣一種思路,在采用CNN做Object Retrieval的時候,我們有兩種方式來更細化Object Retrieval的特征:一種是先做物體檢測然后在檢測到的物體區域里面提取CNN特征;另一種方式是我們通過某種權重自適應的方式,加大有物體區域的權重,而減小非物體區域的權重。CROW pooling (?Cross-dimensional Weighting for Aggregated Deep Convolutional Features?)即是采用的后一種方法,通過構建Spatial權重和Channel權重,CROW pooling能夠在一定程度上加大感興趣區域的權重,降低非物體區域的權重。其具體的特征表示構建過程如下圖所示:
其核心的過程是Spatial Weight和Channel Weight兩個權重。Spatial Weight具體在計算的時候,是直接對每個channel的feature map求和相加,這個Spatial Weight其實可以理解為saliency map。我們知道,通過卷積濾波,響應強的地方一般都是物體的邊緣等,因而將多個通道相加求和后,那些非零且響應大的區域,也一般都是物體所在的區域,因而我們可以將它作為feature map的權重。Channel Weight借用了IDF權重的思想,即對于一些高頻的單詞,比如“the”,這類詞出現的頻率非常大,但是它對于信息的表達其實是沒多大用處的,也就是它包含的信息量太少了,因此在BoW模型中,這類停用詞需要降低它們的權重。借用到Channel Weight的計算過程中,我們可以想象這樣一種情況,比如某一個channel,其feature map每個像素值都是非零的,且都比較大,從視覺上看上去,白色區域占據了整個feature map,我們可以想到,這個channel的feature map是不利于我們去定位物體的區域的,因此我們需要降低這個channel的權重,而對于白色區域占feature map面積很小的channel,我們認為它對于定位物體包含有很大的信息,因此應該加大這種channel的權重。而這一現象跟IDF的思想特別吻合,所以作者采用了IDF這一權重定義了Channel Weight。
總體來說,這個Spatial Weight和Channel Weight的設計還是非常巧妙的,不過這樣一種pooling的方式只能在一定程度上契合感興趣區域,我們可以看一下Spatial Weight*Channel Weight的熱力圖:
從上面可以看到,權重大的部分主要在塔尖部分,這一部分可以認為是discriminate區域,當然我們還可以看到,在圖像的其他區域,還有一些比較大的權重分布,這些區域是我們不想要的。當然,從小白菜可視化了一些其他的圖片來看,這種crow pooling方式并不總是成功的,也存在著一些圖片,其權重大的區域并不是圖像中物體的主體。不過,從千萬級圖庫上跑出來的結果來看,crow pooling這種方式還是可以取得不錯的效果。
RMAC pooling
RMAC pooling的池化方式源自于Particular object retrieval with integral max-pooling of CNN activations,三作是Hervé Jégou(和Matthijs Douze是好基友)。在這篇文章中,作者提出來了一種RMAC pooling的池化方式,其主要的思想還是跟上面講過的MOP pooling類似,采用的是一種變窗口的方式進行滑窗,只不過在滑窗的時候,不是在圖像上進行滑窗,而是在feature map上進行的(極大的加快了特征提取速度),此外在合并local特征的時候,MOP pooling采用的是VLAD的方式進行合并的,而RMAC pooling則處理得更簡單(簡單并不代表效果不好),直接將local特征相加得到最終的global特征。其具體的滑窗方式如下圖所示:
from?Day 2 Lecture 6 Content-based Image Retrieval
圖中示意的是三種窗口大小,圖中‘x’代表的是窗口的中心,對于每一個窗口的feature map,論文中采用的是MAX pooling的方式,在L=3時,也就是采用圖中所示的三種窗口大小,我們可以得到20個local特征,此外,我們對整個fature map做一次MAX pooling會得到一個global特征,這樣對于一幅圖像,我們可以得到21個local特征(如果把得到的global特征也視為local的話),這21個local特征直接相加求和,即得到最終全局的global特征。論文中作者對比了滑動窗口數量對mAP的影響,從L=1到L=3,mAP是逐步提升的,但是在L=4時,mAP不再提升了。實際上RMAC pooling中設計的窗口的作用是定位物體位置的(CROW pooling通過權重圖定位物體位置)。如上圖所示,在窗口與窗口之間,都是一定的overlap,而最終在構成global特征的時候,是采用求和相加的方式,因此可以看到,那些重疊的區域我們可以認為是給予了較大的權重。
上面說到的20個local特征和1個global特征,采用的是直接合并相加的方式,當然我們還可以把這20個local特征相加后再跟剩下的那一個global特征串接起來。實際實驗的時候,發現串接起來的方式比前一種方式有2%-3%的提升。在規模100萬的圖庫上測試,RMAC pooling能夠取得不錯的效果,跟Crow pooling相比,兩者差別不大。
上面總結了6中不同的pooling方式,當然還有很多的pooling方式沒涵蓋不到,在實際應用的時候,小白菜比較推薦采用RMAC pooling和CROW pooling的方式,主要是這兩種pooling方式效果比較好,計算復雜度也比較低。
抽取網絡任意層的特征
在上面一節中,我們頻繁的對網絡的不同層進行特征的抽取,并且我們還提到fc6_dropx和fc6_relux是一樣的(比如fc7_drop7和fc7_relu7是一樣的),這一節主要講述使用Caffe抽取網絡任意一層的特征,并從實驗的角度驗證fc6_dropx和fc6_relux是一樣的這樣一個結論。
為了掌握Caffe中網絡任意一層的特征提取,不妨以一個小的題目來說明此問題。題目內容為:給定VGGNet16網絡,抽取fc7、fc7_relu7以及fc7_drop7層的特征。
求解過程:VggNet16中deploy.txt中跟fc7相關的層如下:
如果使用net.blobs['fc7'].data[0],我們抽取的特征是fc7層的特征,也就是上面:
layers {bottom: "fc6"top: "fc7"name: "fc7"type: INNER_PRODUCTinner_product_param {num_output: 4096} }這一層的特征,仿照抽取fc7特征抽取的代碼,我們很自然的想到抽取relu7的特征為net.blobs['relu7'].data[0]和drop7的特征為net.blobs['drop7'].data[0],但是在運行的時候提示不存在relu7層和drop7層,原因是:
To elaborate a bit further: The layers drop7 and relu7 have the same blobs as top and bottom, respectively, and as such the blobs’ values are manipulated directly by the layers. The advantage is saving a bit of memory, with the drawback of not being able to read out the state the values had before being fed through these two layers. It is simply not saved anywhere. If you want to save it, you can just create another two blobs and re-wire the layers a bit.
摘自Extracting ‘relu’ and ‘drop’ blobs with pycaffe,因而,為了能夠提取relu7和drop7的特征,我們需要將上面的配置文件做些更改,主要是將layers里面的字段換下名字(在finetune模型的時候,我們也需要做類似的更改字段的操作),這里小白菜改成了:
layers {bottom: "fc6"top: "fc7"name: "fc7"type: INNER_PRODUCTinner_product_param {num_output: 4096} } layers {bottom: "fc7"top: "fc7_relu7"name: "fc7_relu7"type: RELU } layers {bottom: "fc7"top: "fc7_drop7"name: "fc7_drop7"type: DROPOUTdropout_param {dropout_ratio: 0.5} }經過這樣的修改后,我們使用net.blobs['fc7_relu7'].data[0]即可抽取到relu7的特征,使用net.blobs['fc7_drop7'].data[0]可抽取到drop7的特征。
數據增強
有了上面CNN網絡中哪一層最適合于做圖像檢索、基于pre-trained模型做圖像檢索幾種典型的特征表示方法以及抽取網絡任意層的特征三方面的知識儲備后,在具體fine-tuning網絡進行圖像檢索實驗前,還有一節很重要(雖然我們都很熟悉)內容,即數據增強(Data Augmentation)。數據增強作用有二:一是均衡樣本,比如某些類別只有幾張圖片,而有的類別有上千張,如果不做均衡,分類的時候計算的分類準確率會向樣本充足的類別漂移;二是提高網絡對于樣本旋轉、縮放、模糊等的魯棒性,提高分類精度。在實際工作中,我們拿到了圖像數據樣本對采用深度學習模型而言,經常是不充足且不均衡的,所以這一步數據的前置處理是非常重要的。
在正式開始數據增強之前,對圖片進行異常檢測是非常重要的,其具體的異常表現在圖片內容缺失、圖片不可讀取或者可以讀取但數據出現莫名的問題,舉個例子,比如通過爬蟲爬取的圖片,可能上半部分是正常的,下半分缺失一片灰色。因此,如果你不能確保你訓練的圖片數據都是可正常讀取的時候,最好對圖片做異常檢測。假設你的訓練圖片具有如下層級目錄:
? imgs_diff tree -L 2 . ├── 0 │?? ├── 1227150149_1.jpg │?? ├── 1612549977_1.jpg │?? ├── 1764084098_1.jpg │?? ├── 1764084288_1.jpg │?? └── 1764085346_1.jpg └── 1├── 1227150149_1.jpg├── 1612549977_1.jpg├── 1764084098_1.jpg├── 1764084288_1.jpg└── 1764085346_1.jpg下面是小白菜參考網上資料寫的圖片異常檢測代碼如下:
from PIL import Image import glob import cv2 import os import numpy as npfrom PIL import Imagedef check_pic(path):try:Image.open(path).load()except:print 'ERROR: %s' % pathreturn Falseelse:return Trueimgs_list = glob.glob('/raid/yuanyong/neuralcode/ncdata/*/*')for img_path in imgs_list:#img = Image.open(img_path)#if img.verify() is not None or img is None:# print img_pathtry:img = Image.open(img_path)im = img.load()except IOError:print 'ERROR: %s' % img_pathcontinuetry:img = np.array(img, dtype=np.float32)except SystemError:print 'ERROR: %s' % img_pathcontinueif len(img.shape) != 3:print 'ERROR: %s' % img_path通過上面的圖片異常檢測,我們可以找到那些不可讀取或者讀取有問題的圖片找出來,這樣在我們使用Caffe將圖片轉為LMDB數據存儲的時候,不會出現圖片讀取有問題的異常。在圖片異常檢測完成后,便可以繼續后面的數據增強了。
在小白菜調研的數據增強工具中,小白菜以為,最好用的還是Keras中的數據增強。Keras數據增強部分包含在image.py,通過類ImageDataGenerator可以看到Keras包含了對圖片的不同處理,下面是小白菜基于Keras寫的數據增強腳本,假設你的圖像數據目錄結構具有如下結構:
? imgs_dataset tree -L 2 . ├── 0 │?? ├── 1227150149_1.jpg │?? ├── 1612549977_1.jpg │?? ├── 1764084098_1.jpg │?? ├── 1764084288_1.jpg │?? └── 1764085346_1.jpg └── 1├── 1227150149_1.jpg├── 1612549977_1.jpg├── 1764084098_1.jpg├── 1764084288_1.jpg└── 1764085346_1.jpg....數據增強的腳本如下:
#!/usr/bin/env python #-*- coding: utf-8 -*- #Author: yuanyong.nameimport os import random from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_imgnum_wanted = 800 target_path_dir = '/raid/yuanyong/neuralcode/ncdata'datagen = ImageDataGenerator(rotation_range = 10,width_shift_range = 0.2,height_shift_range = 0.2,shear_range = 0.2,zoom_range = 0.2,horizontal_flip = True,fill_mode = 'nearest')sub_dirs = os.walk(target_path_dir).next()[1] for sub_dir in sub_dirs:sub_dir_full = os.path.join(target_path_dir, sub_dir)img_basenames = os.listdir(sub_dir_full)num_imgs = len(img_basenames)num_perAug = int(float(num_wanted)/float(num_imgs)) - 1if num_imgs >= num_wanted:continuenum_total = 0 for i, img_basename in enumerate(img_basenames):num_total = num_imgs + i*num_perAugif num_total >= num_wanted:break img_path = os.path.join(sub_dir_full, img_basename)#print "Aug: %s" % img_pathimg = load_img(img_path) # this is a PIL image, please replace to your own file pathif img == None:continuetry:x = img_to_array(img) # this is a Numpy array with shape (3, 150, 150)x = x.reshape((1,) + x.shape) # this is a Numpy array with shape (1, 3, 150, 150)i = 0for batch in datagen.flow(x, batch_size = 1, save_to_dir = sub_dir_full, save_prefix = 'aug', save_format = 'jpg'):i += 1if i >= num_perAug:break # otherwise the generator would loop indefinitelyexcept:print "%s" % img_path# delete extra aug images for sub_dir in sub_dirs:sub_dir_full = os.path.join(target_path_dir, sub_dir)img_basenames = os.listdir(sub_dir_full)num_imgs = len(img_basenames)if num_imgs <= num_wanted:continueaug_imgs = [img_basename for img_basename in img_basenames if img_basename.startswith('aug_')]random.shuffle(aug_imgs, random.random)num_del = num_imgs - num_wantedaug_imgs_del = aug_imgs[-num_del:]for img_basename in aug_imgs_del:img_full = os.path.join(sub_dir_full, img_basename)os.remove(img_full)target_path_dir是設置你訓練數據集目錄的,num_wanted是設置每一個類你想要多少個樣本(包括原始樣本在內),比如在Neural Codes for Image Retrieval中的數據集landmark數據集,里面的類別少的樣本只有9個,多的類別樣本有上千個,樣本部分極其不均勻,上面代碼中,小白菜設置得是num_wanted=800,即讓每一個類別的樣本數據控制在800左右(多出來的類別樣本不會刪除,比如有的類別可能原始樣本就有1000張,那么不會對該類做數據增強)。
在這一節,小白菜總結了為什么要數據增強,或者說數據增強有什么好處,然后提供了兩個腳本:圖片異常檢測腳本check_image.py和數據增強腳本keras_imgAug.py。有了前面三部分的知識和本節數據前置處理的實踐,我們終于可以進行fine-tuning了。
VGGNet16網絡模型fine-tuning實踐
在實際中,用CNN做分類任務的時候,一般我們總是用在ImageNet上預訓練好的模型去初始化待訓練模型的權重,也就是不是train from scratch,主要原因有二:一是在實際中很難獲取到大量樣本(即便是做了數據增強);二是加快模型訓練的速度。因而,針對檢索這個任務,我們也采用fine-tuning的方式,讓在ImageNet上預訓練的模型遷移到我們自己的特定的數據集上,從而提升特征在檢索任務上的表達能力。下面小白菜以fine-tuning Neural Codes提供的數據集為例,比較詳實的總結一個完整的fine-tuning過程。
在fine-tuning之前,我們先追問一個簡單的問題和介紹一下Neural Codes提供的landmark數據集。追問的這個問題很簡單:為什么幾乎所有的做檢索的論文中,使用的都是AlexNet、VGGNet16(偶爾會見到一兩篇使用ResNet101)網絡模型?難道做研究的只是關注方法,使用AlexNet、VGGNet、ResNet或者Inception系列只是替換一下模型而已?小白菜曾也有過這樣的疑問,但是對這些網絡測試下來,發覺VGGNet在做基于預訓練模型特征再表達里面效果是最好的,對于同一個方法,使用ResNet或Inception系列,其mAP反而沒有VGGNet的高。至于為什么會這樣,小白菜也沒有想明白(如果有小伙伴知道,請告知),我們就暫且把它當做一條經驗。
我們再對Neural Codes論文里提供的landmark數據集做一個簡單的介紹。該數據集共有680類,有的類別樣本數據至于幾個,多則上千,樣本分布極其不均勻。不過這不是問題,通過第4節介紹的數據增強和提供的腳本,我們可以將每個類別的樣本數目控制在800左右。同時,我們可以使用下面腳本將每個類別所在目錄的文件夾名字命名為數字:
import ospath_ = '/raid/yuanyong/neuralcode/ncdata' # 該目錄下有很多子文件夾,每個子文件夾是一個類別 dirs = os.walk(path_).next()[1]dirs.sort()for i, dir_ in enumerate(dirs):print '%d, %s' %(i, dir_)os.rename(os.path.join(path_, dir_), os.path.join(path_, str(i)))得到清洗后的數據后,下面分步驟詳解使用VGGNet16來fine-tuning的過程。
切分數據集為train和val
經過上面重命名后的數據集文件夾目錄具有如下形式:
? imgs_dataset tree -L 2 . ├── 0 │?? ├── 1227150149_1.jpg │?? ├── 1612549977_1.jpg │?? ├── 1764084098_1.jpg │?? ├── 1764084288_1.jpg │?? └── 1764085346_1.jpg │?? .... └── 1├── 1227150149_1.jpg├── 1612549977_1.jpg├── 1764084098_1.jpg├── 1764084288_1.jpg└── 1764085346_1.jpg....我們需要將數據集劃分為train數據集和val數據集,注意val數據集并不單純只是在訓練的時候測試一下分類的準確率。為了方便劃分數據集,小白菜寫了如下的腳本,可以很方便的將數據集劃分為train數據集和val數據集:
import os import glob import argparse import numpy as np import randomclasses_path = glob.glob('/raid/yuanyong/neuralcode/ncdata/*') # 該目錄下有很多子文件夾,每個子文件夾train_samples = [] val_samples = []imgs_total = 0for i, class_path in enumerate(classes_path):class_ = class_path.split('/')[-1]imgs_path = glob.glob(class_path + '//*')num_imgs = len(imgs_path)num_train = int(num_imgs*0.6) # 訓練集占60%num_val = num_imgs - num_trainnp.random.seed(1024)sample_idx = np.random.choice(range(num_imgs), num_imgs, replace=False)train_idx = sample_idx[0:num_train]val_idx = sample_idx[num_train:]for idx_ in train_idx:img_path = imgs_path[idx_]train_samples.append(img_path + ' ' + class_ + '\n')for idx_ in val_idx:img_path = imgs_path[idx_]val_samples.append(img_path + ' ' + class_ + '\n')imgs_total += num_imgsrandom.shuffle(train_samples) random.shuffle(val_samples)with open('lmdb/train.txt', 'w') as f_train, open('lmdb/val.txt', 'w') as f_val:for sample in train_samples:f_train.write(sample)運行上面腳本,會在lmdb目錄下(事先需要建立lmdb目錄)生成兩個文本文件,分別為train.txt和val.txt,對于為訓練數據集和驗證數據集。
圖片轉成lmdb存儲
為了提高圖片的讀取效率,Caffe將圖片轉成lmdb進行存儲,在上面得到train.txt和val.txt后,我們需要借助caffe的convert_imageset工具將圖片resize到某一固定的尺寸,同時轉成為lmdb格式存儲。下面是小白菜平時使用的完成該任務的一個簡單腳本crop.sh
/home/yuanyong/caffe/build/tools/convert_imageset \--resize_height 256 \--resize_width 256 \/ \lmdb/train.txt \lmdb/train_lmdb運行兩次,分別對應于train.txt和val.txt。運行完后,會在lmdb目錄下生成train_lmdb和val_lmdb兩目錄,為了校驗轉成lmdb存儲是否成功,我們最好分別進入這兩個目錄下看看文件的大小以做簡單的驗證。
生成均值文件
對于得到的train_lmdb,我們在其上計算均值。具體地,使用Caffe的compute_image_mean工具:
$CAFFE_ROOT/build/tools/compute_image_mean lmdb/train_lmdb lmdb/mean.binaryproto在lmdb目錄下即可得到均值文件mean.binaryproto。
修改train_val.prototxt和solver.prototxt
針對VGGNET16網絡,在fine-tuning的時候,我們通常將最后的分類層的學習率設置得比前面網絡層的要大,一般10倍左右。當然,我們可以結合自己的需要,可以將前面層的學習率都置為0,這樣網絡在fine-tuning的時候,只調整最后一層分類層的權重;在或者我們分兩個階段去做fine-tuning,第一階段只fine-tuning最后的分類層,第二階段正常的fine-tuning所有的層(包含最后的分類層)。同時,我們還需要對最后一層分類層重新換個名字,并且對應的分類輸出類別也需要根據自己數據集的分類類別數目做調整,下面小白菜給出自己在fine-tuning Neural Codes的landmark數據集上train_val.prototxt的前面輸入部分和后面分類的部分:
name: "VGG_ILSVRC_16_layers" layers {name: "data"type: DATAinclude {phase: TRAIN}transform_param {crop_size: 224mean_file: "/raid/yuanyong/neuralcode/lmdb/mean.binaryproto"mirror: true}data_param {source: "/raid/yuanyong/neuralcode/lmdb/train_lmdb"batch_size: 64backend: LMDB}top: "data"top: "label" } layers {name: "data"type: DATAinclude {phase: TEST}transform_param {crop_size: 224mean_file: "/raid/yuanyong/neuralcode/lmdb/mean.binaryproto"mirror: false}data_param {source: "/raid/yuanyong/neuralcode/lmdb/val_lmdb"batch_size: 52backend: LMDB}top: "data"top: "label" }... ... ...layers {name: "fc8_magic" # 改名字type: INNER_PRODUCTbottom: "fc7"top: "fc8_magic" # 改名字blobs_lr: 10 # 學習率是前面網絡層是10倍blobs_lr: 20 # 學習率是前面網絡層是10倍weight_decay: 1weight_decay: 0inner_product_param {num_output: 680 # 共680類weight_filler {type: "gaussian"std: 0.01}bias_filler {type: "constant"value: 0}} } layers {name: "loss"type: SOFTMAX_LOSSbottom: "fc8_magic" # 改名字bottom: "label"top: "loss/loss" } layers {name: "accuracy"type: ACCURACYbottom: "fc8_magic" # 改名字bottom: "label"top: "accuracy"include: { phase: TEST } }上面配置測試輸入的時候,batch_size設置的是52,這個設置非常重要,我們一定要保證這個設置的batch_size跟solver.prototxt里面設置的test_iter乘起來等于測試樣本數目。下面再看看solver.prototxt這個文件:
net: "train_val.prototxt" test_iter: 4005 test_interval: 5000 base_lr: 0.001 lr_policy: "step" gamma: 0.1 stepsize: 20000 display: 1000 max_iter: 50000 momentum: 0.9 weight_decay: 0.0005 snapshot: 5000 snapshot_prefix: "../models/snapshots_" solver_mode: GPU比較重要的5個需要調整參數分別是test_iter、test_interval、base_lr、stepsize,?momentum。test_iter怎么設置上面已經介紹。test_interval表示迭代多少次進行一次驗證及測試,base_lr表示基礎學習率,一般要比正常訓練時的學習率要小,stepsize跟步長相關,可以簡單的理解為步長的分母,momentum按推薦設置為0.9就可以。
設置完上面的train_val.prototxt和solver.prototxt后,便可以開始正式fine-tuning了。
正式fine-tuning
借助Caffe工具集下的caffe,我們只需要簡單的執行下面命令即可完成網絡的fine-tuning:
$CAFFE_ROOT/build/tools/caffe train -solver solver.prototxt -weights http://www.robots.ox.ac.uk/~vgg/software/very_deep/caffe/VGG_ILSVRC_16_layers.caffemodel -gpu 0,1 | tee log.txt其中-gpu后面接的數字表示GPU設備的編號,這里我們使用了0卡和1卡,同時我們將訓練的日志輸出到log.txt里面。
測試
完成了在Neural Codes的landmark數據集上的fine-tuning后,我們使用經過了fine-tuning后的模型在Oxford Building數據集上mAP提升了多少。為了方便對比,我們仍然提取fc6的特征,下面是不做ft(fine-tuning)和做ft的結果對比:
| fc6 | 45.9% |
| fc6+ft | 60.2% |
可以看到,經過fine-tuning,mAP有了較大幅度的提升。從而也從實驗的角度驗證了對于檢索任務,在數據允許的條件,對預訓練模型進行fine-tuning顯得非常的有必要。
復現本文實驗
如想復現本文實驗,可以在這里fc_retrieval找到相應的代碼。
總結
在本篇博文中,小白菜就5個方面的問題展開了總結和整理,分別是:
- CNN網絡中哪一層最適合于做圖像檢索
- 基于pre-trained模型做圖像檢索幾種典型的特征表示方法
- 抽取網絡任意層的特征
- 數據增強(Data Augmentation)
- VGGNet16網絡模型fine-tuning實踐
整個文章的基本組織結構依照典型的工科思維方式進行串接,即從理論到實踐。
from:?http://yongyuan.name/blog/layer-selection-and-finetune-for-cbir.html?
總結
以上是生活随笔為你收集整理的图像检索:layer选择与fine-tuning性能提升验证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习:Neural Network
- 下一篇: 图像检索:再叙ANN Search