“让Keras更酷一些!”:层与模型的重用技巧
作者丨蘇劍林
單位丨追一科技
研究方向丨NLP,神經網絡
個人主頁丨kexue.fm
今天我們繼續來深挖 Keras,再次體驗 Keras 那無與倫比的優雅設計。這一次我們的焦點是“重用”,主要是層與模型的重復使用。
所謂重用,一般就是奔著兩個目標去:一是為了共享權重,也就是說要兩個層不僅作用一樣,還要共享權重,同步更新;二是避免重寫代碼,比如我們已經搭建好了一個模型,然后我們想拆解這個模型,構建一些子模型等。
基礎
事實上,Keras 已經為我們考慮好了很多,所以很多情況下,掌握好基本用法,就已經能滿足我們很多需求了。?
層的重用
層的重用是最簡單的,將層初始化好,存起來,然后反復調用即可:
x_in?=?Input(shape=(784,)) x?=?x_inlayer?=?Dense(784,?activation='relu')?#?初始化一個層,并存起來x?=?layer(x)?#?第一次調用 x?=?layer(x)?#?再次調用 x?=?layer(x)?#?再次調用要注意的是,必須先初始化好一個層,存為一個變量好再調用,才能保證重復調用的層是共享權重的。反之,如果是下述形式的代碼,則是非共享權重的:
x?=?Dense(784,?activation='relu')(x)? x?=?Dense(784,?activation='relu')(x)?#?跟前面的不共享權重 x?=?Dense(784,?activation='relu')(x)?#?跟前面的不共享權重模型重用
Keras 的模型有著類似層的表現,在調用時可以用跟層一樣的方式,比如:
x_in?=?Input(shape=(784,)) x?=?x_inx?=?Dense(10,?activation='softmax')(x)model?=?Model(x_in,?x)?#?建立模型x_in?=?Input(shape=(100,)) x?=?x_inx?=?Dense(784,?activation='relu')(x) x?=?model(x)?#?將模型當層一樣用model2?=?Model(x_in,?x)讀過 Keras 源碼的朋友就會明白,之所以可以將模型當層那樣用,是因為 Model 本身就是繼承 Layer 類來寫的,所以模型自然也包含了層的一些相同特性。?
模型克隆
模型克隆跟模型重用類似,只不過得到的新模型跟原模型不共享權重了,也就是說,僅僅保留完全一樣的模型結構,兩個模型之間的更新是獨立的。Keras 提供了模型可用專用的函數,直接調用即可:
from?keras.models?import?clone_modelmodel2?=?clone_model(model1)注意,clone_model 完全復制了原模型模型的結構,并重新構建了一個模型,但沒有復制原模型的權重的值。也就是說,對于同樣的輸入,model1.predict 和 model2.predict 的結果是不一樣的。
如果要把權重也搬過來,需要手動 set_weights 一下:
model2.set_weights(K.batch_get_value(model1.weights))進階
上述談到的是原封不等的調用原來的層或模型,所以比較簡單,Keras 都準備好了。下面介紹一些復雜一些的例子。?
交叉引用
這里的交叉引用是指在定義一個新層的時候,沿用已有的某個層的權重,注意這個自定義層可能跟舊層的功能完全不一樣,它們之間純粹是共享了某個權重而已。比如,Bert 在訓練 MLM 的時候,最后預測字詞概率的全連接層,權重就是跟 Embedding 層共享的。?
參考寫法如下:
class?EmbeddingDense(Layer):"""運算跟Dense一致,只不過kernel用Embedding層的embedding矩陣"""def?__init__(self,?embedding_layer,?activation='softmax',?**kwargs):super(EmbeddingDense,?self).__init__(**kwargs)self.kernel?=?K.transpose(embedding_layer.embeddings)self.activation?=?activationself.units?=?K.int_shape(self.kernel)[1]def?build(self,?input_shape):super(EmbeddingDense,?self).build(input_shape)self.bias?=?self.add_weight(name='bias',shape=(self.units,),initializer='zeros')def?call(self,?inputs):outputs?=?K.dot(inputs,?self.kernel)outputs?=?K.bias_add(outputs,?self.bias)outputs?=?Activation(self.activation).call(outputs)return?outputsdef?compute_output_shape(self,?input_shape):return?input_shape[:-1]?+?(self.units,)#?用法 embedding_layer?=?Embedding(10000,?128) x?=?embedding_layer(x)?#?調用Embedding層 x?=?EmbeddingDense(embedding_layer)(x)?#?調用EmbeddingDense層提取中間層
有時候我們需要從搭建好的模型中提取中間層的特征,并且構建一個新模型,在 Keras 中這同樣是很簡單的操作:
from?keras.applications.resnet50?import?ResNet50 model?=?ResNet50(weights='imagenet')Model(inputs=model.input,outputs=[model.get_layer('res5a_branch1').output,model.get_layer('activation_47').output,] )從中間拆開
最后,來到本文最有難度的地方了,我們要將模型從中間拆開,搞懂之后也可以實現往已有模型插入或替換新層的操作。這個需求看上去比較奇葩,但是還別說,stackoverflow 上面還有人提問過,說明這確實是有價值的。?
https://stackoverflow.com/questions/49492255/how-to-replace-or-insert-intermediate-layer-in-keras-model
假設我們有一個現成的模型,它可以分解為:
那可能我們需要將 h2 替換成一個新的輸入,然后接上后面的層,來構建一個新模型,即新模型的功能是:
如果是 Sequential 類模型,那比較簡單,直接把 model.layers 都遍歷一邊,就可以構建新模型了:
x_in?=?Input(shape=(100,)) x?=?x_infor?layer?in?model.layers[2:]:x?=?layer(x)model2?=?Model(x_in,?x)但是,如果模型是比較復雜的結構,比如殘差結構這種不是一條路走到底的,就沒有這么簡單了。事實上,這個需求本來沒什么難度,該寫的 Keras 本身已經寫好了,只不過沒有提供現成的接口罷了。為什么這么說,因為我們通過 model(x) 這樣的代碼調用已有模型的時候,
實際上 Keras 就相當于把這個已有的這個 model 從頭到尾重新搭建了一遍,既然可以重建整個模型,那搭建“半個”模型原則上也是沒有任技術難度的,只不過沒有現成的接口。具體可以參考 Keras 源碼的 keras/engine/network.py 的 run_internal_graph 函數:
https://github.com/keras-team/keras/blob/master/keras/engine/network.py
完整重建一個模型的邏輯在 run_internal_graph 函數里邊,并且可以看到它還不算簡單,所以如無必要我們最好不要重寫這個代碼。但如果不重寫這個代碼,又想調用這個代碼,實現從中間層拆解模型的功能,唯一的辦法是“移花接木”了:通過修改已有模型的一些屬性,欺騙一下 run_internal_graph 函數,使得它以為模型的輸入層是中間層,而不是原始的輸入層。有了這個思想,再認真讀讀 run_internal_graph 函數的代碼,就不難得到下述參考代碼:
def?get_outputs_of(model,?start_tensors,?input_layers=None):"""start_tensors為開始拆開的位置"""#?為此操作建立新模型model?=?Model(inputs=model.input,outputs=model.output,name='outputs_of_'?+?model.name)#?適配工作,方便使用if?not?isinstance(start_tensors,?list):start_tensors?=?[start_tensors]if?input_layers?is?None:input_layers?=?[Input(shape=K.int_shape(x)[1:],?dtype=K.dtype(x))for?x?in?start_tensors]elif?not?isinstance(input_layers,?list):input_layers?=?[input_layers]#?核心:覆蓋模型的輸入model.inputs?=?start_tensorsmodel._input_layers?=?[x._keras_history[0]?for?x?in?input_layers]#?適配工作,方便使用if?len(input_layers)?==?1:input_layers?=?input_layers[0]#?整理層,參考自?Model?的?run_internal_graph?函數layers,?tensor_map?=?[],?set()for?x?in?model.inputs:tensor_map.add(str(id(x)))depth_keys?=?list(model._nodes_by_depth.keys())depth_keys.sort(reverse=True)for?depth?in?depth_keys:nodes?=?model._nodes_by_depth[depth]for?node?in?nodes:n?=?0for?x?in?node.input_tensors:if?str(id(x))?in?tensor_map:n?+=?1if?n?==?len(node.input_tensors):if?node.outbound_layer?not?in?layers:layers.append(node.outbound_layer)for?x?in?node.output_tensors:tensor_map.add(str(id(x)))model._layers?=?layers?#?只保留用到的層#?計算輸出outputs?=?model(input_layers)return?input_layers,?outputs用法:
from?keras.applications.resnet50?import?ResNet50 model?=?ResNet50(weights='imagenet')x,?y?=?get_outputs_of(model,model.get_layer('add_15').output )model2?=?Model(x,?y)代碼有點長,但其實邏輯很簡單,真正核心的代碼只有三行:
model.inputs?=?start_tensors model._input_layers?=?[x._keras_history[0]?for?x?in?input_layers] outputs?=?model(input_layers)也就是覆蓋模型的 model.inputs 和 model._input_layers 就可以實現欺騙模型從中間層開始構建的效果了,其余的多數是適配工作,不是技術上的,而 model._layers = layers 這一句是只保留了從中間層開始所用到的層,只是為了統計模型參數量的準確性,如果去掉這一部分,模型的參數量依然是原來整個 model 那么多。
小結
Keras 是最讓人賞心悅目的深度學習框架,至少到目前為止,就模型代碼的可讀性而言,沒有之一。可能讀者會提到 PyTorch,誠然 PyTorch 也有不少可取之處,但就可讀性而言,我認為是比不上 Keras 的。
在深究 Keras 的過程中,我不僅驚嘆于 Keras 作者們的深厚而優雅的編程功底,甚至感覺自己的編程技能也提高了不少。不錯,我的很多 Python 編程技巧,都是從讀 Keras 源碼中學習到的。
點擊以下標題查看作者其他文章:?
基于DGCNN和概率圖的輕量級信息抽取模型
#投 稿 通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術干貨。我們的目的只有一個,讓知識真正流動起來。
??來稿標準:
? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?
? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?
? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志
? 投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請單獨在附件中發送?
? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通
?
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點擊 |?閱讀原文?| 查看作者博客
總結
以上是生活随笔為你收集整理的“让Keras更酷一些!”:层与模型的重用技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑黑屏怎么开机进去u盘启动不了怎么回事
- 下一篇: 文件夹隐藏后怎么找出来 找回被隐藏的文件