乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      “讓Keras更酷一些!”:層與模型的重用技巧

       LibraryPKU 2019-10-10

      作者丨蘇劍林

      單位丨追一科技

      研究方向丨NLP,神經(jīng)網(wǎng)絡(luò)

      個人主頁丨kexue.fm

      今天我們繼續(xù)來深挖 Keras,再次體驗 Keras 那無與倫比的優(yōu)雅設(shè)計。這一次我們的焦點是“重用”,主要是層與模型的重復使用。

      所謂重用,一般就是奔著兩個目標去:一是為了共享權(quán)重,也就是說要兩個層不僅作用一樣,還要共享權(quán)重,同步更新;二是避免重寫代碼,比如我們已經(jīng)搭建好了一個模型,然后我們想拆解這個模型,構(gòu)建一些子模型等。


      基礎(chǔ)


      事實上,Keras 已經(jīng)為我們考慮好了很多,所以很多情況下,掌握好基本用法,就已經(jīng)能滿足我們很多需求了。 

      層的重用

      層的重用是最簡單的,將層初始化好,存起來,然后反復調(diào)用即可:

      x_in = Input(shape=(784,))
      x = x_in

      layer = Dense(784, activation='relu') # 初始化一個層,并存起來

      x = layer(x) # 第一次調(diào)用
      x = layer(x) # 再次調(diào)用
      x = layer(x) # 再次調(diào)用

      要注意的是,必須先初始化好一個層,存為一個變量好再調(diào)用,才能保證重復調(diào)用的層是共享權(quán)重的。反之,如果是下述形式的代碼,則是非共享權(quán)重的:

      x = Dense(784, activation='relu')(x) 
      x = Dense(784, activation='relu')(x) # 跟前面的不共享權(quán)重
      x = Dense(784, activation='relu')(x) # 跟前面的不共享權(quán)重

      模型重用

      Keras 的模型有著類似層的表現(xiàn),在調(diào)用時可以用跟層一樣的方式,比如:

      x_in = Input(shape=(784,))
      x = x_in

      x = Dense(10, activation='softmax')(x)

      model = Model(x_in, x) # 建立模型


      x_in = Input(shape=(100,))
      x = x_in

      x = Dense(784, activation='relu')(x)
      x = model(x) # 將模型當層一樣用

      model2 = Model(x_in, x)

      讀過 Keras 源碼的朋友就會明白,之所以可以將模型當層那樣用,是因為 Model 本身就是繼承 Layer 類來寫的,所以模型自然也包含了層的一些相同特性。 

      模型克隆

      模型克隆跟模型重用類似,只不過得到的新模型跟原模型不共享權(quán)重了,也就是說,僅僅保留完全一樣的模型結(jié)構(gòu),兩個模型之間的更新是獨立的。Keras 提供了模型可用專用的函數(shù),直接調(diào)用即可:

      from keras.models import clone_model

      model2 = clone_model(model1)

      注意,clone_model 完全復制了原模型模型的結(jié)構(gòu),并重新構(gòu)建了一個模型,但沒有復制原模型的權(quán)重的值。也就是說,對于同樣的輸入,model1.predict 和 model2.predict 的結(jié)果是不一樣的。

      如果要把權(quán)重也搬過來,需要手動 set_weights 一下:

      model2.set_weights(K.batch_get_value(model1.weights))

      進階

      上述談到的是原封不等的調(diào)用原來的層或模型,所以比較簡單,Keras 都準備好了。下面介紹一些復雜一些的例子。 

      交叉引用

      這里的交叉引用是指在定義一個新層的時候,沿用已有的某個層的權(quán)重,注意這個自定義層可能跟舊層的功能完全不一樣,它們之間純粹是共享了某個權(quán)重而已。比如,Bert 在訓練 MLM 的時候,最后預測字詞概率的全連接層,權(quán)重就是跟 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 = activation
              self.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 outputs

          def compute_output_shape(self, input_shape):
              return input_shape[:-1] + (self.units,)


      # 用法
      embedding_layer = Embedding(10000, 128)
      x = embedding_layer(x) # 調(diào)用Embedding層
      x = EmbeddingDense(embedding_layer)(x) # 調(diào)用EmbeddingDense層

      提取中間層

      有時候我們需要從搭建好的模型中提取中間層的特征,并且構(gòu)建一個新模型,在 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,
          ]
      )

      從中間拆開

      最后,來到本文最有難度的地方了,我們要將模型從中間拆開,搞懂之后也可以實現(xiàn)往已有模型插入或替換新層的操作。這個需求看上去比較奇葩,但是還別說,stackoverflow 上面還有人提問過,說明這確實是有價值的。 

      https:///questions/49492255/how-to-replace-or-insert-intermediate-layer-in-keras-model

      假設(shè)我們有一個現(xiàn)成的模型,它可以分解為:

      那可能我們需要將 h2 替換成一個新的輸入,然后接上后面的層,來構(gòu)建一個新模型,即新模型的功能是:

      如果是 Sequential 類模型,那比較簡單,直接把 model.layers 都遍歷一邊,就可以構(gòu)建新模型了:

      x_in = Input(shape=(100,))
      x = x_in

      for layer in model.layers[2:]:
          x = layer(x)

      model2 = Model(x_in, x)

      但是,如果模型是比較復雜的結(jié)構(gòu),比如殘差結(jié)構(gòu)這種不是一條路走到底的,就沒有這么簡單了。事實上,這個需求本來沒什么難度,該寫的 Keras 本身已經(jīng)寫好了,只不過沒有提供現(xiàn)成的接口罷了。為什么這么說,因為我們通過 model(x) 這樣的代碼調(diào)用已有模型的時候,

      實際上 Keras 就相當于把這個已有的這個 model 從頭到尾重新搭建了一遍,既然可以重建整個模型,那搭建“半個”模型原則上也是沒有任技術(shù)難度的,只不過沒有現(xiàn)成的接口。具體可以參考 Keras 源碼的 keras/engine/network.py 的 run_internal_graph 函數(shù):


      https://github.com/keras-team/keras/blob/master/keras/engine/network.py

      完整重建一個模型的邏輯在 run_internal_graph 函數(shù)里邊,并且可以看到它還不算簡單,所以如無必要我們最好不要重寫這個代碼。但如果不重寫這個代碼,又想調(diào)用這個代碼,實現(xiàn)從中間層拆解模型的功能,唯一的辦法是“移花接木”了:通過修改已有模型的一些屬性,欺騙一下 run_internal_graph 函數(shù),使得它以為模型的輸入層是中間層,而不是原始的輸入層。有了這個思想,再認真讀讀 run_internal_graph 函數(shù)的代碼,就不難得到下述參考代碼:

      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_tensors
          model._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 函數(shù)
          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 = 0
                  for x in node.input_tensors:
                      if str(id(x)) in tensor_map:
                          n += 1
                  if 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 就可以實現(xiàn)欺騙模型從中間層開始構(gòu)建的效果了,其余的多數(shù)是適配工作,不是技術(shù)上的,而 model._layers = layers 這一句是只保留了從中間層開始所用到的層,只是為了統(tǒng)計模型參數(shù)量的準確性,如果去掉這一部分,模型的參數(shù)量依然是原來整個 model 那么多。

      小結(jié)

      Keras 是最讓人賞心悅目的深度學習框架,至少到目前為止,就模型代碼的可讀性而言,沒有之一。可能讀者會提到 PyTorch,誠然 PyTorch 也有不少可取之處,但就可讀性而言,我認為是比不上 Keras 的。

      在深究 Keras 的過程中,我不僅驚嘆于 Keras 作者們的深厚而優(yōu)雅的編程功底,甚至感覺自己的編程技能也提高了不少。不錯,我的很多 Python 編程技巧,都是從讀 Keras 源碼中學習到的。

      點擊以下標題查看作者其他文章: 

      #投 稿 通 道#

       讓你的論文被更多人看到 


      如何才能讓更多的優(yōu)質(zhì)內(nèi)容以更短路徑到達讀者群體,縮短讀者尋找優(yōu)質(zhì)內(nèi)容的成本呢?答案就是:你不認識的人。

      總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術(shù)靈感相互碰撞,迸發(fā)出更多的可能性。

      PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優(yōu)質(zhì)內(nèi)容,可以是最新論文解讀,也可以是學習心得技術(shù)干貨。我們的目的只有一個,讓知識真正流動起來。

      ?? 來稿標準:

      · 稿件確系個人原創(chuàng)作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向) 

      · 如果文章并非首發(fā),請在投稿時提醒并附上所有已發(fā)布鏈接 

      · PaperWeekly 默認每篇文章都是首發(fā),均會添加“原創(chuàng)”標志

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多