Keras梯度累積優化器 用時間換取效果

2021-09-25 04:28:31 字數 3875 閱讀 5264

現在keras中你也可以用小的batch size實現大batch size的效果了——只要你願意花nn倍的時間,可以達到nn倍batch size的效果,而不需要增加顯。

npl:在一兩年之前,nlp (natural language processing) 是人工智慧(ai)的乙個子領域。

oom:out-of-memory 表示記憶體溢位

cv:關於計算機視覺(computervision, cv)

bert:bidirectional encoder representation from transformers,即雙向transformer的encoder。

gpt-2:「generative pretrained transformer 2」的簡稱,即生成預訓練transformer

xlnet:廣義自回歸方法xlnet,它既利用了ar語言建模的優點,又避免了ae的侷限性。

fine-tuning :是乙個trick(技巧),在遷移學習中有所涉及,但不僅僅出現在遷移學習中,指對引數進行微調。

seq2seq:模型是rnn最重要的乙個變種:n vs m(輸入與輸出序列長度不同),這種結構又叫encoder-decoder模型。

attention機制:又稱為注意力機制,顧名思義,是一種能讓模型對重要資訊重點關注並充分學習吸收的技術,它不算是一 個完整的模型,應當是一種技術,能夠作用於任何序列模型中。

transformer:就是乙個公升級版的seq2seq,也是由乙個encoder(編碼器)和乙個decoder(解碼器)組成的。

做nlp任務都不用怎麼擔心oom問題,因為相比cv領域的模型,其實大多數nlp模型都是很淺的,極少會視訊記憶體不足。幸運或者不幸的是,bert出世了,然後火了。bert及其後來者們(gpt-2、xlnet等)都是以足夠龐大的transformer模型為基礎,通過足夠多的語料預訓練模型,然後通過fine tune的方式來完成特定的nlp任務。

即使你很不想用bert,但現在的實際情況是:你精心設計的複雜的模型,效果可能還不如簡單地fine tune一下bert好。所以不管怎樣,為了跟上時代,總得需要學習一下bert的fine tune。問題是「不學不知道,一學嚇一跳」,只要任務稍微複雜一點,或者句子長度稍微長一點,視訊記憶體就不夠用了,batch size急劇下降——32?16?8?一跌再跌都是有可能的。

這不難理解,transformer基於attention,而attention理論上空間和時間複雜度都是o(n2)o(n2),雖然在算力足夠強的時候,attention由於其並行性還是可以表現得足夠快,但是視訊記憶體佔用量是省不了了,o(n2)o(n2)意味著當你句子長度變成原來的2倍時,視訊記憶體占用基本上就需要原來的4倍,這個增長比例肯定就容易oom了~

而更不幸的訊息是,大家都在fine tune預訓練bert的情況下,你

batch_size=8可能比別人batch_size=80低好幾個千分點甚至是幾個百分點

,顯然這對於要刷榜的讀者是很難受的。難道除了加顯示卡就沒有別的辦法了嗎?

有!通過梯度快取和累積的方式,

用時間來換取空間

,最終訓練效果等效於更大的batch size。因此,只要你跑得起batch_size=1,只要你願意花

nn倍的時間,就可以跑出

nn倍的batch size了。

梯度累積的思路,在之前的文章《「讓keras更酷一些!」:小眾的自定義優化器》已經介紹了,當時稱之為「軟batch(soft batch)」,本文還是沿著主流的叫法稱之為「梯度累積(accumulate gradients)」好了。所謂梯度累積,其實很簡單,我們梯度下降所用的梯度,實際上是多個樣本算出來的梯度的平均值,以batch_size=128為例,你可以一次性算出128個樣本的梯度然後平均,我也可以每次算16個樣本的平均梯度,然後快取累加起來,算夠了8次之後,然後把總梯度除以8,然後才執行引數更新。當然,必須累積到了8次之後,用8次的平均梯度才去更新引數,不能每算16個就去更新一次,不然就是batch_size=16了。

剛才說了,在之前的文章的那個寫法是有誤的,因為用到了

