學(xué)習(xí)機(jī)器學(xué)習(xí)最出名的框架是pytorch,但還有另外一個(gè)框架也相當(dāng)優(yōu)秀,那就是Keras。Keras的最大特點(diǎn)就是包裝很好,擁有較高的可自定義性。那么如何自定義Keras呢?接下來(lái)這篇文章帶你了解。
簡(jiǎn)介
Keras是一個(gè)用于在python上搭神經(jīng)網(wǎng)絡(luò)模型的框架,語(yǔ)法和torch比較相似。我個(gè)人認(rèn)為Keras最大的特點(diǎn)是包裝很好,一些在訓(xùn)練過(guò)程中要輸出的方法和常用的優(yōu)化函數(shù)、目標(biāo)函數(shù)都已經(jīng)內(nèi)置了,非常適合用來(lái)寫大作業(yè)。Keras和python的哲學(xué)有些相似,那就是盡量不自己造輪子。
但是最近逛知乎,看到有答案說(shuō),Keras只能用來(lái)搭一些世面上已經(jīng)普及的網(wǎng)絡(luò),和其它框架相比比較小白。換句話說(shuō),就是Keras的擴(kuò)展性不好。作為一個(gè)試用過(guò)theano、tensorflow、torch、caffe等框架,最后定居在Keras的人,我對(duì)此不太同意。事實(shí)上,Keras擁有不錯(cuò)的擴(kuò)展性,這一方面是因?yàn)樵O(shè)計(jì)時(shí)就留好的接口,另一方面是因?yàn)榍逦拇a結(jié)構(gòu),讓你可以有很多自定義的空間。所以下面用幾個(gè)例子介紹在Keras中如何自定義層和各種方法。
0、backend
如果想在Keras中自定義各種層和函數(shù),一定會(huì)用到的就是backend。一般導(dǎo)入的方法是
from keras import backend as K
這是因?yàn)镵eras可以有兩種后臺(tái),即theano和tensorflow,所以一些操作張量的函數(shù)可能是隨后臺(tái)的不同而不同的,
通過(guò)引入這個(gè)backend,就可以讓Keras來(lái)處理兼容性。
比如求x的平均,就是K.mean(x)。backend文件本身在keras/backend文件夾下,可以通過(guò)閱讀代碼來(lái)了解backend都支持哪些操作。backend里面函數(shù)很多,一般都?jí)蛴昧恕?/p>
1、Lambda 層
如果你只是想對(duì)流經(jīng)該層的數(shù)據(jù)做個(gè)變換,而這個(gè)變換本身沒(méi)有什么需要學(xué)習(xí)的參數(shù),那么直接用Lambda Layer是最合適的了。
導(dǎo)入的方法是
from keras.layers.core import Lambda
Lambda函數(shù)接受兩個(gè)參數(shù),第一個(gè)是輸入張量對(duì)輸出張量的映射函數(shù),第二個(gè)是輸入的shape對(duì)輸出的shape的映射函數(shù)。比如想構(gòu)建這樣一個(gè)層,流經(jīng)該層的數(shù)據(jù)會(huì)被減去平均值,那么可以這樣定義:
def sub_mean(x):
x -= K.mean(x,axis=1,keepdims=True)
return x
model.add( Lambda(sub_mean,output_shape=lambda input_shape:input_shape ))
因?yàn)檩敵龅膕hape和輸入的shape是一樣的,第二個(gè)參數(shù)就直接用了恒等映射。
把模型完整地建立出來(lái):
def get_submean_model():
model = Sequential()
model.add(Dense(5,input_dim=7))
def sub_mean(x):
x -= K.mean(x,axis=1,keepdims=True)
return x
model.add( Lambda(sub_mean,output_shape=lambda input_shape:input_shape))
model.compile(optimizer='rmsprop',loss='mse')
return model
model = get_submean_model()
res=model.predict(np.random.random((3,7)))
得到地res的平均值是[ 5.96046448e-08 -5.96046448e-08 0.00000000e+00],可見(jiàn)確實(shí)實(shí)現(xiàn)了減去均值的作用。
2、自定義非遞歸層
如果自己想定義的層中有需要學(xué)習(xí)的變量,那么就不能用lambda層了,需要自己寫一個(gè)出來(lái)。
比如說(shuō)我想定義一個(gè)層,它的效果是對(duì)張量乘一個(gè)正對(duì)角陣(換句話說(shuō),輸入向量與一個(gè)要學(xué)習(xí)的向量逐元素相乘),那么可以這樣寫:
首先要導(dǎo)入基類
from keras.engine.topology import Layer
然后對(duì)MyLaber定義如下:
class MyLayer(Layer):
def __init__(self,output_dim,**kw):
self.output_dim = output_dim
super(MyLayer,self).__init__(**kw)
def build(self,input_shape):
input_dim = input_shape[1]
assert(input_dim == self.output_dim)
inital_SCALER = np.ones((input_dim,))*1000
self.SCALER = K.variable(inital_SCALER)
self.trainable_weights = [self.SCALER]
super(MyLayer,self).build(input_shape)
def call(self,x,mask=None):
#return x - K.mean(x,axis=1,keepdims=True)
x *= self.SCALER
return x
def get_output_shape_for(self,input_shape):
return input_shape
主要參照Keras內(nèi)置的層的寫法,比如Dense在keras/layers/core.py中,要把能學(xué)習(xí)的參數(shù)放在self.trainable_weights中。這里把初始值設(shè)成了1000是為了讓該層的效果更顯著。然后把模型寫全來(lái)測(cè)試一下
def get_mylayer_model():
model = Sequential()
model.add(Dense(5,input_dim=7))
model.add(MyLayer(5))
model.compile(optimizer='rmsprop',loss='mse')
return model
model = get_mylayer_model()
res=model.predict(np.random.random((3,7)))
print res
res如下:
[[ 271.2746582 -1053.31506348 147.17185974 -1120.33740234 609.54876709]
[ -263.69671631 -390.41921997 291.17721558 -594.58721924 615.97369385]
[ -46.58752823 -733.11328125 -21.9815979 -570.79351807 649.44158936]]
都是很大的數(shù),而不加MyLayer時(shí)每個(gè)值一般也不超過(guò)+-2,這個(gè)層確實(shí)起了作用。
在fit之前調(diào)用model.get_weights(),看到該層的權(quán)重都是1000,隨便隨機(jī)出來(lái)個(gè)測(cè)試集,fit幾千個(gè)epoch只后,loss變得很小,MyLayer的權(quán)重變成了997左右,而前面一層Dense的權(quán)重都成10^-4量級(jí),說(shuō)明MyLayer中的參數(shù)也確實(shí)是可學(xué)習(xí)的。
3、自定義損失函數(shù)
Keras內(nèi)置的損失函數(shù)都在keras/objectives.py中,比如mse的定義是:
def mean_squared_error(y_true, y_pred):
return K.mean(K.square(y_pred - y_true), axis=-1)
按照相同的格式,可以定義自己的損失函數(shù)。比如我們想要差值的4次方的平均作為損失函數(shù):
def my_object(y_true,y_pred):
return K.mean(K.square(K.square(y_pred-y_true)),axis=-1)
把模型寫全:
def get_myobj_model():
model = Sequential()
model.add(Dense(5,input_dim=7))
model.add(Dense(3))
def my_object(y_true,y_pred):
return K.mean(K.square(K.square(y_pred-y_true)),axis=-1)
model.compile(optimizer='sgd',loss=my_object)
return model
model = get_myobj_model()
能自定義損失函數(shù)是非常重要一環(huán),它極大的擴(kuò)展了網(wǎng)絡(luò)的應(yīng)用。例如希望用cnn訓(xùn)練出來(lái)一個(gè)前后景分割的濾波器,它的輸出的像素在對(duì)應(yīng)前景的位置是1,在對(duì)應(yīng)后景的位置是0。不但希望網(wǎng)絡(luò)輸出的值的mse小,而且希望0和1分別都連在一起,不要出來(lái)雪花狀的輸出。那么自定義損失函數(shù)就能做到了,實(shí)際是把兩個(gè)損失函數(shù)放到了一個(gè)損失函數(shù)中。
另外一些很有用的損失函數(shù)如warp-ctc,就可以在這里集成進(jìn)模型。
4、自定義遞歸層
遞歸層的定義方法和非遞歸層不太一樣。根據(jù)Keras內(nèi)LSTM的寫法,它還有一個(gè)reset_states函數(shù)和step函數(shù),這是由遞歸的性質(zhì)決定的。例子都在keras/layers/recurrent.py中。
之前看學(xué)長(zhǎng)用lasagne寫的LSTM的變體,看得我想哭,還不如在Keras中把LSTM得代碼復(fù)制過(guò)來(lái)修修改改。不過(guò)LSTM也不能直接復(fù)制過(guò)來(lái),還需要import幾個(gè)依賴:
rom keras.layers.recurrent import LSTM,Recurrent,time_distributed_dense
from keras import initializations,regularizers,activations
from keras.engine import InputSpec
5、自定義優(yōu)化函數(shù)
Keras的代碼確實(shí)好,耦合度很低。Keras內(nèi)置的優(yōu)化函數(shù)在keras/optimizers.py中,基類Optimizer也在這個(gè)文件里。例如把它內(nèi)置的SGD算法拷貝到自己的文件中,只要先f(wàn)rom keras.optimizers import Optimizer就能編譯通過(guò)。
有時(shí)候要得到state-of-the-art的結(jié)果,需要用sgd加動(dòng)量法充分收斂。比如學(xué)習(xí)率0.01學(xué)習(xí)上100epoch,再把學(xué)習(xí)率減半,再學(xué)100epoch,依次類推。如果不自定義優(yōu)化函數(shù)的話,就要分階段調(diào)用fit函數(shù),修改學(xué)習(xí)率,可能還要重新compile。這就不是很優(yōu)美了。其它一些奇葩的學(xué)習(xí)策略,也可以通過(guò)自定義優(yōu)化函數(shù)來(lái)得到。
6、后記
Keras確實(shí)非常強(qiáng)大,不但能用來(lái)寫大作業(yè),做一些研究也夠用了。Yeah
補(bǔ)充:keras的擴(kuò)展性:自定義keras
1. 自定義keras
keras是一種深度學(xué)習(xí)的API,能夠快速實(shí)現(xiàn)你的實(shí)驗(yàn)。keras也集成了很多預(yù)訓(xùn)練的模型,可以實(shí)現(xiàn)很多常規(guī)的任務(wù),如圖像分類。TensorFlow 2.0之后tensorflow本身也變的很keras化。
另一方面,keras表現(xiàn)出高度的模塊化和封裝性,所以有的人會(huì)覺(jué)得keras不易于擴(kuò)展, 比如實(shí)現(xiàn)一種新的Loss,新的網(wǎng)絡(luò)層結(jié)構(gòu);其實(shí)可以通過(guò)keras的基礎(chǔ)模塊進(jìn)行快速的擴(kuò)展,實(shí)現(xiàn)更新的算法。
本文就keras的擴(kuò)展性,總結(jié)了對(duì)layer,model和loss的自定義。
2. 自定義keras layers
layers是keras中重要的組成部分,網(wǎng)絡(luò)結(jié)構(gòu)中每一個(gè)組成都要以layers來(lái)表現(xiàn)。keras提供了很多常規(guī)的layer,如Convolution layers,pooling layers, activation layers, dense layers等, 我們可以通過(guò)繼承基礎(chǔ)layers來(lái)擴(kuò)展自定義的layers。
2.1 base layer
layer實(shí)了輸入tensor和輸出tensor的操作類,以下為base layer的5個(gè)方法,自定義layer只要重寫這些方法就可以了。
init(): 定義自定義layer的一些屬性
build(self, input_shape):定義layer需要的權(quán)重weights
call(self, *args, **kwargs):layer具體的操作,會(huì)在調(diào)用自定義layer自動(dòng)執(zhí)行
get_config(self):layer初始化的配置,是一個(gè)字典dictionary。
compute_output_shape(self,input_shape):計(jì)算輸出tensor的shape
2.2 例子
# 標(biāo)準(zhǔn)化層
class InstanceNormalize(Layer):
def __init__(self, **kwargs):
super(InstanceNormalize, self).__init__(**kwargs)
self.epsilon = 1e-3
def call(self, x, mask=None):
mean, var = tf.nn.moments(x, [1, 2], keep_dims=True)
return tf.div(tf.subtract(x, mean), tf.sqrt(tf.add(var, self.epsilon)))
def compute_output_shape(self,input_shape):
return input_shape
# 調(diào)用
inputs = keras.Input(shape=(None, None, 3))
x = InstanceNormalize()(inputs)
# 可以通過(guò)add_weight() 創(chuàng)建權(quán)重
class SimpleDense(Layer):
def __init__(self, units=32):
super(SimpleDense, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(shape=(input_shape[-1], self.units),
initializer='random_normal',
trainable=True)
self.b = self.add_weight(shape=(self.units,),
initializer='random_normal',
trainable=True)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
# 調(diào)用
inputs = keras.Input(shape=(None, None, 3))
x = SimpleDense(units=64)(inputs)
3. 自定義keras model
我們?cè)诙x完網(wǎng)絡(luò)結(jié)構(gòu)時(shí),會(huì)把整個(gè)工作流放在 keras.Model, 進(jìn)行 compile(), 然后通過(guò) fit() 進(jìn)行訓(xùn)練過(guò)程。執(zhí)行 fit() 的時(shí)候,執(zhí)行每個(gè) batch size data 的時(shí)候,都會(huì)調(diào)用 Model 中train_step(self, data)
from keras.models import Sequential
from keras.layers import Dense, Activation
model = Sequential()
model.add(Dense(units=64, input_dim=100))
model.add(Activation("relu"))
model.add(Dense(units=10))
model.add(Activation("softmax"))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5, batch_size=32)
當(dāng)你需要自己控制訓(xùn)練過(guò)程的時(shí)候,可以重寫Model的train_step(self, data)方法
class CustomModel(keras.Model):
def train_step(self, data):
# Unpack the data. Its structure depends on your model and
# on what you pass to `fit()`.
x, y = data
with tf.GradientTape() as tape:
y_pred = self(x, training=True) # Forward pass
# Compute the loss value
# (the loss function is configured in `compile()`)
loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)
# Compute gradients
trainable_vars = self.trainable_variables
gradients = tape.gradient(loss, trainable_vars)
# Update weights
self.optimizer.apply_gradients(zip(gradients, trainable_vars))
# Update metrics (includes the metric that tracks the loss)
self.compiled_metrics.update_state(y, y_pred)
# Return a dict mapping metric names to current value
return {m.name: m.result() for m in self.metrics}
import numpy as np
# Construct and compile an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])
# Just use `fit` as usual
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.fit(x, y, epochs=3)
4. 自定義keras loss
keras實(shí)現(xiàn)了交叉熵等常見(jiàn)的loss,自定義loss對(duì)于使用keras來(lái)說(shuō)是比較常見(jiàn),實(shí)現(xiàn)各種魔改loss,如focal loss。
我們來(lái)看看keras源碼中對(duì)loss實(shí)現(xiàn)
def categorical_crossentropy(y_true, y_pred):
return K.categorical_crossentropy(y_true, y_pred)
def mean_squared_error(y_true, y_pred):
return K.mean(K.square(y_pred - y_true), axis=-1)
可以看出輸入是groud true y_true和預(yù)測(cè)值y_pred, 返回為計(jì)算loss的函數(shù)。自定義loss可以參照如此模式即可。
def focal_loss(weights=None, alpha=0.25, gamma=2):
r"""Compute focal loss for predictions.
Multi-labels Focal loss formula:
FL = -alpha * (z-p)^gamma * log(p) -(1-alpha) * p^gamma * log(1-p)
,which alpha = 0.25, gamma = 2, p = sigmoid(x), z = target_tensor.
# https://github.com/ailias/Focal-Loss-implement-on-Tensorflow/blob/master/focal_loss.py
Args:
prediction_tensor: A float tensor of shape [batch_size, num_anchors,
num_classes] representing the predicted logits for each class
target_tensor: A float tensor of shape [batch_size, num_anchors,
num_classes] representing one-hot encoded classification targets
weights: A float tensor of shape [batch_size, num_anchors]
alpha: A scalar tensor for focal loss alpha hyper-parameter
gamma: A scalar tensor for focal loss gamma hyper-parameter
Returns:
loss: A (scalar) tensor representing the value of the loss function
"""
def _custom_loss(y_true, y_pred):
sigmoid_p = tf.nn.sigmoid(y_pred)
zeros = array_ops.zeros_like(sigmoid_p, dtype=sigmoid_p.dtype)
# For poitive prediction, only need consider front part loss, back part is 0;
# target_tensor > zeros <=> z=1, so poitive coefficient = z - p.
pos_p_sub = array_ops.where(y_true > zeros, y_true - sigmoid_p, zeros)
# For negative prediction, only need consider back part loss, front part is 0;
# target_tensor > zeros <=> z=1, so negative coefficient = 0.
neg_p_sub = array_ops.where(y_true > zeros, zeros, sigmoid_p)
per_entry_cross_ent = - alpha * (pos_p_sub ** gamma) * tf.log(tf.clip_by_value(sigmoid_p, 1e-8, 1.0))
- (1 - alpha) * (neg_p_sub ** gamma) * tf.log(
tf.clip_by_value(1.0 - sigmoid_p, 1e-8, 1.0))
return tf.reduce_sum(per_entry_cross_ent)
return _custom_loss
5. 總結(jié)
本文分享了keras的擴(kuò)展功能,擴(kuò)展功能其實(shí)也是實(shí)現(xiàn)Keras模塊化的一種繼承實(shí)現(xiàn)。
總結(jié)如下:
繼承Layer實(shí)現(xiàn)自定義layer, 記住bulid() call()
繼續(xù)Model實(shí)現(xiàn)train_step定義訓(xùn)練過(guò)程,記住梯度計(jì)算tape.gradient(loss, trainable_vars) ,權(quán)重更新optimizer.apply_gradients, 計(jì)算evaluate compiled_metrics.update_state(y, y_pred)
魔改loss,記住groud true y_true和預(yù)測(cè)值y_pred輸入,返回loss function
以上就是Keras自定義層的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持W3Cschool。