弄清楚摘要裡出現的一系列概念,終於可以進入論文的核心——Ghost Module,Ghost Bottlenecks, Ghost Net
問題4:Ghost Module長什麼樣?Ghost Bottlenecks長什麼樣?Ghost Net長什麼樣?通常的卷積如圖2(a)所示,而Ghost Module則分為兩步操作來獲得與普通卷積一樣數量的特徵圖(這裡需要強調,是數量一樣)。第一步:少量卷積(比如正常用32個卷積核,這裡就用16個,從而減少一半的計算量);第二步:cheap operations,如圖中的Φ表示,從問題3中可知,Φ是諸如3*3的卷積,並且是逐個特徵圖的進行卷積(Depth-wise convolutional)。了解了Ghost Module,下面看Ghost Bottlenecks。論文3.2 介紹Ghost Bottlenecks ,結構與ResNet的是類似的,並且與mobilenet-v2一樣在第二個module之後不採用ReLU激活函數。左邊是stride=1的Ghost Bottlenecks,右邊是stride=2的Ghost Bottlenecks,目的是為了縮減特徵圖大小。接著來看Ghost Net,Ghost Net結構與MobileNet-V3類似,並且用了SE結構,如下表,其中#exp表示G-bneck的第一個G-Module輸出特徵圖數量到這裡,論文中概念全都清晰了,下面看看實驗及效果吧。實驗及結果實驗一,Toy Experiments,這個實驗結果並不重要,只需要知道結論可以了,即 cheap operations中的卷積核選擇3*3最佳。在這裡,有探討 cheap operations為什麼用卷積,請看「Besides convolutions used in the above experiments, we can also explore some other low-cost linear operations to construct the Ghost module such as affine transformation and wavelet transformation. However, convolution is an efficient operation already well support by current hardware and it can cover a number of widely used linear operations such as smoothing, blurring, motion, etc. Moreover, although we can also learn the size of each filter w.r.t. the linear operation Φ, the irregular module will reduce the efficiency of computing units (e.g. CPU and GPU).」簡單地說,卷積高效且高質,所以linear transformations和cheap operations就是卷積,沒錯了。實驗二,在Cifar10上,對VGG-16和ResNet-56進行ghost module的即插即用實驗,具體做法是,對於VGG-16和ResNet-56,其中的所有卷積替換為Ghost module,並命名為 Ghost-VGG-16和Ghost-ResNet-56。實驗結果如下表,精度不怎麼變化的條件下,參數和FLOPs均減少一半左右,效果不賴。作者還對 vanilla VGG-16 以及 Ghost-VGG-16的第二個卷積特徵圖進行可視化,大意就是經過 cheap operation,得到的特徵圖與vanilla的差不多實驗三,在Imagenet 對ResNet-50進行ghost module的即插即用實驗這裡s=4,所以參數和FLOPs大約也減小為1/4讀完論文就迫不及待想試試神奇的ghost module的能力。由於沒有設備,未能在imagenet上訓練ghostnet,這裡僅對cifar10進行了復現實驗,有機器的話,修改模型和dataloader就可以訓練imagenet了。1. 訓練模型,訓練文中提到的vgg16, ghost-vgg16, resnet56, ghost-resnet562. 計算Weights和FLOPs, 計算4個模型的Weights和FLOPs3. 可視化特徵圖,對vgg16,ghost-vgg16所有卷積特徵圖進行可視化上述實現代碼在:https://github.com/TingsongYu/ghostnet_cifar10ghost部分:https://github.com/huawei-noah/ghostnetvgg部分:https://github.com/kuangliu/pytorch-cifarresnet部分:https://github.com/akamaster/pytorch_resnet_cifar10實驗1. 訓練模型這部分訓練論文中提到的vgg16, ghost-vgg16, resnet56, ghost-resnet56從http://www.cs.toronto.edu/~kriz/cifar.html 下載python版,得到cifar-10-python.tar.gz,解壓得到 cifar-10-batches-py,並放到ghostnet_cifar10/data下,然後執行 python bin/01_parse_cifar10_to_png.py,可在data/文件夾下獲得cifar10_train 和 cifar10_test兩個文件夾nohup python 02_main.py -gpu 1 0 -arc resnet56 > resnet56.log 2>&1 &nohup python bin/02_main.py -max_epoch 190 -lr 0.1 -gpu 1 0 -arc resnet56 -replace_conv > ghost-resnet56.log 2>&1 &nohup python bin/02_main.py -max_epoch 190 -lr 0.1 -gpu 1 0 -arc vgg16 > vgg16.log 2>&1 &nohup python bin/02_main.py -max_epoch 190 -lr 0.1 -gpu 1 0 -arc vgg16 -replace_conv > ghost-vgg16.log 2>&1 &- replace_conv, 表示是否將模型中的conv2d層替換為GhostModule- arc,目前寫死了,只支持resnet56, vgg16,可自行修改- pretrain,訓練ghost-***時,加載baseline的卷積核參數,這個不屬於論文範疇,屬於拓展的訓練方法,拓展部分講解- frozen_primary,凍結primary部分的卷積核,訓練ghost-***時,採用baseline的卷積核參數初始化之後對primary部分卷積核進行凍結,只訓練cheap conv部分- low_lr,primary部分卷積核學習率是否小10倍- point_conv,block中第一個primary是否採用1*1卷積結構比較清晰,分為5個步驟, 數據--模型--損失函數--優化器--迭代訓練核心在於replace_conv函數進行卷積核替換為GhostModule,實現論文的操作,只需要傳入三個參數,分別是model, GhostModule和arc即可實驗結果輸出均在results/文件夾下,以時間戳為子文件夾,未自動記錄log實驗超參按Ghostnet論文中提到那樣,與Resnet論文中保持一致,lr=0.1, bs=128,2個gpu,epoch通過iteration轉換得來的實驗日誌、曲線、checkpoint均在results裡獲得。實驗結果如下表所示,與原文中提到的精度還是有一點點差距,不知是什麼原因,大家也可以嘗試一下參照論文中的設置進行實驗,歡迎大家貢獻自己的訓練log和results到github上供大家一起學習。實驗2. 計算Weights和FLOPs腳本:bin/03_compute_flops.pyWeights和FLOPs的計算採用torchstat工具,必須是python3以上才可安裝安裝方法:pip install torchstattorchstat網站:https://github.com/Swall0w/torchstat方法:執行03_compute_flops.py 即可得到Weights和FLOPs,得到的結果與論文中有一些誤差,具體如下:實驗3. 可視化特徵圖方法:運行腳本會在 results/runs下以時間戳為子文件夾記錄下events file,然後藉助tensorboard就可以查看特徵圖與論文一致,對vgg16和ghost-vgg16進行可視化,可視化的圖片也與論文一致,是那只可愛的小狗,可是論文沒告訴大家那張圖片在哪裡,所以我人肉把那張可愛的狗狗給人肉出來了,在cifar10_train/5/5_11519.pngghost-vgg16的第二個卷積部分的primary和cheap的卷積特徵圖如下:在這裡發現cheap conv中有死神經元,輸出是全黑的,不知道是哪個地方出錯了,是否是這裡導致模型指標達不到論文的水平呢?大家可以關注一下這個問題
ghostmodule的思想是很巧妙的,它還有很多東西可以改進,可以去做的地方,這裡就提幾個想到的方案,但是經過代碼實踐失敗的idea。圖中可知,Ghost module中的primary conv與原始卷積是一模一樣的操作,唯一不同是cheap conv的逐通道卷積會改變一半的特徵圖,那麼能否找到恰當的逐通道卷積核,讓這一半特徵圖儘可能的與原始卷積得到的特徵圖一模一樣,就可以實現不需要訓練的即插即用。思想就是,讓Ghost module輸出的特徵圖保持與原始卷積一致,那麼就可以實現不需要訓練的即插即用。1. 針對primary convolutoin:先從baseline模型中挑選一半的卷積核直接賦值給Ghost module的primary conv,這一步是完全等價的,很好理解。2. 針對cheap convolution:找到恰當卷積核權值使得Ghost module輸出的特徵圖與原始卷積儘可能保持一致方案1:藉助baseline的卷積核參數從baseline中挑選卷積核賦值到 ghost-baseline模型的primary convolution具體操作只需要在 02_main.py 中 添加-pretrain,即可在replace_conv函數中執行baseline卷積核賦值操作。在這裡就會有一個問題,假設baseline的卷積層2K個卷積核,那麼如何挑選出K個最有價值的卷積核呢? 這是最頭疼的問題,按論文的思想,應該從特徵圖上去觀察,哪些卷積核得到的特徵圖比較接近,那麼就從相近的特徵圖對應的卷積核中挑選出一個。這裡就採用了卷積核聚類的方法來挑選8個卷積核,然後賦值。這個方案效果不好,多種嘗試均存在掉點,於是放棄了,大家也可以嘗試不同超參進行訓練,看一下效果。問題1. 如何從baseline中2K個卷積核中挑選K個卷積核?2. 採用聚類方法對卷積核進行聚類,聚類類別為K個類別;3. 對卷積核進行L1排序,挑選L1較大的前K個;上述4個方法匯總,1,3,4都可在代碼中找到對應函數,但由於效果不好,就沒有指出。問題2. ghost-baseline訓練中,primary是否需要更新?按論文的思想,primary 卷積核來自baseline的卷積核,應該是一個已經訓練得很好的卷積核了,因此不需要再訓練,只要保證cheap convolution的卷積核學習到比較好的線性變換就可以。但實際上,如果凍結primary的卷積核,模型性能大幅下降,這裡可以通過參數 -low_lr 和 -frozen_primary分別實現primary採用較小學習率或完全不更新。由於primary卷積核來自baseline,特徵圖拼接部分應該按卷積核順序拼接,否則下一個primary卷積接收的特徵圖就亂了。因此對Ghost Module進行了少少修改,增加fmap_order參數用於記錄卷積核順序,同時在forward中增加特徵圖排序。經過不斷實驗,想通過baseline的訓練參數進行即插即用的方案暫時以失敗告終。想從baseline中借用已經訓練好的卷積核方案還不成熟,希望大家可針對這個想法進一步改進,按論文中的理論,baseline中的卷積核應該可以拿到ghost-baseline中用一用的。方案2:真正意義的即插即用文中提到的即插即用是模型結構的變換,但是還需要重新訓練,那麼有沒有方案實現真正意義的即插即用,不用訓練?要實現不訓練的即插即用,就要回顧Ghost Module對原始Conv的改變,這裡還要回顧這張圖:從圖中可以發現,唯一變化的地方就是 cheap operation導致一半的特徵圖變化。因此,重點關注cheap部分的卷積核,只要這部分的卷積核能實現從intrinsic feature maps中變換得到與baseline卷積核對input進行卷積得到的特徵圖一致的特徵圖,那麼Ghost Module就實現了不需要訓練的即插即用。上面那句話很是繞口,下面配圖解釋方案2的想法,如何可以實現不訓練的即插即用。先看下圖,上半部分是正常的卷積,下半部分是Ghost module示意圖。為了更好理解intrinsic和ghost,正常卷積的卷積操作拆分為兩個部分,分別是W_i,和W_g,分別對應intrinsic和ghost,其中W_i是需要保留的卷積核,它得到的特徵圖為F_i,這一部分在Ghost Module中是完全被保留的,因此它的特徵圖不會發生變化,發現變化的是W_g部分卷積核卷積得到的F_g。 再看下半部分的Ghost Module中,特徵圖被變化的是cheap ops中的生成的F_c。如何找到恰當的權值W_c使得F_c等價於F_g呢?為了得到W_c的權值,可以從文中的假設出發,intrinsic feature maps和ghost feature maps存在線性變換的話,勢必存在公式F_g = W*F_i, 其中W是權值矩陣。卷積操作又可以寫成矩陣乘法的形式,那麼我們可以找到W的權值,請看下圖公式這裡存在一個問題,W_i是否可逆?大部分情況都是不可逆的,所以這個方案通過這個思路還是受阻的,當然大家可以嘗試求偽逆,得到W矩陣,然後賦值給cheap ops中的卷積核,試一下效果,這裡僅提供一個思路,就沒有去實現了。方案3:定製符合Ghost module的baseline方案2中無法找到合適的線性變換,使得F_g = F_i ,其根本原因是baseline的卷積部分的卷積核之間不存在這種線性關係。那麼,為了實現不訓練的即插即用,是否可以從baseline結構出發,設計一個卷積核內部存在線性變換的模型結構,然後再用以上等價方式進行變換到ghost module。不知道卷積核內部存在線性變換的結構是否已經有論文提出?如果有,請發郵件(yts3221@126.com)或評論告訴大家吧。Ghost Module的想法很巧妙,可即插即用的實現輕量級卷積模型,但若能實現不訓練的輕量級卷積模型,那就更好了。這也是本筆記中遺憾的部分,未能實現不訓練的即插即用,在此希望集思廣益,改進上述提出的不成熟方案,說不定GhostNet-V2就誕生了,當然更期待原作者提出GhostNet-V2。為了方便大家閱讀和實踐,在公眾號後臺回復 ghostnet 即可獲取論文原文和文中提到的vgg model下載。