目錄
5.dropout(0.5*0.5*0.5)+BN(without biases):
關於snapshot(ckpt)的優化——ensemble:
概要:
問題來源:
kaggle的一個表情識別的訓練集。
題目的發起者也是用三個競賽發起了一篇論文。ICML的表示學習專題
https://arxiv.org/abs/1307.0414
論文對此比賽的說明:
在本次比賽中,我們想比較一個研究得很好但使用全新數據集的任務的方法。這避免了過度擬合重複使用的基準數據集的測試集的問題。舉辦這種競賽的一個原因是,它允許我們以儘可能公平的方式將特徵學習方法與手工設計的特徵進行比較。這個數據集是一個大型項目的一部分,是用谷歌的人臉識別API獲得的,進行了各種邊界處理、去重和裁剪的48*48灰度圖(所以其實自己能做的數據增強選項不多)。fer2013有采集錯誤,人類也只有65%+-5%的準確度。
JamesBergstra還確定了“空”模型的最佳性能,該模型由一個卷積網絡組成,除了最後的分類器層之外,沒有任何學習。通過使用TPE超參數優化算法,他發現這種最佳卷積網絡的精度達到60%。使用這樣的組合模型,他獲得了65.5%的準確率。詳見[13]。
前三個團隊都使用卷積神經網絡[14]對圖像轉換進行歧視性訓練。勝利者Yichuan Tang,他將SVM的primal objective作爲loss去訓練,然後加了一個L2-SVM的loss。這是一種在競賽數據集等方面取得了巨大成果的新發展。
選擇原因:
選這個fer2013數據集的主要原因,主要是覺得適合拿來練手。
fer2013的圖形比MNIST複雜一些,MNIST隨便用一個簡單模型,全連接層強行記憶就幾乎完全擬合,做一些額外的改進可能根本看不到區別。
fer2013比ImageNet比,又簡單的多,不至於因爲顯卡危機而把時間都浪費在等待上,調兩個參數,估計什麼都涼了。
實際測下來,fer2013訓練集與測試集有一定差距,難以達到理想的泛化效果,所以用各種優化都能明顯、或者比較明顯的看到實際的改進,反饋好,很適合學習。(至少在我接觸的數據集中,算是比較合適的一個)
實現與優化思路:
第一的模型能達到71%,但是結構特殊;
其他有cliquenet,不是典型DenseNet;
還有VGG,不過VGG用的數據集不同,還有1000類,感覺微調應該不夠用,而且訓練也會很慢;
我打算先用普通卷積網絡加交叉熵來做,看能到多少,再考慮後續優化。。
前置:
數據處理:
這套fer2013數據本身問題還是比較多的。各種水印、卡通、抽象加遮擋。根據官方說法,訓練數據和測試數據差距也較大。
全流程的數據操作,三部分。
原csv數據的讀取與分割:
csv讀取解析存儲等。
csv數據轉圖片和tfrecord的存取:
csv的字符串數據,經過處理,以圖片文件和文件名-標籤列表的形式存在。
最後將圖片和標籤一起存入tfrecord。
這塊細節比較多,好好調一下。
tfrecord接生產隊列供模型訓練:
線程協調器,生產者隊列,生產者直接從文件讀取數據,存入隊列,模型直接從隊列拿數據
神經網絡定義:
根據lenet5和alexnet進行變形:
因爲fer2013的圖形是48*48*1的,比mnist大的多,lenet-5是針對mnist的24*24的,本文圍繞Lenet5進行改造,增加捲積層數和全連接層數(),加入dropout、Regularization、BN、data augmentation等。
卷積核大小,5*5,三層。三層通道數64,64,128,conv配套ReLU、pooling和BN。FC層大小512.
網絡結構如下
layer | k_size | stride | padding | k_num | output_size |
input | b,48,48,1 | ||||
conv_1 | 5*5 | 1*1 | SAME | 64 | b,48,48,64 |
poolling_1 | 3*3 | 2*2 | SAME | b,24,24,64 | |
conv_2 | 5*5 | 1*1 | SAME | 64 | b,24,24,64 |
poolling_2 | 3*3 | 2*2 | SAME | b,12,12,64 | |
conv_3 | 5*5 | 1*1 | SAME | 128 | b,12,12,128 |
poolling_3 | 3*3 | 2*2 | SAME | b,6,6,128 | |
flatten | b,4608 | ||||
FC_1 | 512 | b,512 | |||
FC_2 | 512 | b,512 | |||
output | 7 | b,7 |
卷積核5*5,stride1*1,stride小爲了更好更細的提取特徵。
池化層3*3,stride2*2,池化本來就是要降採樣縮小特徵圖,所以stride=2,池化核3*3重疊是爲了邊緣平滑。
關於感受野的計算:
一個3*3的卷積核,感受野3*3,兩層3*3的卷積核組合起來,感受野就是5*5,間接等效於一個5*5的卷積核,而且因爲多了一次非線性轉換,其實擬合效果還要更好一些。(三層3*3的感受野是7*7)
一個5*5的卷積核,感受域是5*5,兩層5*5的感受野就是9*9,三層是13*13,每多一層就+k_size-1,其實如果中間組合了pooling,感受野還是會進一步擴大的,經過一層pooling,感受野類似double的效果。下面計算一下:
第一層卷積:1+(5-1)=5
第一層池化:5+(3-1)=7
基本特徵跨度double
第二層卷積:7+(5-1)*2=15
第二層池化:15+(3-1)*2=19
基本特徵跨度double
第三層卷積:19+(5-1)*4=35
第三層池化:35+(3-1)*4=43,
輸出pool3,一個點的感受野是43,移動一位跨度8,2*2的特徵圖即可覆蓋51*51的原圖,足夠覆蓋fer2013的48*48,實際pool3輸出是6*6,留有餘量,這裏是可以適當縮減卷積核的,只是沒太大必要,時間關係,就不實驗了(也有好處,比如減少了參數數量,增加了訓練速度)。
損失函數:
交叉熵+(全連接層)L2正則化
滑動平均(EMA):
加“慣性”,讓weights和biases變動更平緩,避免個別batch造成的過大波動,加速收斂,也能保證測試結果比較穩定。
EMA又叫影子變量,影子之所以叫影子,是因爲他會跟在變量的後邊變化。
注意事項:如果想用影子變量做預測,注意你恢復的變量是不是影子變量。ema.variables_to_restore()是用來幹這個的,指定一個映射,用影子變量替代原變量。
EMA筆記和示例:
https://blog.csdn.net/huqinweI987/article/details/88241776
訓練與優化過程:
基本設置:
學習率:
如果算力有限,初期學習率可以適當高一點,方便觀察,及時調整。
後期學習率當然可以低一點,個人覺得分界線在過擬合吧,如果一個模型能學到過擬合的水平(本例是準確率70+),學習率就不用往高去調了,有算力的話,還可以再低一些。
學習率的decay,如果不監控,可能導致後期學習率過低,跟蹤一下,然後可以加入一個保底。
如果中途想調整模型,也不想重新訓練,斷點續訓,需要重新調整基礎學習率來中和一下,不然學習率低到見不到任何改變。
2019-03-16 15:39:46 : After 244799 training step(s),lr is 1.49615e-13 loss,accuracy on training batch is 0.857955 , 0.746094.
After 244799 training step(s), test accuracy = 0.644531
曲線已經不容易發生變化
LEARNING_RATE_BASE = 0.0005改爲LEARNING_RATE_BASE = 0.05
2019-03-16 15:44:13 : After 244859 training step(s),lr is 1.48118e-11 loss,accuracy on training batch is 0.630326 , 0.839844.
After 244859 training step(s), test accuracy = 0.640625
這樣學習率就提升兩個數量級,具體調整到多少,根據需求。
batch_size:
暫時用虛擬機,沒法GPU,第一次BATCH小了,一直到結束,只有34(中間也跳了一次40,但是因爲batch小,沒說服力)
把batch改爲128:
第一次就36了,訓練1000次以後在五十上下:
補充:最後改到了雙GTX1060 3G,不使用專門的併發邏輯,只觸發單卡,能支持256,再翻倍到512就超了。
訓練次數:
BATCH_SIZE = 128
train_num_examples=28709
225個step對應一個epoch。
前邊設置1000次訓練太少了,提升訓練次數:
兩萬step的時候,已經接近1了,因爲理論上達不到這麼高,所以這是過擬合了,再用測試集對比一下
改了網絡的變量命名,第二次訓練又死機了,卡在6000次的時候,先以6000次爲基準做個對照:
訓練集的mini-batch準確率如下:
測試集的準確率如下:
過擬合明顯,所以打算先從dropout入手改善
Dropout:
消除過擬合的一種手段,每次通過不同神經元,使每個神經元都可能被訓練到,避免路徑依賴,有一種ensemble的作用。詳細對比放在dropout和BN實驗。
BN:
第一個版本用了一下lrn,lrn是激活和pooling之後使用,後邊的版本我會替換成BN,BN在激活函數前使用。
實測替換BN後在同樣的訓練次數下準確率好於之前的lrn,雖然relu正軸斜率是1,相比sigmoid弱化了一部分預防梯度爆炸的效果,但是整體來說BN對於改善分佈和提升訓練效率還是很有效的。
實驗對比(無LRN且無BN對比有BN,不是LRN對比BN):
使用BN前,可以看到三層卷積層的激活輸出relu1、2、3,一個比一個萎縮,兩層FC的輸出也不太好。使用BN後(圖二),conv層激活RELU3的輸出分佈明顯好轉,仔細看FC層y1、y2的數值分佈,深色區域訓練初期1.5+,訓練後期1.5-,明顯好於圖一,而圖一大多數深色區域小於1。
然後是變量的區別,使用BN前,訓練過程中,變量weights範圍變化大,biases有大範圍的偏移,說明參數要遷就數據,使用BN後(圖二),仔細觀察刻度,無BN的模型,卷積層的模型w深色部分超過0.1,變化幅度也大,有BN模型不超過0.1,而且有BN的模型,weights範圍也很早就平穩下來,說明weights需要的範圍也更小,容易達到,收斂速度更快(圖二biases皆爲固定值0.1,因爲在BN前取消掉了biases,短路閒置了,打印出來驗證而已,只有fc_b3因爲對應最終分類輸出,沒有做BN)
具體BN兩種寫法和詳細計算過程以及原理和注意事項的筆記
Dropout和BN的組合、對比實驗:
基礎學習率0.0005,BATCH=256
帶dropout的模型,4k步以後趨於穩定,6k步準確率趨於1.0。
(這圖不算完整模型了)loss有下降趨勢,時間關係,就不看一萬多步了。
接下來打算設計幾個實驗進行對比:
1.基準:動態學習率的素神經網絡(其他都不帶):
訓練準確率接近1,測試準確率57%~59%。
2.單純dropout(0.6*0.6*0.6):
測試準確率61+,感覺dropout還是不夠多,還是充分的過擬合了。(誤區,其實測試準確率上不去還有其他因素,比如數據集的不同)
3.(有biases的)BN:
圖丟了(手動滑稽)(在本例,相對來說差距不算大,略過,以後直接使用無biases的)
4.去掉biases的BN:
加BN的話biases就不是很有用,留offsets就行了(論文),仔細想BN的流程,其實BN已經自帶了offsets了,輸入端也是做了歸一化處理,對於想要的效果來說,輸入之前bias就顯得不是那麼有用。因爲relu就是線性的,和sigmoid不同,sigmoid要避開0點,所以ReLU也不一定就很需要scaling操作。(如果不要scale,接口可以傳參scale=False)
現在網絡保持同等條件下,去掉BN輸入之前+bias操作,只留W*x。影響不大,後邊的操作,BN之前就不用biases了。
5.dropout(0.5*0.5*0.5)+BN(without biases):
組合前邊的用法,仍然是60上下
5.2.FC層追加BN(之前只有conv層加了BN):
FC的ReLU前也加BN層,最後一層輸出層除外。同樣的,在BN層之前去掉biases。
略有升高,圖中65%的點是低平滑度下的單個batch的凸點,整體平滑在60%之上,之前只有60的水平,但是整體差別也不大。
5.3.更低的dropout(0.3):
三層0.5,通過的只有0.125了,但是沒辦法,還是過擬合(“過擬合”不一定是訓練的原因,至少,本例的測試集準確率上不去,多少有些數據集的關係)。
三層0.3,通過率0.027,FC層寬度512,還能平均通過13.824個特徵,7分類的話,應該還算勉強夠用。
從曲線可知,測試集的準確率不是先上升後下降,也不完全算是過擬合或者過度訓練的鍋(否則隨着訓練的進展,測試集可能會有所下降),提前終止的手段用不上。
從dropout和BN看,估計只能到這個水平了。嘗試其他出路。
目前已知的優化方向:數據增強、數據均衡和標籤優化(fer2013_plus,不一定能用得上)。
數據增強:
flip_left_and_right:
通過觀察數據集圖片可知,第一個可行的方法應該就是左右翻轉了。另外,如果不改網絡的輸入尺寸,可以考慮把圖像resize放大一點,再crop,觀察一下效果,不過看起來圖片都很緊湊,這樣做也可能會丟失面部特徵。
最終方案,保持0.3*0.3*0.3的dropout以及標準BN操作,所有條件不變,以0.5的概率,對讀取的數據進行批操作,左右翻轉。(更新,將tf.cond換成了tf.where,從使用一個隨機數控制一個batch改爲使用batch_size個隨機數分別控制每一個樣本隨機翻轉)
結果對比:
藍線是batch準確率,不是全部樣本。使用相同平滑度0.946.
舊的曲線,最後一段,低點0.61,高點0.62。而數據增強的結果,低點0.625,高點0.646,提高一個百分比有餘。
單獨跑測試集,全體平均,明顯高於之前。
輕度rotate:
通過觀察圖片,發現人臉本來就有一定的傾斜度,所以做rotate應該不會影響模型抽象特徵,並且能增加數據量。
和flip同理,隨機對數據進行批處理(更新,將tf.cond換成了tf.where,從使用一個隨機數控制一個batch改爲使用batch_size個隨機數分別控制每一個樣本隨機旋轉),隨機rotate[-0.5,0.5],其他所有都保持不變,包括BN、dropout的keep率、隨機的左右flip的操作。
tensorboard保持同樣平滑度,後期的藍線低點0.66,高點0.679,總體在66%~67%吧。
使用整個測試集測試,平均能保持在0.66~0.67!
驗證了古人那句話“有時候不是贏在算法,而是贏在數據!”
更多的數據增強思路:對於fer2013,應該認識到,訓練集和測試集的不同是預測準確率上不去的主要原因,所以數據增強應該圍繞這兩個集合的差距來研究,雖然也不能硬生觀察兩個集合區別強行改變,因爲用測試集有作弊嫌疑。但是可以研究學習當做“行業經驗”。
根據圖片有各種字母和水印這個事實,也可以考慮自己加一些類似噪音進去。
(todo:如果能智能識別,給遠景圖crop,近景圖保留原樣就好了?也許可以考慮集成一個人臉識別進來專門做預處理,不過這已經是谷歌API針對人臉裁剪過的了,按理說已經不需要這種操作了,隨意縮放也可能毀了圖,提升空間可能不大,有空嘗試做center_crop)
數據增強易犯錯誤:如果數據增強是對tensor操作,屬於計算圖,那麼計算準確度的時候圖像自身也發生偏轉,這是不應該的,所以需要一個額外的placeholder控制,計算準確率的時候關閉數據增強。當然,這個主要是影響tensorboard的觀測,不影響真實訓練過程。更何況提取圖片的過程和前向傳播的過程不在同一張計算圖上,所以,能確保測試集的數據流程沒有數據增強就可以了。
關於顏色方面的增強,顏色的考量限於彩圖數據集,fer2013本來就是單通道灰度圖,所以顏色方面沒有變更的必要。另一方面,可能不同顏色甚至代表了不同類別,比如藍貓和橘貓,顏色也是特徵的一部分,所以顏色的更改要慎重,但是人臉,也許能考慮,黑人白人,憤怒都是憤怒。但是!!!!!關鍵還是看輸入網絡時是什麼樣子,如果輸入網絡時有些特徵已經被消除了,就沒必要再動腦筋了,比如數值,如果已經歸一化,都是0~1,就沒必要改了!!還有圖片,雖然我的網絡蒐集的測試圖片是彩圖,但是神經網絡輸入前已經被預處理了,所以可以認爲顏色沒起作用。
L2正則化係數:
因爲仍然存在很大的“過擬合”現象,所以可以考慮加大L2正則化係數。
默認在FC1和FC2使用了0.004,改爲0.4。
明顯看到不同,對訓練集的擬合約束力很強,訓練兩萬多步,兩條曲線還是齊頭並進,
不過0.4可能過高了,過高可能導致擬合能力不足,訓練集準確率降低,測試集準確率也沒有因此而提升。最後反而達不到之前的效果,根據結果再調整一下。
平滑準確率64%開始平緩了,30k到35k時,正則化係數改成0.04。
最終也就是65%+,可見,雖然訓練集降低了很多,測試集也並不能獲得很大的提升,這並不是真正的過擬合問題,一味增加對weights的懲罰並不能解決問題,至少不能達到“訓練集準確率降低,測試集準確率就升高”這樣的預期。
降低再調,改爲0.01斷點續訓,改善不明顯了,需要從頭訓練或者改變基準學習率(前邊提到過學習率的監督和修改),時間有限,跑起來費時間,不測太細了。總的來說,相比初始的0.004,不能提升太多。
總之這個正則化的可調空間也不大,畢竟本例的核心問題不在過擬合上,正則化係數太高反而阻礙擬合!!
樣本均衡問題:
實際打印,訓練集七個分類的樣本個數如下。不是很均衡,整體還可以,第二類有點少:
[3995, 436, 4097, 7215, 4830, 3171, 4965]
specified_class_idx = 1
specified_class_weight = 10
other_class_weight= 1 # 其他類
y = tf.nn.softmax(y)#手動的話,需要softmax
ce1 = -specified_class_weight*tf.reduce_sum((y_[:,specified_class_idx] * tf.log(y[:,specified_class_idx])))#tf.clip_by_value(,1e-10,1.0)
ce2 = -other_class_weight*tf.reduce_sum((y_[:,:specified_class_idx] * tf.log(y[:,:specified_class_idx])))
ce3 = -other_class_weight*tf.reduce_sum((y_[:,specified_class_idx+1:] * tf.log(y[:,specified_class_idx+1:])))
cem = tf.reduce_mean(ce1+ce2+ce3)
可以考慮讓這個分類的loss乘以10。但是如果這個權重太大,比如指定分類乘以10,其他乘以1,再加上tf.log輸入的clip,整體的loss值,比以前大(其實不是很多)。具體數值可以適當調節,clip還是要用,方法可能要改,後邊會提。
如圖,使用了錯誤的y,cem丟了,給y加softmax(手動交叉熵要用softmax,而用接口直接softmax_cross_entropy_with_logits求的時候往往沒有這層處理。(也有說不用softmax而用sigmoid的?至少softmax可行,先用softmax!!分析,softmax會讓不同的輸出平均分擔1.0,而sigmoid是每一個單獨的類的輸出轉換成0~1之間,如果這個交叉熵是多分類,針對的都是具體的對應的分類,其實sigmoid可行。))
現在可以正常訓練了
其他設置,基本保持不變,dropout從0.3*0.3*0.3調到0.5*0.5*0.5。正則化係數暫時用0.04,相比初始的0.004,是10倍,但是現在交叉熵也一定程度變大(436/28709 = 1/66的數據的交叉熵變成10倍,總的數據倒不至於變成10倍),總之,這種相互關聯的東西,參數沒有死的。但是以目前的樣本情況,正則化係數改回0.004附近也許更好,感覺訓練集準確率提升過早減緩(前邊試驗過了)。訓練到一半,感覺過擬合了,適當降低一下dropout的keep率,適當提升L2正則化的係數就好了。
圖一,一次性設置L2係數過高,訓練過慢
圖二,keep率高,L2係數低,開始過擬合了。
降低keep率,提升L2懲罰,繼續訓練。
最後又Nan了!!!先加clip,爲了不影響訓練,用tf.reduce_max(y)當上限,而不是1.0!!!
clip的弊端,強行限制數值的傳遞,可能會影響網絡的訓練(舉例,a=100被砍成a=1,a=1和target=1的loss是0,則a=100永遠不能變成a=1)。最好是把網絡層的輸出調好,讓tensor處於一個比較好的區間,避免Nan的情況發生。(不行再把softmax換sigmoid,目前效果良好)
實驗結果:batch峯值71%+,平滑測試準確率67%~68%+(如果在合適時機早停,可能會獲得一個不錯的效果,當然,選不好也可能泛化更差),較之前有一個百分比的提升:
目前模型準確率:68%左右
驗證集準確率
測試集準確率測試
其他可持續做的優化:
網絡結構調整:可適當加深,寬度已經足夠寬,看是否有必要收窄一些,因爲太寬就容易過擬合,過擬合就要dropout。
激活函數,從ReLU改爲Leaky ReLU,原則上能改善梯度消失和神經元死亡的問題,但是不一定有直觀區別。
數據方面:網上還有一個fer2013 plus的git,數據不變,只是label改成了10類,原來是7類。這個號稱能提升識別結果,乍一看簡單,就是換個label數據,但是也有問題,我把這個數據集當10個label來看,進行訓練,最終的預測呢?怎麼轉成7個?所以得研究一下那個git的說明,好好看一下。
私有和公有驗證集的問題:訓練集是自動訓練調參用的,先不論。(公有)驗證集是人工調參使用的集合,測試集(私有驗證集)等於提交後的成績,用來二次修正。目前兩者區別不大,等到後期差別大的時候再做調整,減少過擬合,比如調整dropout。有時間的話,還可以使用交叉驗證等方法。
網絡結構:替換cliquenet等其他網絡結構。
用共有驗證集valid set訓練:目前都是純用train set,雖然加入valid set可能會過擬合導致test set性能下降,但是仍然可以做嘗試,之後再做調節。
對比手動BN層(沒區別按理說)
不用那個自動的BN,改一些參數。
學習率等優化
收窄全連接層(可考慮項):考慮網絡很容易就“學”會了所有的特徵,將訓練集識別到100%,可以考慮收緊全連接層寬度。
估計效果不會太好,因爲已經dropout到0.3*0.3*0.3了,直覺上提升空間不大,是數據集本身的差距。FC從512改到256,但是算了一下dropout的通過率,如果keep_prob用0.3,256的寬度估計不夠用了。反過來想,如果FC層變窄,那麼dropout也不用設置到畸形的0.3。
標籤優化(可考慮項):有一個fer2013_plus。其實我也很好奇標籤優化是怎麼做的,原來7類,他優化到10類,用10類的訓練,之後怎麼回到7類?fine-tuning?好像也沒必要,單純從conv層提取特徵來講,7個標籤和10個標籤應該無差。如果按10類訓練,再fine-tuning回去7類,FC層改變,好像繞一圈繞回來了,也多此一舉。
SVM-LOSS:根據論文內容和對準確率71%的冠軍模型的描述,應該是用了SVM-Loss和SVM-L2 Loss。這是最根本的區別。應該是餘下的最大提升空間了,但是這是Hinton門徒專門設計的模型,不是簡單改一個loss,暫時不想了。
預測與輸出:
各層抽象結果:
三個conv層各打印了三個通道,第一層是人臉按不同形式的抽象進行圖層剝離,第二層是結構抽象,但還有些人臉的關鍵特徵點,和一點輪廓,第三層基本就剩下光斑了。
單一圖片舉例,看三層卷積激活輸出:
原圖
relu1和pool1,pool1比relu1“和諧”一點,這是肯定的,池化縮短了舉例嘛,何況還是核3*3,stride(2,2)的,邊緣要細緻一些
保護視力,三指放大
第二層relu和pool,同樣的,pool處理過後看起來更像是個人臉,“特徵”更緊湊了,減少比必要的浪費
第三層relu
結果預測:
使用網絡圖片,進行長邊居中裁剪、尺寸縮放、灰度處理,喂入神經網絡,進行預測。(只裁剪到正方形,沒有臉部位置的裁剪!因爲沒法直接識別臉的位置,隨意裁剪可能會丟失面部特徵,除非加一個專門的人臉識別模塊。)
好像有點失敗啊(手動滑稽),可能有演技超越時代的關係,不過也有面部不正的原因,畢竟訓練數據幾乎都是大頭貼。
把面部擺正點,換幾個靠譜的,再來一發,因爲訓練集就是很近的人臉,我的預處理代碼又不夠智能(原數據集好歹也是谷歌API提取出來的),所以實際預測中,人臉佔比對結果影響還是很大的,多次手動裁剪調整圖片,出現不一樣的預測結果:
金館長全身和近景分別是sad和happy;
老虎伍茲左邊那張圖,之前是全身的,預測neutral,放大到近景截圖保存以後,正確預測了surprise;
紫薇是沒辦法搶救了;
圖10黑人可能是像素不行,對比度不行,怎麼調也是neutral;
柯南和表情包可能比較特殊了,識別的不準;
包拯和伍茲的哭喪臉都判斷成了Angry,算是識別失敗吧,如果把個別不好的圖排除,整體還是有點接近66%的正確率的。
最新:處理完樣本均衡後,模型變了,預測也有了新的結果,下面和老模型對比,順便也加上個別樣本的願景和大頭對比:
(圖一,新模型+遠景;圖二:新模型+個別大頭)
下面是詳細預測對比,各分類的評分,越大越好,預測結果是最大的,編號對應圖片順序:
黑人表情哥沒懸念,不需要再放大了(橫縱比的問題,默認居中裁剪剛剛好)
pic1: Angry :-3.53 Disgust :-11.54 Fear :-5.84 Happy :5.21 Sad :-3.25 Surprise :-5.25 Neutral :2.34
相比舊模型,老太太從Happy變成了Disgust,算是有點不準了。但是考慮到有裁剪的問題,所以用一張大頭照對比,大頭能正確預測
pic2: Angry :1.10 Disgust :-6.84 Fear :-1.15 Happy :1.13 Sad :-3.90 Surprise :-2.43 Neutral :-3.25
(新模型用了一張新圖)金館長全身Feary,大頭是Happy,變準了。
pic3: Angry :-7.13 Disgust :-26.87 Fear :-8.32 Happy :10.42 Sad :-10.23 Surprise :-2.03 Neutral :-4.32
包拯沒變化,Angry佔最大,其次是Fear和Sad
pic4: Angry :1.69 Disgust :-5.61 Fear :0.68 Happy :-4.63 Sad :0.89 Surprise :-4.64 Neutral :-0.32
爾康相比老模型的Happy,現在變成了Neutral,Sad還比Happy高一點。這圖爾康應該是“幸福”,也就是“Happiness”,不好說,表情太淡吧也許,確實不算笑的“happy”。裁剪成大頭,依然沒改變!
pic5: Angry :-3.56 Disgust :-10.76 Fear :-2.81 Happy :-1.30 Sad :-0.05 Surprise :-6.55 Neutral :3.45
pic5: Angry :-0.50 Disgust :-9.01 Fear :-2.16 Happy :-4.53 Sad :2.28 Surprise :-6.62 Neutral :3.00
紫薇終於從Happy變成Surprise,算好了一點吧。(Fear也算可以,Happy也許是演技問題~~)
pic6: Angry :-1.92 Disgust :-7.96 Fear :-0.79 Happy :0.31 Sad :-3.83 Surprise :2.29 Neutral :-1.87
伍茲左側的運動員:全身是Sad,大頭Surprise還是很準的!
pic7: Angry :-2.40 Disgust :-8.55 Fear :1.04 Happy :-2.09 Sad :-3.71 Surprise :3.49 Neutral :-2.83
伍茲是Angry,雖然是擊球,有點Sad的意思,不過還預測到了Happy,總的來說這個圖預測的不行。
pic8: Angry :1.46 Disgust :-4.52 Fear :-0.62 Happy :0.23 Sad :-0.65 Surprise :-5.01 Neutral :-1.97
相對舊模型,小孩也從Angry變成了sad,這個更準了。
pic9: Angry :-1.43 Disgust :-7.17 Fear :-0.29 Happy :-3.19 Sad :2.04 Surprise :-4.28 Neutral :2.01
黑人張嘴從Neutral變成Angry(從經驗看,應該是震驚,預測也不太對),大頭照是Fear,其次是Angry和Surprise。
pic10: Angry :0.34 Disgust :-5.37 Fear :1.74 Happy :-3.44 Sad :-1.74 Surprise :0.17 Neutral :-2.65
柯南看來是太難了,本來也畫的比較平靜臉
pic11: Angry :-2.69 Disgust :-8.54 Fear :-2.30 Happy :0.83 Sad :0.84 Surprise :-4.88 Neutral :1.84
相比老模型,表情包的Fear預測對了,不是Angry了。
pic12: Angry :-1.35 Disgust :-6.60 Fear :2.58 Happy :-3.06 Sad :-1.77 Surprise :-0.39 Neutral :-3.43
關於snapshot(ckpt)的優化——ensemble:
訓練了那麼多次,training和testing的accuracy忽高忽低,究竟我截取哪一次的才最合適?看運氣?看手速?這個問題就會自然衍生出一個方案——ensemble,把最後5次、10次(也不一定是特別連續的幾次,有些間隔可能更好)的ckpt都保存下來,取平均,也許會更好。實際操作方法:訓練時多留幾個ckpt,最後預測的時候把每個ckpt都加載進來跑一次預測結果,對結果取平均(細節沒想太多,比如5個ckpt,大概就是哪個label預測的多取哪個,如果5個ckpt預測不重樣,可能選一個分數最多的)。
ensembles實際上是一個更通用的方法,指的是把不同模型取一個平均,比如說,把AlexNet、VGG、googLeNet、ResNet等網絡的輸出取一個平均,但是所謂不同模型,也不一定是結構不一樣,同一個結構,不同的W矩陣構成,理論上都是不同的模型,所以,本例保存幾個snapshot(ckpt)來共同預測取平均,是一個非常可操作(相對於從頭訓練好幾個模型)的簡約ensemble方案。
改進空間:接一個人臉識別模塊,對數據進行預處理
現在只是手動裁剪測試數據,簡單實驗。實際使用不可能手動去裁剪。
================================================================================================
================================================================================================
================================================================================================
調試過程及操作等問題:
中間遇到了很多細節問題需要調試。主要是工程實現的問題。
工程都放在github了,調試放到ipynb
https://github.com/huqinwei/my_fer2013_model
更換環境,使用GPU進行訓練:
https://blog.csdn.net/huqinweI987/article/details/88107750
todo:另一個問題,雙GPU的訓練,默認是不帶的!
使用雙GPU:todo,實測發現另一GPU沒有運轉,雖然顯存佔用,溫度和轉速都沒上來。可能需要顯性的使用代碼進行雙GPU並行,tensorboard並沒有自動方案。(當然,只是訓練速度的差異)
準確率打印要避免受dropout影響:
比如直接sess.run([train_op,accuracy])這種偷懶寫法,其實得到的不是正確的準確率。
各種滑動平均,包括BN,保存與使用:
無論是普通weights的滑動平均ema,還是BN自帶的隱藏滑動平均,保存和恢復這裏都是個坑點。
尤其BN,BN是部分訓練,部分滑動。很明顯測試和預測時都應該用滑動的這部分,而這部分是不能train的,不包含在trainable。所以保存時也要注意。
很多坑都是不可感知的,不動腦不調試就漏掉了,但是好在tensorflow做了防呆設計,至少能保證你確實保存了變量,不然bn層是根本加載不出來的,會報錯(話說回來,就算防呆不能運行,也白訓練了不是~):
說起來簡單,但是自己摳清楚還是花了些時間的。如果不想摳,也不要緊,TF有很多機制,可能簡單的常規操作(複製正確的模板的前提下)不會碰到問題,但是最好理解並掌握。
其他都好說,但是有一個錯誤是沒有預防機制的,就是不更新滑動平均,一定要記得更新滑動平均!!
預測的時候,一定要把BN層的training項改回False,如果保持同樣的網絡設置,就不對了。(其實在一定訓練條件下,BN的training=False,反而看起來準確率更低)
https://github.com/huqinwei/tensorflow_demo/blob/master/batch_normalization_use_TF.ipynb
https://blog.csdn.net/huqinweI987/article/details/88071425
BN有兩種寫法,有一種黑盒直接使用,第二種需要自定義變量,並區分訓練和測試過程維護滑動變量。學習研究用第二種,模型圖方便用第一種。
因爲用了第一種,沒有顯式的去聲明變量,但是不代表沒有,把模型resotre並打印出來。每個BN層包括四個變量,所有數據的滑動平均mean和variance,經過mean和variance處理後,縮放係數gamma和平移係數beta,他們未經訓練的時候應該是剛好讓數據保持不變,訓練之後的效果是讓不同的輸入對應的輸出都在一個相同的分佈。
EMA更新了moving_mean和moving_variance
但是使用非訓練模式的BN,結果總是不太滿意,不知道是訓練不充足還是什麼其他原因,更新是肯定更新了,被EMA給更新了,受dropout影響?改成dropout 1.0 測試
ema的慣性方面,應該沒問題,batch_normalization默認momentum=0.99。理論上是沒問題,實際上效果不好,0.99還是太大了,可能要訓練很久才能真正有效,不然測試的時候準確率會很難看。0.5就明顯高了。
調試ema和bn,一些實際情況對比(因爲算力原因,沒有充分訓練,僅供觀察):
基礎學習率0.001
ema decay=0.5,dropout=1.0,1000~2000步,訓練正確率60+,測試正確率25%左右
ema decay=0.5,dropout=1.0,3600步,訓練正確率80+,測試正確率15%左右
基礎學習率0.0005
ema decay=0.99,dropout=0.5,0.5,0.5,1000~10000步,訓練正確率60~90,測試正確率15%左右
ema decay=0.99,dropout=1.0,1000~10000步,訓練正確率60~90,測試正確率15%左右
ema decay=0.99,dropout=0.6,0.6,0.6,37700步,訓練正確率90-,測試正確率45%左右
ema decay=0.99,dropout=0.6,0.6,0.6,49400步,訓練正確率90~95,測試正確率46%左右
由此可見,使用方法正確的前提下,加BN層,momentum=0.99,需要很多學習才能達到加BN之前的效果!
爲了區分dropout、BN各種不同情況,程序結構和參數要調整好:
BN的training參數可以用tf.bool通過placeholder傳入,這樣能保證訓練的同時用同樣的網絡進行驗證。
prob = tf.placeholder(tf.float32)
bn_training = tf.placeholder(tf.bool)
def forward(x, keep_prob,bn_training,bn_enable = False)
#訓練
sess.run([train_op, loss, accuracy, global_step, learning_rate],feed_dict={x: reshape_xs, y_: ys, prob:1.0, bn_training:True}
#使用訓練集驗證
sess.run([accuracy, merged], feed_dict={x: reshape_xs, y_: ys, prob: 1.0, bn_training:True})
#測試
sess.run([accuracy, test_merged], feed_dict={x: reshape_xs, y_: ys, prob: 1.0, bn_training:False})
BN的問題最多:
首先,要區分訓練狀態和測試狀態。
其次,要注意傳入的是python還是tensor的bool。如果是python的boolean,通過建立網絡傳入,那麼測試集和訓練集就沒法區分階段。
最後,BN層的更新,普通的變量被Adam更新,並被EMA調整,但是滑動平均卻並不會,雖然EMA也是“滑動平均”,但是EMA是找的trainable(嚴格的說是可設置的參數列表,但是沒意外的話直接使用所有trainable了),而這兩個變量不是trainable,不包括在內。
ema_op和update_ops缺一不可
train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss, global_step=global_step)
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
ema_op = ema.apply(tf.trainable_variables())
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
with tf.control_dependencies([train_step, ema_op]):
train_op = tf.no_op(name='train')
關於tfrecord中的數據類型的問題:
使用PIL(Pillow)的Image.fromarray轉換fer2013數據集報錯
fer2013,通過pandas的read_csv得到的是這個類型 '<i8'
找不到typestr的key:((1, 1), '<i8')
普通的,如果使用Image.open()打開一張圖,得到的是 '|u1',是可以在typemap中找到的。
這個類型其實就是描述內存存儲的,大小端、數據類型、字節數。
PIL支持的就只有這些類型:
Image._fromarray_typemap is : {((1, 1), '|u1'): ('L', 'L'), ((1, 1), '|i1'): ('I', 'I;8'), ((1, 1), '<u2'): ('I', 'I;16'), ((1, 1), '>u2'): ('I', 'I;16B'), ((1, 1), '<i2'): ('I', 'I;16S'), ((1, 1), '>i2'): ('I', 'I;16BS'), ((1, 1), '<u4'): ('I', 'I;32'), ((1, 1), '>u4'): ('I', 'I;32B'), ((1, 1), '<i4'): ('I', 'I'), ((1, 1), '>i4'): ('I', 'I;32BS'), ((1, 1), '<f4'): ('F', 'F'), ((1, 1), '>f4'): ('F', 'F;32BF'), ((1, 1), '<f8'): ('F', 'F;64F'), ((1, 1), '>f8'): ('F', 'F;64BF'), ((1, 1, 2), '|u1'): ('LA', 'LA'), ((1, 1, 3), '|u1'): ('RGB', 'RGB'), ((1, 1, 4), '|u1'): ('RGBA', 'RGBA')}
python3.6,Pillow更新到最新,沒解決問題
PILLOW_VERSION = '4.2.1'
(現在也想明白了,和環境版本關係不大,人家庫就這樣設計的,既然Image存的是圖像,像素值也不需要64位整型)
array的來源就是split的字符串,也不該有什麼綁定的屬性自動傳入array了(最開始以爲和編碼格式之類的有關係,所以跑偏了)
因爲是array帶的屬性,所以去看np.array的說明,這個應該就是array的數據類型,默認情況下,應該按滿足最低需求的來準備。寫的是dtype=int,可能因爲系統是64的,就算成64,自己主動指定dtype=np.int32(float64也支持),解決問題。。但是我要用到的樣本的數值上限,只是255,所以類型隨便用。後邊灰度轉換直接變8位。
其他要注意的,bytes確實是按8位存數據,並且只支持8位數據,轉bytes存儲前,灰度轉換,數據已經不大於255.
TFRecord:
其他都是小問題,這個最重要,因爲數據給不出來,訓練過程根本無法進行,不搞清楚數據的每一步形式的話很不容易發現問題,所以把tfrecord的各種操作好好鞏固一下。
數據方面容易有坑,有好幾個可能的問題,類型不匹配,根本讀不出去,不能訓練;bytes的轉化過程中把數據都丟掉,不過本例不算是個問題,因爲轉灰度了,灰度255以上都會強制255,最後只要decode的時候確保使用uint8就行了。
不管有沒有灰度轉換,留意,decode要和tobytes的源頭類型一樣,因爲這個bytes是字節序列,和C語言的內存操作一個道理。
在restore模型時遇到的問題:
restore主要用於恢復global_step和變量,斷點續訓;還有在測試階段提取出EMA變量用於測試。
先說重點:要指定var_list,每個變量都要有自己的名字!!!如果給模型改過變量名,之前的model就不能直接用了(想用的話需要找出對應關係,倒是可以改名映射)!
細說,restore會遇到兩個問題:找不到對應的變量,形狀不對。
前者是現在定義的變量比model存的變量多了(指定var_list=ema.variables_to_restore(),能減少一部分錯誤,按理說在ema範圍內變量足夠就行了,新定義的無關變量不會去強行匹配)
後者的話,主要可能是因爲沒給所有變量都命名,比如我之前所有的bias都叫Variable,然後自動重命名,這樣萬一新定義的模型,哪怕順序不一樣,後綴就變了,就可能出錯。
我的一個報錯是:兩個變量錯位了,一個想要64給0,一個想要0給64。(又或者是512和128不匹配)
InvalidArgumentError: Assign requires shapes of both tensors to match. lhs shape= [64] rhs shape= []
[[Node: save/Assign = Assign[T=DT_FLOAT, _class=["loc:@Variable"], use_locking=true, validate_shape=true, _device="/job:localhost/replica:0/task:0/device:CPU:0"](Variable, save/RestoreV2)]]
InvalidArgumentError (see above for traceback): Assign requires shapes of both tensors to match. lhs shape= [64] rhs shape= []
關於模型的save/restore,最煩的問題就是變量的自動重命名,這一點主要體現在使用ipynb調試,如果每次都單獨開一個進程,且保證沒有多餘動作——比如不小心多調用一次網絡定義——問題應該不大。或者,用get
最好所有變量都命名,圖中兩段,第一個是血淚教訓,好不容易扔一晚上的模型,用不了了(其實可以手動指定映射關係,但是太亂就不想弄了,參見ema筆記和demo)。第二個是規範的能用的。
restore基本練習:https://github.com/huqinwei/tensorflow_demo/save_model_practice.ipynb
示例和調試筆記:
https://blog.csdn.net/huqinweI987/article/details/88241776
https://github.com/huqinwei/DL_demos/kaggle_face_expression_recognition_challenge/my_practice/ema_restore_practice.ipynb
測試準確率時內存溢出:
一次性把測試集三千多數據都讀出來,我的7G虛擬機爆了
tensorflow.python.framework.errors_impl.ResourceExhaustedError: OOM when allocating tensor with shape[3589,48,48,64]
解決方法,分批次測試,取平均值。如下:把數據改小,加個循環和平均。因爲取數據用的shuffle,加上不能整除的零散部分也向上取整了,所以數據有微小變動:
訓練一半改學習率會發生什麼?而這個模型用的是動態學習率
ckpt加載只有global_step,,是用基數和global_step計算得到,故而學習率可以隨意更改,按相同的步數折算就好了。這樣可以做的事情就多一些,比如,改了模型,斷點續訓,如果學習率已經衰減到很低,可能修改就看不到效果了,可以調大學習率基數,提升實際學習率,以後學習率仍然能衰減,最終仍然能收斂,這樣的好處是不用重新訓練。
一個測試:
線程協調器問題:
生產者和reader的定義一定要早於線程協調器啓動!!不然可能卡住,什麼都讀不出來!
因爲定義操作被get_tfrecord()封裝起來了,容易看走眼。tf這個線程協調器相對來說有點透明,有時候出了問題不便於觀察,害的我把一套數據流程都調了一遍,最後發現就是函數封裝導致執行初始化晚了。。
函數封裝有生產者的定義
Windows路徑:
爲了充分利用GPU,轉到windows,配置文件的路徑要變,注意windows的斜槓,會成爲轉義字符,最好保持linux的寫法。
最坑的是這種,他拼接的時候,又會加錯斜槓,主動加斜槓改正
訓練同時運行單獨的測試程序:
GPU會不夠,限制只用CPU完成。
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = '-1'
各種運算圖和各種階段各種流程的兼容問題:
很多前邊已經提到,像BN操作,像數據增強操作,像訓練集和測試集的準確率,很多,需要搞清邏輯,處理好關係。
Nan問題和clip注意事項:
像樣本均衡時的權重cross entropy,使用clip,不過也要注意情形,最好把相關變量都跟蹤好,看看會不會遇到其他問題,比如因爲clip而影響訓練效果。
簡單說,就是,一個tensor,比如是a = 100,被clip到10,那麼他和target=10的loss就是0,自然無法train。
生產隊列報錯:
主要原因可能圖片或者數據不對,我這裏是因爲做測試,把tfrecord動過。
TFRecordReader "OutOfRangeError (see above for traceback): RandomShuffleQueue '_1_shuffle_batch/random_shuffle_queue' is closed and has insufficient elements (requested 1, current size 0)"
打印graph
最終網絡圖
這是一張新的graph,已經加了很多東西,比如BN層的參數,還有各種打印
全景圖
數據增強
下邊的不是最新的graph,但是主體流程相同,沒有太多雜項,反而更清晰一些
下圖是前向傳播結構,bias_add支持同名,如果不自己改名,圖不是一條線,是斷的,通過BiasAdd銜接的。
這是不自定義名的效果,會形成一個數組,圖形會斷掉,也能通過紅色標記的斷點觀察,不過不直觀。
給bias_add單獨命名,保證圖形連續
除了bias_add,multiply、matmul都是如此,統統改完。
改完所有重名操作,無EMA情況下的前向+反向網絡結構,無EMA的圖更清晰一些。
有ema的情形,加了一堆判斷。圖形也不完整,有重名
而且這個操作是隱藏了的,這個對整體結構圖影響不大,應該是一個原地閉路,現在打印成開路了。
在tensorboard可以手動解開,就是看着更不舒服了。。。
其他操作失誤與注意事項:
雖然定義時候不執行,但是merge也是有定義順序的,如果先定義merge,後定義其他的summary,可能不會被包含進去。
使用ipynb調試時,file句柄沒有及時close掉,最後把train的tfRecord寫壞了,而這個0字節的tfrecord,在這種生產者模式下是不報錯的,本來也不報錯,只是負責讀數據,沒數據了就不讀了,沒毛病。。。
tensorboard刪除文件夾新建文件夾的操作,只有不斷點續訓時纔可用,不然圖像不全。
------------------------------------
--------------------------------------\
下載:
https://download.csdn.net/download/huqinwei987/11830851
網盤
鏈接:https://pan.baidu.com/s/1GeaQFrn2eM5ZAbFPnQQ4eA
提取碼:2qma
git鏈接:
https://github.com/huqinwei/my_fer2013_model