Keras提供眾多常見的已編寫好的層對象,例如常見的卷積層、池化層等,我們可以直接通過以下代碼調用:
# 調用一個Conv2D層from keras import layersconv2D = keras.layers.convolutional.Conv2D(filters,\kernel_size, \strides=(1, 1), \padding='valid', \...)
但是在實際應用中,我們經常需要自己構建一些層對象,已滿足某些自訂網路的特殊需求。
幸運的是,Keras對自訂層提供了良好的支援。
下面對常用方法進行總結。 方法1:keras.core.lambda()
如果我們的自訂層中不包含可訓練的權重,而只是對上一層輸出做一些函數變換,那麼我們可以直接使用keras.core模組(該模組包含常見的基礎層,如Dense、Activation等)下的lambda函數:
keras.layers.core.Lambda(function, output_shape=None, mask=None, arguments=None)
參數說明:
function:要實現的函數,該函數僅接受一個變數,即上一層的輸出
output_shape:函數應該返回的值的shape,可以是一個tuple,也可以是一個根據輸入shape計算輸出shape的函數
mask: 掩膜
arguments:可選,字典,用來記錄向函數中傳遞的其他關鍵字參數
但是多數情況下,我們需要定義的是一個全新的、擁有可訓練權重的層,這個時候我們就需要使用下面的方法。 方法2: 編寫Layer繼承類
keras.engine.topology中包含了Layer的父類,我們可以通過繼承來實現自己的層。
要定製自己的層,需要實現下面三個方法
build(input_shape):這是定義權重的方法,可訓練的權應該在這裡被加入列表self.trainable_weights中。其他的屬性還包括self.non_trainabe_weights(列表)和self.updates(需要更新的形如(tensor,new_tensor)的tuple的列表)。這個方法必須設定self.built = True,可通過調用super([layer],self).build()實現。
call(x):這是定義層功能的方法,除非你希望你寫的層支援masking,否則你只需要關心call的第一個參數:輸入張量。
compute_output_shape(input_shape):如果你的層修改了輸入資料的shape,你應該在這裡指定shape變化的方法,這個函數使得Keras可以做自動shape推斷。
一個比較好的學習方法是閱讀Keras已編寫好的類的原始碼,嘗試理解其中的邏輯。
下面,我們將通過一個實際的例子,編寫一個自訂層。
出於學習的目的,在該例子中,會添加一些的注釋文字,用以解釋一些函數功能。
該層結構來源自DenseNet,代碼參考Github。
from keras.layers.core import Layerfrom keras.engine import InputSpecfrom keras import backend as Ktry: from keras import initializationsexcept ImportError: from keras import initializers as initializations# 繼承父類Layerclass Scale(Layer): ''' 該層功能: 通過向量元素依次相乘(Element wise multiplication)調整上層輸出的形狀。 out = in * gamma + beta, gamma代表權重weights,beta代表偏置bias 參數列表: axis: int型,代表需要做scale的軸方向,axis=-1 代表選取預設方向(橫行)。 momentum: 對資料方差和標準差做指數平均時的動量. weights: 初始權重,是一個包含兩個numpy array的list, shapes:[(input_shape,), (input_shape,)] beta_init: 偏置量的初始化方法名。(參考Keras.initializers.只有weights未傳參時才會使用. gamma_init: 權重量的初始化方法名。(參考Keras.initializers.只有weights未傳參時才會使用. ''' def __init__(self, weights=None, axis=-1, beta_init = 'zero', gamma_init = 'one', momentum = 0.9, **kwargs): # 參數**kwargs代表按字典方式繼承父類 self.momentum = momentum self.axis = axis self.beta_init = initializers.Zeros() self.gamma_init = initializers.Ones() self.initial_weights = weights super(Scale, self).__init__(**kwargs) def build(self, input_shape): self.input_spec = [InputSpec(shape=input_shape)] # 1:InputSpec(dtype=None, shape=None, ndim=None, max_ndim=None, min_ndim=None, axes=None) #Docstring: #Specifies the ndim, dtype and shape of every input to a layer. #Every layer should expose (if appropriate) an `input_spec` attribute:a list of instances of InputSpec (one per input tensor). #A None entry in a shape is compatible with any dimension #A None shape is compatible with any shape. # 2:self.input_spec: List of InputSpec class instances # each entry describes one required input: # - ndim # - dtype # A layer with `n` input tensors must have # an `input_spec` of length `n`. shape = (int(input_shape[self.axis]),) # Compatibility with TensorFlow >= 1.0.0 self.gamma = K.variable(self.gamma_init(shape), name='{}_gamma'.format(self.name)) self.beta = K.variable(self.beta_init(shape), name='{}_beta'.format(self.name)) self.trainable_weights = [self.gamma, self.beta] if self.initial_weights is not None: self.set_weights(self.initial_weights) del self.initial_weights def call(self, x, mask=None): input_shape = self.input_spec[0].shape broadcast_shape = [1] * len(input_shape) broadcast_shape[self.axis] = input_shape[self.axis] out = K.reshape(self.gamma, broadcast_shape) * x + K.reshape(self.beta, broadcast_shape) return out def get_config(self): config = {"momentum": self.momentum, "axis": self.axis} base_config = super(Scale, self).get_config() return dict(list(base_config.items()) + list(config.items()))
以上就是編寫自訂層的執行個體,可以直接添加到自己的model中。
編寫好的layer自動存放在custom_layers中,通過import調用。
from custom_layers import Scaledef myNet(growth_rate=32, \nb_filter=64, \reduction=0.0, \dropout_rate=0.0, weight_decay=1e-4,...)...x = "last_layer_name"x = Scale(axis=concat_axis, name='scale')(x)...model = Model(input, x, name='myNet')return model