因為AI這兩年的火爆,大家拿著錘子到處找釘子,錘子當(dāng)然也砸到了我頭上,有很多做業(yè)務(wù)的同事嘗試通過AI的方法解決需要一些很復(fù)雜的業(yè)務(wù)邏輯算法,同時需要很多參數(shù)組合才能搞定的問題。但因為都是非科班出身也沒有系統(tǒng)學(xué)習(xí),所以遇到不少問題,所以在這里一一列出來,并且持續(xù)更新,希望能夠總結(jié)出一些經(jīng)驗,在后續(xù)的應(yīng)用中能夠跳過這些坑,把更多精力集中在數(shù)據(jù)和業(yè)務(wù)問題上。
關(guān)于神經(jīng)網(wǎng)絡(luò)為什么不工作,有一篇非常實用的指南,在訓(xùn)練過程中遇到問題可以首先參考這個指南。傳送門。
問題1、沒有激活函數(shù),你是認(rèn)真的嗎
真的會發(fā)生這樣的問題,尤其是直接用tensorflow寫模型的時候。同事前幾天寫了段代碼,搭了一個單隱層的模型來近似一個產(chǎn)品中的算法,但是怎么訓(xùn)練都不收斂,按說那個算法是復(fù)雜,但也沒有必要整一個十好幾層的模型來模擬,嘗試了種種手段也還是沒用,最后仔細(xì)一看模型代碼,所有層都沒有激活函數(shù),相當(dāng)于費很大勁寫了個線性回歸還要擬合出產(chǎn)品算法(/攤手)。所以對于大多數(shù)的應(yīng)用,不涉及復(fù)雜的網(wǎng)絡(luò)結(jié)構(gòu)或初始化、loss函數(shù)的,就用keras吧,畢竟人生苦短。
當(dāng)然,無激活函數(shù),也就是單位激活函數(shù),在一些場景下也會使用,通常這種使用能夠帶來減少訓(xùn)練參數(shù)的好處。所以除非是有意的設(shè)計來簡化網(wǎng)絡(luò),否則激活函數(shù)可不能忘掉。
問題2、還是激活函數(shù),選對了沒有
很多指南上也都說過,一般情況下,分類器隱層的激活函數(shù)用relu,輸出層用sigmoid或softmax就八九不離十了,但是這里也有坑。許多的例子中使用的數(shù)據(jù)集是離我們面臨的問題比較遠(yuǎn)的,最近被問到一個回歸任務(wù)的問題,由于是原型嘗試沒有做數(shù)據(jù)規(guī)范化,訓(xùn)練的時候發(fā)現(xiàn)模型不收斂,或是不管多大的batch都在一個epoch中間波動非常大,打印出來才發(fā)現(xiàn)輸出值區(qū)間和目標(biāo)值區(qū)間都差老遠(yuǎn),很明顯是激活函數(shù)限制了輸出范圍。所以選激活函數(shù),尤其是回歸任務(wù)或者autoencoder,在寫最后一層的時候停一下,回憶回憶每個激活函數(shù)的范圍,如果都不合適,那就做一下數(shù)據(jù)預(yù)處理。
激活函數(shù)還有一個問題就是sigmoid函數(shù)用在隱層的時候,要注意梯度消失的問題,sigmoid在x=±5的附近就基本沒有梯度了,如果輸入值很大或權(quán)重初始化函數(shù)比較另類,那妥妥的訓(xùn)練效率會比較差。
問題3、損失函數(shù)和輸出層激活函數(shù),請尊重原配
對于sigmoid輸出單元,如果使用非交叉熵?fù)p失函數(shù),損失函數(shù)就會在sigmoid飽和時飽和,從而導(dǎo)致梯度消失。而最大似然給出的交叉熵?fù)p失函數(shù)中的log能夠抵消sigmoid中的exp,因此sigmoid要搭配交叉熵給出的損失函數(shù)。
對數(shù)似然之外的許多目標(biāo)函數(shù)對softmax函數(shù)不起作用,具體來說,那些不使用對數(shù)來抵消softmax中的指數(shù)的目標(biāo)函數(shù),當(dāng)指數(shù)的變量取非常小的負(fù)值時會造成梯度消失,從而無法學(xué)習(xí)。對于softmax函數(shù),當(dāng)輸入值之間的差異變得極端時,輸出值會飽和。當(dāng)softmax飽和時,基于softmax的許多代價函數(shù)也會飽和,除非它們能夠轉(zhuǎn)化飽和的激活函數(shù),也就是說交叉熵類的損失函數(shù)能和輸出層的softmax搭配。
線性單元不會飽和,所以和relu系列的激活函數(shù),不挑損失函數(shù)。另外對于relu,在初始化參數(shù)時,可以將b的所有元素設(shè)置成一個小小的正值,這使得relu在初始時就對訓(xùn)練集中的大多數(shù)輸入呈現(xiàn)激活狀態(tài),并且允許導(dǎo)數(shù)通過。
問題4、學(xué)習(xí)率,初始化模型時不要改默認(rèn)參數(shù)
學(xué)習(xí)率基本上是新手最想調(diào)整的一個高參,容易理解,改起來也最方便。但是在初始化網(wǎng)絡(luò)的時候,還是不要動人家的默認(rèn)值,除非你對這個問題的損失函數(shù)空間和形態(tài)非常了解(那就不用黑盒算法了),在其他方面不出問題的情況下,學(xué)習(xí)率默認(rèn)值基本上能讓網(wǎng)絡(luò)有一個基本靠譜的收斂,在對問題的收斂水平、精度有一定認(rèn)識之后再去修改默認(rèn)值也不遲。默認(rèn)值一般在1e-3到1e-4的范圍內(nèi),對于MNIST數(shù)據(jù)集,不同的學(xué)習(xí)率對應(yīng)的loss變化趨勢如下,從圖中也可以看到,使用默認(rèn)的學(xué)習(xí)率能得到一個基本夠看的結(jié)果:

問題5、網(wǎng)絡(luò)規(guī)模應(yīng)該如何選擇,參考如何將大象裝冰箱
網(wǎng)絡(luò)規(guī)模的選擇是個頭疼的問題,沒有確定的指導(dǎo)思想或策略,花書中也有具體的篇幅講這個:
“萬能近似定理表明,一個前饋神經(jīng)網(wǎng)絡(luò)如果具有線性輸出層和至少一層具有任何一種“擠壓”性質(zhì)的激活函數(shù)的隱藏層,只要給予網(wǎng)絡(luò)足夠數(shù)量的隱藏單元,它可以以任意的精度來近似任何從一個有限空間到另一個有限空間的Borel可測函數(shù)。前饋網(wǎng)絡(luò)的導(dǎo)數(shù)也可以任意好地來近似函數(shù)的導(dǎo)數(shù)”
“具有單層的前饋網(wǎng)絡(luò)足以表示任何函數(shù),但是網(wǎng)絡(luò)層可能大得不可實現(xiàn),并且可能無法正確地學(xué)習(xí)和泛化”
什么意思呢?理論上單層網(wǎng)絡(luò)干啥都夠,但是隨著要擬合的目標(biāo)函數(shù)越來越復(fù)雜,需要的神經(jīng)元和參數(shù)是指數(shù)級增加的。所以我們要視問題的復(fù)雜度來確定網(wǎng)絡(luò)的層數(shù),問題復(fù)雜度是個什么呢,其實就是“要把大象裝冰箱,總共分幾步”。我們可以結(jié)合對于問題的理解,感受下要擬合的這個目標(biāo)函數(shù)都干了些什么事,分了幾步,比如大象裝冰箱這個問題它就分了三步,三層左右應(yīng)該就差不多。更深層的網(wǎng)絡(luò)通常能夠?qū)γ恳粚邮褂酶俚膯卧獢?shù)和更少的參數(shù),并且經(jīng)常容易泛化到測試集,但是通常也更難以優(yōu)化。
問題6、南轅北轍,訓(xùn)練測試集劃分要小心
還是同事在擬合產(chǎn)品算法時遇到的問題,拿到的數(shù)據(jù)是一個持續(xù)數(shù)小時的記錄,系統(tǒng)每隔xxx ms會報一條消息,所以有幾萬條的樣本。他在訓(xùn)練的時候沒有做數(shù)據(jù)的shuffle,僅僅按照時間維度劃分了訓(xùn)練集和測試集,結(jié)果發(fā)現(xiàn)很早就出現(xiàn)了“過擬合”——測試集誤差很快就上去了,而且還沒收斂到理想的水平就上去了。在這里的問題就在于劃分訓(xùn)練集測試集的方式,其實從問題背景出發(fā),這個數(shù)小時的記錄在時間變化的過程中所反映的環(huán)境也肯定是在變化的,所以訓(xùn)練集和測試集所代表的環(huán)境不一定會一樣。一定要有一個mindset,要保證train-dev-test三個集合都是同分布的,脫離了這個前提,訓(xùn)練就是做樣子。所以要shuffle數(shù)據(jù),要保證三個集合同分布,或者最好是劃分完之后train和dev用k-fold的方法來訓(xùn)練。
不對數(shù)據(jù)進(jìn)行shuffle還有一個問題就是,我們拿到的數(shù)據(jù)很多是順序數(shù)據(jù),在劃分batch之后大多數(shù)batch對應(yīng)的目標(biāo)值很可能都是單一的,這樣在訓(xùn)練過程中會出現(xiàn)很大的波動,甚至?xí)?dǎo)致完全無法收斂
問題7、數(shù)據(jù)特征的維度夠不夠
網(wǎng)絡(luò)是通過輸入的特征來學(xué)習(xí)的,所以要解決一個問題,業(yè)務(wù)經(jīng)驗在這里十分重要,即使是E2E不考慮特征抽取的神經(jīng)網(wǎng)絡(luò),也需要確保所需要的信息全都輸入進(jìn)來了。在考慮數(shù)據(jù)特征的時候,可以分為兩個階段:第一個階段需要業(yè)務(wù)專家的深度參與,從傳統(tǒng)業(yè)務(wù)的角度看,做這個事情需要哪些輸入,能從什么渠道獲取這些輸入等,這些必須要進(jìn)行一個完備的分析;第二個階段就是對這些輸入進(jìn)行必要的處理,以便后續(xù)設(shè)計網(wǎng)絡(luò)結(jié)構(gòu),當(dāng)然輸入形式、網(wǎng)絡(luò)結(jié)構(gòu)、網(wǎng)絡(luò)性能這是一個反饋的閉環(huán),需要不斷進(jìn)行更新以達(dá)到最好的目標(biāo)性能。
問題8、性能標(biāo)準(zhǔn)是什么,keras自帶的acc合適不
使用keras訓(xùn)練時,一般我們會在compile里定義相關(guān)的metrics,有時候犯個懶就直接用系統(tǒng)給的acc了。但是在用了幾回吃了幾回土之后發(fā)現(xiàn),keras自帶的acc,實際上對分類任務(wù)更友好一些,對于多維輸出的回歸問題(我所在的行業(yè)涉及的大部分都是這類問題),acc就很容易把人整蒙圈了。有時候訓(xùn)練半天,看著acc一直在0.2左右晃蕩,就開始懷疑人生了,但是看著mse loss的值感覺也不應(yīng)該這么離譜,結(jié)果自己結(jié)合業(yè)務(wù)定義一個性能函數(shù),發(fā)現(xiàn)已經(jīng)達(dá)到九十好幾的精度了。所以對于回歸問題,尤其是多維的、稀疏的場景,一開始就要想好性能指標(biāo)怎么定義,不要犯懶,自己手寫一個,這樣往后優(yōu)化模型,目標(biāo)感也會更強(qiáng)。
問題9、最好把Model的生成封裝在一個函數(shù)中
還有一個實用的經(jīng)驗是對于模型初始化后調(diào)參和ensemble模型優(yōu)化的場景,把模型的定義、構(gòu)建等放在一個函數(shù)中,每次使用不同參數(shù)、不同數(shù)據(jù)集時調(diào)用函數(shù)生成模型再進(jìn)行訓(xùn)練,這樣可以避免使用上次訓(xùn)練后遺留的網(wǎng)絡(luò)權(quán)重和偏置等信息,避免因為歷史遺留參數(shù)而導(dǎo)致收斂問題或無用的性能提升。具體的原因和案例可以參考《為什么Recompile之后你的網(wǎng)絡(luò)不收斂了》