“让Keras更酷一些!”:分层的学习率和自由的梯度
作者丨蘇劍林
單位丨廣州火焰信息科技有限公司
研究方向丨NLP,神經(jīng)網(wǎng)絡(luò)
個(gè)人主頁(yè)丨kexue.fm
高舉“讓 Keras 更酷一些!”大旗,讓 Keras 無(wú)限可能。
今天我們會(huì)用 Keras 做到兩件很重要的事情:分層設(shè)置學(xué)習(xí)率和靈活操作梯度。?
首先是分層設(shè)置學(xué)習(xí)率,這個(gè)用途很明顯,比如我們?cè)?fine tune 已有模型的時(shí)候,有些時(shí)候我們會(huì)固定一些層,但有時(shí)候我們又不想固定它,而是想要它以比其他層更低的學(xué)習(xí)率去更新,這個(gè)需求就是分層設(shè)置學(xué)習(xí)率了。
對(duì)于在 Keras 中分層設(shè)置學(xué)習(xí)率,網(wǎng)上也有一定的探討,結(jié)論都是要通過(guò)重寫(xiě)優(yōu)化器來(lái)實(shí)現(xiàn)。顯然這種方法不論在實(shí)現(xiàn)上還是使用上都不友好。?
然后是操作梯度。操作梯度一個(gè)最直接的例子是梯度裁剪,也就是把梯度控制在某個(gè)范圍內(nèi),Keras 內(nèi)置了這個(gè)方法。但是 Keras 內(nèi)置的是全局的梯度裁剪,假如我要給每個(gè)梯度設(shè)置不同的裁剪方式呢?甚至我有其他的操作梯度的思路,那要怎么實(shí)施呢?不會(huì)又是重寫(xiě)優(yōu)化器吧??
本文就來(lái)為上述問(wèn)題給出盡可能簡(jiǎn)單的解決方案。
分層的學(xué)習(xí)率
對(duì)于分層設(shè)置學(xué)習(xí)率這個(gè)事情,重寫(xiě)優(yōu)化器當(dāng)然是可行的,但是太麻煩。如果要尋求更簡(jiǎn)單的方案,我們需要一些數(shù)學(xué)知識(shí)來(lái)指導(dǎo)我們?cè)趺催M(jìn)行。?
參數(shù)變換下的優(yōu)化
首先我們考慮梯度下降的更新公式:
其中 L 是帶參數(shù) θ 的 loss 函數(shù),α 是學(xué)習(xí)率,是梯度,有時(shí)候我們也寫(xiě)成。記號(hào)是很隨意的,關(guān)鍵是理解它的含義。
然后我們考慮變換 θ=λ?,其中 λ 是一個(gè)固定的標(biāo)量,? 也是參數(shù)。現(xiàn)在我們來(lái)優(yōu)化 ?,相應(yīng)的更新公式為:
其中第二個(gè)等號(hào)其實(shí)就是鏈?zhǔn)椒▌t。現(xiàn)在我們?cè)趦蛇叧松?λ,得到:
對(duì)比 (1) 和 (3),大家能明白我想說(shuō)什么了吧:
在 SGD 優(yōu)化器中,如果做參數(shù)變換 θ=λ?,那么等價(jià)的結(jié)果是學(xué)習(xí)率從 α 變成了。
不過(guò),在自適應(yīng)學(xué)習(xí)率優(yōu)化器(比如 RMSprop、Adam 等),情況有點(diǎn)不一樣,因?yàn)樽赃m應(yīng)學(xué)習(xí)率使用梯度(作為分母)來(lái)調(diào)整了學(xué)習(xí)率,抵消了一個(gè) λ,從而(請(qǐng)有興趣的讀者自己推導(dǎo)一下):
在 RMSprop、Adam 等自適應(yīng)學(xué)習(xí)率優(yōu)化器中,如果做參數(shù)變換 θ=λ?,那么等價(jià)的結(jié)果是學(xué)習(xí)率從 α 變成了 λα。
移花接木調(diào)整學(xué)習(xí)率
有了前面這兩個(gè)結(jié)論,我們就只需要想辦法實(shí)現(xiàn)參數(shù)變換,而不需要自己重寫(xiě)優(yōu)化器,來(lái)實(shí)現(xiàn)逐層設(shè)置學(xué)習(xí)率了。?
實(shí)現(xiàn)參數(shù)變換的方法也不難,之前我們?cè)?strong>《 “讓Keras更酷一些!”:隨意的輸出和靈活的歸一化》[1] 一文討論權(quán)重歸一化的時(shí)候已經(jīng)講過(guò)方法了。因?yàn)?Keras 在構(gòu)建一個(gè)層的時(shí)候,實(shí)際上是分開(kāi)了 build 和 call 兩個(gè)步驟,我們可以在 build 之后插一些操作,然后再調(diào)用 call 就行了。?
下面是一個(gè)封裝好的實(shí)現(xiàn):
class?SetLearningRate:
????"""層的一個(gè)包裝,用來(lái)設(shè)置當(dāng)前層的學(xué)習(xí)率
????"""
????def?__init__(self,?layer,?lamb,?is_ada=False):
????????self.layer?=?layer
????????self.lamb?=?lamb?#?學(xué)習(xí)率比例
????????self.is_ada?=?is_ada?#?是否自適應(yīng)學(xué)習(xí)率優(yōu)化器
????def?__call__(self,?inputs):
????????with?K.name_scope(self.layer.name):
????????????if?not?self.layer.built:
????????????????input_shape?=?K.int_shape(inputs)
????????????????self.layer.build(input_shape)
????????????????self.layer.built?=?True
????????????????if?self.layer._initial_weights?is?not?None:
????????????????????self.layer.set_weights(self.layer._initial_weights)
????????for?key?in?['kernel',?'bias',?'embeddings',?'depthwise_kernel',?'pointwise_kernel',?'recurrent_kernel',?'gamma',?'beta']:
????????????if?hasattr(self.layer,?key):
????????????????weight?=?getattr(self.layer,?key)
????????????????if?self.is_ada:
????????????????????lamb?=?self.lamb?#?自適應(yīng)學(xué)習(xí)率優(yōu)化器直接保持lamb比例
????????????????else:
????????????????????lamb?=?self.lamb**0.5?#?SGD(包括動(dòng)量加速),lamb要開(kāi)平方
????????????????K.set_value(weight,?K.eval(weight)?/?lamb)?#?更改初始化
????????????????setattr(self.layer,?key,?weight?*?lamb)?#?按比例替換
????????return?self.layer(inputs)
使用示例:
x?=?x_in
#?默認(rèn)情況下是x?=?Embedding(100,?1000,?weights=[word_vecs])(x)
#?下面這一句表示:后面將會(huì)用自適應(yīng)學(xué)習(xí)率優(yōu)化器,并且Embedding層以總體的十分之一的學(xué)習(xí)率更新。
#?word_vecs是預(yù)訓(xùn)練好的詞向量
x?=?SetLearningRate(Embedding(100,?1000,?weights=[word_vecs]),?0.1,?True)(x)
#?后面部分自己想象了~
x?=?LSTM(100)(x)
model?=?Model(x_in,?x)
model.compile(loss='mse',?optimizer='adam')?#?用自適應(yīng)學(xué)習(xí)率優(yōu)化器優(yōu)化
幾個(gè)注意事項(xiàng):
1. 目前這種方式,只能用于自己動(dòng)手寫(xiě)代碼來(lái)構(gòu)建模型的時(shí)候插入,無(wú)法對(duì)建立好的模型進(jìn)行操作;
2. 如果有預(yù)訓(xùn)練權(quán)重,有兩種加載方法。第一種是像剛才的使用示例一樣,在定義層的時(shí)候通過(guò) weights 參數(shù)傳入;第二種方法是建立好模型后(已經(jīng)在相應(yīng)的地方插入好 SetLearningRate),用 model.set_weights (weights) 來(lái)賦值,其中 weights 是“在 SetLearningRate 的位置已經(jīng)被除以了 λ 或的原來(lái)模型的預(yù)訓(xùn)練權(quán)重”;
3. 加載預(yù)訓(xùn)練權(quán)重的第二種方法看起來(lái)有點(diǎn)不知所云,但如果你已經(jīng)理解了這一節(jié)的原理,那么應(yīng)該能知道我在說(shuō)什么。因?yàn)樵O(shè)置學(xué)習(xí)率是通過(guò) weight * lamb 來(lái)實(shí)現(xiàn)的,所以 weight 的初始化要變?yōu)?weight / lamb;
4. 這個(gè)操作基本上不可逆,比如你一開(kāi)始設(shè)置了 Embedding 層以總體的 1/10 比例的學(xué)習(xí)率來(lái)更新,那么很難在這個(gè)基礎(chǔ)上,再將它改為 1/5 或者其他比例。(當(dāng)然,如果你真的徹底搞懂了這一節(jié)的原理,并且也弄懂了加載預(yù)訓(xùn)練權(quán)重的第二種方法,那么還是有辦法的,那時(shí)候相信你也能搞出來(lái));
?
5. 這種做法有以上限制,是因?yàn)槲覀儾幌胪ㄟ^(guò)修改或者重寫(xiě)優(yōu)化器的方式來(lái)實(shí)現(xiàn)這個(gè)功能。如果你決定要自己修改優(yōu)化器,請(qǐng)參考《“讓Keras更酷一些!”:小眾的自定義優(yōu)化器》[2]。
自由的梯度操作
在這部分內(nèi)容中,我們將學(xué)習(xí)對(duì)梯度的更為自由的控制。這部分內(nèi)容涉及到對(duì)優(yōu)化器的修改,但不需要完全重寫(xiě)優(yōu)化器。?
Keras優(yōu)化器的結(jié)構(gòu)
要修改優(yōu)化器,必須先要了解 Keras 優(yōu)化器的結(jié)構(gòu)。在《“讓Keras更酷一些!”:小眾的自定義優(yōu)化器》[2]?一文我們已經(jīng)初步看過(guò)了,現(xiàn)在我們重新看一遍。?
Keras 優(yōu)化器代碼:
https://github.com/keras-team/keras/blob/master/keras/optimizers.py?
隨便觀察一個(gè)優(yōu)化器,就會(huì)發(fā)現(xiàn)你要自定義一個(gè)優(yōu)化器,只需要繼承 Optimizer 類(lèi),然后定義 get_updates 方法。但本文我們不想做新的優(yōu)化器,只是想要對(duì)梯度有所控制。可以看到,梯度的獲取其實(shí)是在父類(lèi) Optimizer 的 get_gradients 方法中:?
????????grads?=?K.gradients(loss,?params)
????????if?None?in?grads:
????????????raise?ValueError('An?operation?has?`None`?for?gradient.?'
?????????????????????????????'Please?make?sure?that?all?of?your?ops?have?a?'
?????????????????????????????'gradient?defined?(i.e.?are?differentiable).?'
?????????????????????????????'Common?ops?without?gradient:?'
?????????????????????????????'K.argmax,?K.round,?K.eval.')
????????if?hasattr(self,?'clipnorm')?and?self.clipnorm?>?0:
????????????norm?=?K.sqrt(sum([K.sum(K.square(g))?for?g?in?grads]))
????????????grads?=?[clip_norm(g,?self.clipnorm,?norm)?for?g?in?grads]
????????if?hasattr(self,?'clipvalue')?and?self.clipvalue?>?0:
????????????grads?=?[K.clip(g,?-self.clipvalue,?self.clipvalue)?for?g?in?grads]
????????return?grads
其中方法中的第一句就是獲取原始梯度的,后面則提供了兩種梯度裁剪方法。不難想到,只需要重寫(xiě)優(yōu)化器的 get_gradients 方法,就可以實(shí)現(xiàn)對(duì)梯度的任意操作了,而且這個(gè)操作不影響優(yōu)化器的更新步驟(即不影響 get_updates 方法)。?
處處皆對(duì)象:覆蓋即可
怎么能做到只修改 get_gradients 方法呢?這得益于 Python 的哲學(xué)——“處處皆對(duì)象”。Python 是一門(mén)面向?qū)ο蟮木幊陶Z(yǔ)言,Python 中幾乎你能碰到的一切變量都是一個(gè)對(duì)象。我們說(shuō) get_gradients 是優(yōu)化器的一個(gè)方法,也可以說(shuō) get_gradients 的一個(gè)屬性(對(duì)象),既然是屬性,直接覆蓋賦值即可。?
我們來(lái)舉一個(gè)最粗暴的例子(惡作劇):
????return?[K.zeros_like(p)?for?p?in?params]
adam_opt?=?Adam(1e-3)
adam_opt.get_gradients?=?our_get_gradients
model.compile(loss='categorical_crossentropy',
??????????????optimizer=adam_opt)
其實(shí)這樣做的事情很無(wú)聊,就是把所有梯度置零了(然后你怎么優(yōu)化它都不動(dòng)了),但這個(gè)惡作劇例子已經(jīng)足夠有代表性了——你可以將所有梯度置零,你也可以將梯度做任意你喜歡的操作。比如將梯度按照 l1 范數(shù)而非 l2 范數(shù)裁剪,又或者做其他調(diào)整。
假如我只想操作部分層的梯度怎么辦?那也簡(jiǎn)單,你在定義層的時(shí)候需要起一個(gè)能區(qū)分的名字,然后根據(jù) params 的名字做不同的操作即可。都到這一步了,我相信基本是“一法通,萬(wàn)法皆通”的了。
飄逸的Keras
也許在很多人眼中,Keras 就是一個(gè)好用但是封裝得很“死”的高層框架,但在我眼里,我只看到了它無(wú)限的靈活性——那是一個(gè)無(wú)懈可擊的封裝。
相關(guān)鏈接
[1]?https://kexue.fm/archives/6311
[2]?https://kexue.fm/archives/5879
點(diǎn)擊以下標(biāo)題查看作者其他文章:?
變分自編碼器VAE:原來(lái)是這么一回事 | 附源碼
再談變分自編碼器VAE:從貝葉斯觀點(diǎn)出發(fā)
變分自編碼器VAE:這樣做為什么能成?
簡(jiǎn)單修改,讓GAN的判別器秒變編碼器
深度學(xué)習(xí)中的互信息:無(wú)監(jiān)督提取特征
全新視角:用變分推斷統(tǒng)一理解生成模型
細(xì)水長(zhǎng)flow之NICE:流模型的基本概念與實(shí)現(xiàn)
細(xì)水長(zhǎng)flow之f-VAEs:Glow與VAEs的聯(lián)姻
深度學(xué)習(xí)中的Lipschitz約束:泛化與生成模型
#投 稿 通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優(yōu)質(zhì)內(nèi)容以更短路徑到達(dá)讀者群體,縮短讀者尋找優(yōu)質(zhì)內(nèi)容的成本呢??答案就是:你不認(rèn)識(shí)的人。
總有一些你不認(rèn)識(shí)的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學(xué)者和學(xué)術(shù)靈感相互碰撞,迸發(fā)出更多的可能性。?
PaperWeekly 鼓勵(lì)高校實(shí)驗(yàn)室或個(gè)人,在我們的平臺(tái)上分享各類(lèi)優(yōu)質(zhì)內(nèi)容,可以是最新論文解讀,也可以是學(xué)習(xí)心得或技術(shù)干貨。我們的目的只有一個(gè),讓知識(shí)真正流動(dòng)起來(lái)。
??來(lái)稿標(biāo)準(zhǔn):
? 稿件確系個(gè)人原創(chuàng)作品,來(lái)稿需注明作者個(gè)人信息(姓名+學(xué)校/工作單位+學(xué)歷/職位+研究方向)?
? 如果文章并非首發(fā),請(qǐng)?jiān)谕陡鍟r(shí)提醒并附上所有已發(fā)布鏈接?
? PaperWeekly 默認(rèn)每篇文章都是首發(fā),均會(huì)添加“原創(chuàng)”標(biāo)志
? 投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請(qǐng)單獨(dú)在附件中發(fā)送?
? 請(qǐng)留下即時(shí)聯(lián)系方式(微信或手機(jī)),以便我們?cè)诰庉嫲l(fā)布時(shí)和作者溝通
?
現(xiàn)在,在「知乎」也能找到我們了
進(jìn)入知乎首頁(yè)搜索「PaperWeekly」
點(diǎn)擊「關(guān)注」訂閱我們的專(zhuān)欄吧
關(guān)于PaperWeekly
PaperWeekly 是一個(gè)推薦、解讀、討論、報(bào)道人工智能前沿論文成果的學(xué)術(shù)平臺(tái)。如果你研究或從事 AI 領(lǐng)域,歡迎在公眾號(hào)后臺(tái)點(diǎn)擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點(diǎn)擊 |?閱讀原文?| 查看作者博客
總結(jié)
以上是生活随笔為你收集整理的“让Keras更酷一些!”:分层的学习率和自由的梯度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: NeuSomatic:基于深度CNN的肿
- 下一篇: 被“轻视”的CV·AR的背后核心技术