Keras是基于Tensorflow等底層張量處理庫的進階API庫。它幫我們實作了一系列經典的神經網絡層(全連接配接層、卷積層、循環層等),以及簡潔的疊代模型的接口,讓我們能在模型層面寫代碼,進而不用仔細考慮模型各層張量之間的資料流動。
但是,當我們有了全新的想法,想要個性化模型層的實作,Keras的進階API是不能滿足這一要求的,而換成Tensorflow又要重新寫很多輪子,這時,Keras的後端就派上用場了。Keras将底層張量庫的函數功能統一封裝在“backend”中,使用者可以用統一的函數接口調用不同的後端實作的相同功能。是以,如果不追求速度的話,可以僅使用Keras實作你的任何獨特想法,進而避免使用原生Tensorflow寫重複的輪子。
我們定義并訓練一個神經網絡模型需要考慮的要素有三個:層、損失函數、優化器。而我們創新主要在于前兩個,是以下面介紹如何結合Keras進階API與後端,自定義特殊神經網絡層以及損失函數。
1 自定義網絡層
自定義層可以通過兩種方式實作:使用Lambda層和繼承Layer類。
1.1 lambda層
Lambda層僅能對輸入做固定的變換,并不能定義可以通過反向傳播訓練的參數(通過Keras的fit訓練),是以能實作的東西較少。以下代碼實作了Dropout的功能:
from keras import backend as Kfrom keras import layersdef my_layer(x): mask = K.random_binomial(K.shape(x),0.5) return x*mask*2x = layers.Lambda(my_layer)(x)
其中my_layer函數是自定義層要實作的操作,傳遞參數隻能是Lambda層的輸入。定義好函數後,直接在layers.Lambda中傳入函數對象即可。實際上,這些變換不整合在lambda層中而直接寫在外面也是可以的:
from keras import backend as Kfrom keras import layersx = layers.Dense(500,activation='relu')(x) mask = K.random_binomial(K.shape(x),0.5)x = x*mask*2
資料先經過一個全連接配接層,然後再被0.5機率Dropout。以上實作Dropout隻是作舉例,你可以以同樣的方式實作其它的功能。
1.2 繼承layer類
如果你想自定義可以訓練參數的層,就需要繼承實作Keras的抽象類Layer。主要實作以下三個方法:
1、__init__(self, *args, **kwargs):構造函數,在執行個體化層時調用。此時還沒有添加輸入,也就是說此時輸入規模未知,但可以定義輸出規模等與輸入無關的變量。類比于Dense層裡的units、activations參數。
2、build(self, input_shape):在添加輸入時調用(__init__之後),且參數隻能傳入輸入規模input_shape。此時輸入規模與輸出規模都已知,可以定義訓練參數,比如全連接配接層的權重w和偏執b。
3、call(self, *args, **kwargs):編寫層的功能邏輯。
1.2.1 單一輸入
當輸入張量隻有一個時,下面是實作全連接配接層的例子:
import numpy as npfrom keras import layers,Model,Input,utilsfrom keras import backend as Kimport tensorflow as tfclass MyDense(layers.Layer): def __init__(self, units=32): #初始化 super(MyDense, self).__init__()#初始化父類 self.units = units #定義輸出規模 def build(self, input_shape): #定義訓練參數 self.w = K.variable(K.random_normal(shape=[input_shape[-1],self.units])) #訓練參數 self.b = tf.Variable(K.random_normal(shape=[self.units]),trainable=True) #訓練參數 self.a = tf.Variable(K.random_normal(shape=[self.units]),trainable=False) #非訓練參數 def call(self, inputs): #功能實作 return K.dot(inputs, self.w) + self.b #定義模型input_feature = Input([None,28,28]) x = layers.Reshape(target_shape=[28*28])(input_feature)x = layers.Dense(500,activation='relu')(x) x = MyDense(100)(x)x = layers.Dense(10,activation='softmax')(x) model = Model(input_feature,x) model.summary() utils.plot_model(model)
模型結構如下:
在build()中,訓練參數可以用K.variable或tf.Variable定義。并且,隻要是用這兩個函數定義并存入self中,就會被keras認定為訓練參數,不管是在build還是__init__或是其它函數中定義。但是K.variable沒有trainable參數,不能設定為Non-trainable params,是以還是用tf.Variable更好更靈活些。
1.2.2 多源輸入
如果輸入包括多個張量,需要傳入張量清單。實作代碼如下:
import numpy as npfrom keras import layers,Model,Input,utilsfrom keras import backend as Kimport tensorflow as tfclass MyLayer(layers.Layer): def __init__(self, output_dims): super(MyLayer, self).__init__() self.output_dims = output_dims def build(self, input_shape): [dim1,dim2] = self.output_dims self.w1 = tf.Variable(K.random_uniform(shape=[input_shape[0][-1],dim1])) self.b1 = tf.Variable(K.random_uniform(shape=[dim1])) self.w2 = tf.Variable(K.random_uniform(shape=[input_shape[1][-1],dim2])) self.b2 = tf.Variable(K.random_uniform(shape=[dim2])) def call(self, x): [x1, x2] = x y1 = K.dot(x1, self.w1)+self.b1 y2 = K.dot(x2, self.w2)+self.b2 return K.concatenate([y1,y2],axis = -1) #定義模型input_feature = Input([None,28,28])#輸入x = layers.Reshape(target_shape=[28*28])(input_feature) x1 = layers.Dense(500,activation='relu')(x) x2 = layers.Dense(500,activation='relu')(x) x = MyLayer([100,80])([x1,x2]) x = layers.Dense(10,activation='softmax')(x) model = Model(input_feature,x) model.summary() utils.plot_model(model,show_layer_names=False,show_shapes=True)
模型結構如下:
總之,傳入張量清單,build傳入的input_shape就是各個張量形狀的清單。其它都與單一輸入類似。
2 自定義損失函數
根據Keras能添加自定義損失的特性,這裡将添加損失的方法分為兩類:
1、損失需要根據模型輸出與真實标簽來計算,也就是隻有模型的輸出與外部真實标簽作為計算損失的參數。
2、損失無需使用外部真實标簽,也就是隻用模型内部各層的輸出作為計算損失的參數。
這兩類損失添加的方式并不一樣,希望以後Keras能把API再改善一下,這種備援有時讓人摸不着頭腦。
2.1 第一類損失
這類損失可以通過自定義函數的形式來實作。函數的參數必須是兩個:真實标簽與模型輸出,不能多也不能少,并且順序不能變。然後你可以在這個函數中定義你想要的關于輸出與真實标簽之間的損失。然後在model.compile()中将這個函數對象傳給loss參數。代碼示例如下(參考連結):
def customed_loss(true_label,predict_label): loss = keras.losses.categorical_crossentropy(true_label,predict_label) loss += K.max(predict_label) return lossmodel.compile(optimizer='rmsprop', loss=customed_loss)
如果硬是想用這種方法把模型隐層的輸出拿來算損失的話,也不是不可以。隻要把相應隐層的輸出添加到模型的輸出清單中,自定義損失函數就可以從模型輸出清單中取出隐層輸出來用了。即:
model = Model(input,[model_output, hidden_layer_output])
當然,這樣就把模型結構改了,如果不想改模型的結構而添加“正則化”損失,可以使用下面的方法。
2.2 第二類損失
這類損失可以用Model.add_loss(loss)方法實作,loss可以使用Keras後端定義計算圖來實作。但是顯然,計算圖并不能把未來訓練用的真實标簽傳入,是以,add_loss方法隻能計算模型内部的“正則化”損失。
add_loss方法可以使用多次,損失就是多次添加的loss之和。使用了add_loss方法後,compile中就可以不用給loss指派,不給loss指派的話使用fit()時就不能傳入資料的标簽,也就是y_train。如果給compile的loss指派,最終的目标損失就是多次add_loss添加的loss和compile中loss之和。另外,如果要給各項損失權重重的話,直接在定義loss的時候加上即可。代碼示例如下:
loss = 100000*K.mean(K.square(somelayer_output))#somelayer_output是定義model時獲得的某層輸出model.add_loss(loss)model.compile(optimizer='rmsprop')
以上講的都是關于層輸出的損失,層權重的正則化損失并不這樣添加,自定義正則項可以看下面。
keras中添加正則化_Bebr的部落格-CSDN部落格_keras 正則化
裡面介紹了已實作層的自定義正則化,但沒有介紹自定義層的自定義正則化,這裡先挖個坑,以後要用再研究。