k.switch(cond, k.update(p, new_p), p)
來控制更新,但事實上這個寫法不能控制更新,因為k.switch只保證結果的選擇性,不保證執行的選擇性,事實上它等價於

cond * k.update(p, new_p) + (1 - cond) * p
也就是說不管cond如何,兩個分支都是被執行了。事實上keras或tensorflow「幾乎」不存在只執行乙個分支的條件寫法(說「幾乎」是因為在一些比較苛刻的條件下可以做到),所以此路不通。

不能這樣寫的話,那只能在「更新量」上面下功夫,如前面所言,每次算16個樣本的梯度,每次都更新引數,只不過8次中有7次的更新量是0,而只有1次是真正的梯度下降更新。很幸運的是,這種寫法還可以無縫地接入到現有的keras優化器中,使得我們不需要重寫優化器!詳細寫法請看:

具體的寫法無外乎就是一些移花接木的程式設計技巧,真正有技術含量的部分不多。關於寫法本身不再細講,如果有疑問歡迎討論區討論。

(注:這個優化器的修改,使得小batch size能起到大batch size的效果,前提是模型不包含batch normalization,因為batch normalization在梯度下降的時候必須用整個batch的均值方差。所以如果你的網路用到了batch normalization,想要準確達到大batch size的效果,目前唯一的方法就是加視訊記憶體/加顯示卡。)

至於用法則很簡單:

opt = accumoptimizer(adam(), 10) # 10是累積步數

model.compile(loss='mse', optimizer=opt)

model.fit(x_train, y_train, epochs=10, batch_size=10)

這樣一來就等價於batch_size=100的adam優化器了,代價就是你跑了10個epoch,實際上只相當於batch_size=100跑了1個epoch,好處是你只需要用到batch_size=10的顯存量。

可能讀者想問的乙個問題是:你怎麼證明你的寫法生效了?也就是說你怎麼證明你的結果確實是batch_size=100而不是batch_size=10?為此,我做了個比較極端的實驗,**在這裡:

/blob/master/mnist_mlp_example.py

**很簡單,就是用多層mlp做mnist分類,用adam優化器,fit的時候batch_size=1。優化器有兩個選擇,第乙個是直接adam(),第二個是accumoptimizer(adam(), 100)

如果是直接adam(),那loss一直在0.4上下徘徊,後面loss越來越大了(訓練集都這樣),val的準確率也沒超過97%;

如果是accumoptimizer(adam(), 100),那麼訓練集的loss越來越低,最終降到0.02左右,val的最高準確率有98%+;

最後我比較了直接adam()但是batch_size=100的結果,發現跟accumoptimizer(adam(), 100)但是batch_size=1時表現差不多。

這個結果足以表明寫法生效了,達到了預期的目的。如果這還不夠說服力,我再提供乙個訓練結果作為參考:

在某個bert的fine tune實驗中,直接用adam()加batch_size=12,我跑到了70.33%的準確率;我用accumoptimizer(adam(), 10)加batch_size=12(預期等效batch size是120),我跑到了71%的準確率,提高了0.7%,如果你在刷榜,那麼這0.7%可能是決定性的。

終於把梯度累積(軟batch)正式地實現了,以後用bert的時候,也可以考慮用大batch_size了哈~

Keras框架優化器引數

keras後端基於tensorflow theano以及cntk編寫而成,keras中文文件參考 1.從keras.models庫引入sequential類 2.定義sequential類的物件model 3.向model裡add每一層 隱藏層,啟用層等 4.逐層加完之後對model進行compil...

梯度下降優化器(1)

編譯神經網路,指定優化器 損失函式,以及評估指標 ann.compile optimizer adam 優化器 loss binary crossentropy 損失函式 metrics acc 評估指標其中adam就是乙個優化器。優化器的引入和神經網路的特點有十分密切的關係。在之前的學習中我們已經...

keras設定學習率 優化器的用法

優化器 optimizer 是編譯 keras 模型的所需的兩個引數之一 from keras import optimizers model sequential model.add dense 64,kernel initializer uniform input shape 10,model....