tf.keras的各種自定義和高級用法(待續)

  • 2021 年 2 月 25 日
  • AI

Keras & Tensorflow 2.0: Custom Layers & Modelswww.kaggle.com

kaggle上的一個很簡明的教程。

使用內置方法進行訓練和評估 | TensorFlow Coretensorflow.google.cn圖標

這裡面的keras的api給了非常多的細緻的定義,搭配蘇建林的文章,基本夠了

tf.keras 各種自定義

metric的自定義是最簡單的:

1、 使用train_on_batch,個人認為最最靈活的方式了,train_on_batch即訓練一個batch size的數據之後,model直接predict得到y_pred,numpy形式,然後sklearn的metric或者自定義基於numpy的metric計算都可以使用,非常的靈活,個人比較推薦train on batch,當然了,直接fit的時候epochs設置為1,然後一個batch 一個batch的讀取原始數據也是可以的,這一點上tf.keras是非常靈活的;基於numpy的自定義metric函數可以使用numba來加速大大提高evaluation的效率;

2、通過

import tensorflow.keras.backend as K

def mean_pred(y_true, y_pred):
    return K.mean(y_pred)

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy', mean_pred])

因為在model fit的過程中得到的中間結果是tf的tensor類型,因此無法直接在內部使用numpy函數,可以通過使用backend來寫,backend的常規用法和numpy api很類似,基本上可以無縫銜接;

當然,我們也可以將y_true,y_pred通過 ..numpy()的方式轉化為numpy,然後使用sklearn的或自定義的基於numpy的評估指標,最後通過convert_to_tensor再轉化為tensor也是很靈活的;

loss的自定義也不複雜:

def smape_error(y_true, y_pred):
    return K.mean(K.clip(K.abs(y_pred - y_true),  0.0, 1.0), axis=-1)

和metric的定義類似,我們可以使用tf.keras.backend來定義,但是不能轉化為numpy了,因為轉化為numpy之後,tf底層無法識別numpy數據類型,無法針對自定義的loss進行autograd,不過其實直接用backend基本夠了,自帶的函數基本上和常見的numpy函數是一樣的。

backend的內置methods

['__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 '_sys',
 'abs',
 'all',
 'any',
 'arange',
 'argmax',
 'argmin',
 'backend',
 'batch_dot',
 'batch_flatten',
 'batch_get_value',
 'batch_normalization',
 'batch_set_value',
 'bias_add',
 'binary_crossentropy',
 'cast',
 'cast_to_floatx',
 'categorical_crossentropy',
 'clear_session',
 'clip',
 'concatenate',
 'constant',
 'conv1d',
 'conv2d',
 'conv2d_transpose',
 'conv3d',
 'cos',
 'count_params',
 'ctc_batch_cost',
 'ctc_decode',
 'ctc_label_dense_to_sparse',
 'cumprod',
 'cumsum',
 'depthwise_conv2d',
 'dot',
 'dropout',
 'dtype',
 'elu',
 'epsilon',
 'equal',
 'eval',
 'exp',
 'expand_dims',
 'eye',
 'flatten',
 'floatx',
 'foldl',
 'foldr',
 'function',
 'gather',
 'get_uid',
 'get_value',
 'gradients',
 'greater',
 'greater_equal',
 'hard_sigmoid',
 'image_data_format',
 'in_test_phase',
 'in_top_k',
 'in_train_phase',
 'int_shape',
 'is_keras_tensor',
 'is_sparse',
 'l2_normalize',
 'learning_phase',
 'learning_phase_scope',
 'less',
 'less_equal',
 'local_conv1d',
 'local_conv2d',
 'log',
 'manual_variable_initialization',
 'map_fn',
 'max',
 'maximum',
 'mean',
 'min',
 'minimum',
 'moving_average_update',
 'name_scope',
 'ndim',
 'normalize_batch_in_training',
 'not_equal',
 'one_hot',
 'ones',
 'ones_like',
 'permute_dimensions',
 'placeholder',
 'pool2d',
 'pool3d',
 'pow',
 'print_tensor',
 'prod',
 'random_binomial',
 'random_normal',
 'random_normal_variable',
 'random_uniform',
 'random_uniform_variable',
 'relu',
 'repeat',
 'repeat_elements',
 'reset_uids',
 'reshape',
 'resize_images',
 'resize_volumes',
 'reverse',
 'rnn',
 'round',
 'separable_conv2d',
 'set_epsilon',
 'set_floatx',
 'set_image_data_format',
 'set_learning_phase',
 'set_value',
 'shape',
 'sigmoid',
 'sign',
 'sin',
 'softmax',
 'softplus',
 'softsign',
 'sparse_categorical_crossentropy',
 'spatial_2d_padding',
 'spatial_3d_padding',
 'sqrt',
 'square',
 'squeeze',
 'stack',
 'std',
 'stop_gradient',
 'sum',
 'switch',
 'tanh',
 'temporal_padding',
 'tile',
 'to_dense',
 'transpose',
 'truncated_normal',
 'update',
 'update_add',
 'update_sub',
 'var',
 'variable',
 'zeros',
 'zeros_like']

除了使用backend之外,tf.keras目前也支援直接使用tf的內置methods來自定義metric與loss,比如:

def customized_mse(y_true, y_pred):
    return tf.reduce_mean(tf.squre(y_pred - y_true))

然後是比較複雜的自定義優化器:

//github.com/bojone/accum_optimizer_for_keras/blob/master/accum_optimizer.pygithub.com

//kexue.fm/archives/6794kexue.fm

用時間換取效果:Keras梯度累積優化器 – 科學空間|Scientific Spaceskexue.fm

這塊的邏輯定義比較複雜,因為用的比較少,暫時不研究了


自定義layer:

1、最簡單的,使用lambda層,一行搞定,缺點是無法處理太複雜的邏輯,例如:

from keras.layers import *
from keras import backend as K

x_in = Input(shape=(10,))
x = Lambda(lambda x: x+2)(x_in) # 對輸入加上2

Python/tf/keras/layers/Lambda” data-draft-node=”block” data-draft-type=”link-card” class=”LinkCard LinkCard–noImage”>//www.tensorflow.org/api_docs/python/tf/keras/layers/Lambdawww.tensorflow.org

lambda layer的用法和pandas中的lambda的用法基本類似,只不過自定義function的時候要通過tf或者tf.keras.backend的方式進行邏輯程式碼的撰寫;

2、通過繼承tf.keras.layer

//www.tensorflow.org/tutorials/customization/custom_layerswww.tensorflow.org

官方文檔寫的非常詳細了:

實現自定義層的最佳方法是繼承tf.keras.Layer類並實現:

  1. __init__ ,在其中進行所有與輸入無關的變數或常量的初始化
  2. build,在其中知道輸入張量的形狀,並可以進行其餘的初始化
  3. call,在這裡進行前向傳播邏輯的計算的定義

請注意,不必等到build被調用才創建變數,也可以在__init__中直接定義。但是,在build中創建變數的好處在於,可以根據build的參數 input shape來自動調整變數的shape。而在__init__中創建變數意味著需要顯式指定創建變數的形狀。

看demo:

class MyDenseLayer(tf.keras.layers.Layer):
  def __init__(self, num_outputs):
    super(MyDenseLayer, self).__init__()
    self.num_outputs = num_outputs

  def build(self, input_shape):
    self.kernel = self.add_weight("kernel",
                                  shape=[int(input_shape[-1]),
                                         self.num_outputs])

  def call(self, input):
    return tf.matmul(input, self.kernel)

layer = MyDenseLayer(10)

主要,這裡 call部分的input的shape,會自動關聯到 build中的input_shape上;

更加複雜的例子,可見蘇劍林大佬的科學空間:

「讓Keras更酷一些!」:精巧的層與花式的回調 – 科學空間|Scientific Spaceskexue.fm

class Dense_with_Center_loss(Layer):

    def __init__(self, output_dim, **kwargs):
        self.output_dim = output_dim
        super(Dense_with_Center_loss, self).__init__(**kwargs)

    def build(self, input_shape):
        # 添加可訓練參數
        self.kernel = self.add_weight(name='kernel',
                                      shape=(input_shape[1], self.output_dim),
                                      initializer='glorot_normal',
                                      trainable=True)
        self.bias = self.add_weight(name='bias',
                                    shape=(self.output_dim,),
                                    initializer='zeros',
                                    trainable=True)
        self.centers = self.add_weight(name='centers',
                                       shape=(self.output_dim, input_shape[1]),
                                       initializer='glorot_normal',
                                       trainable=True)

    def call(self, inputs):
        # 對於center loss來說,返回結果還是跟Dense的返回結果一致
        # 所以還是普通的矩陣乘法加上偏置
        self.inputs = inputs
        return K.dot(inputs, self.kernel) + self.bias

    def compute_output_shape(self, input_shape):
        return (input_shape[0], self.output_dim)

    def loss(self, y_true, y_pred, lamb=0.5):
        # 定義完整的loss
        y_true = K.cast(y_true, 'int32') # 保證y_true的dtype為int32
        crossentropy = K.sparse_categorical_crossentropy(y_true, y_pred, from_logits=True)
        centers = K.gather(self.centers, y_true[:, 0]) # 取出樣本中心
        center_loss = K.sum(K.square(centers - self.inputs), axis=1) # 計算center loss
        return crossentropy + lamb * center_loss


f_size = 2

x_in = Input(shape=(784,))
f = Dense(f_size)(x_in)

dense_center = Dense_with_Center_loss(10)
output = dense_center(f)

model = Model(x_in, output)
model.compile(loss=dense_center.loss,
              optimizer='adam',
              metrics=['sparse_categorical_accuracy'])

# 這裡是y_train是類別的整數id,不用轉為one hot
model.fit(x_train, y_train, epochs=10)

這裡是完全使用keras.backend的方式來定義的,當然也可以使用tf的函數來定義,這裡給了一種非常靈活的方式,loss和層的權重有關係,將loss直接定義在層中。

//www.tensorflow.org/guide/keras/custom_layers_and_models?hl=zh-cnwww.tensorflow.org

//keras.io/examples/keras_recipes/antirectifier/keras.io圖標

Keras documentation: Simple custom layer example: Antirectifier

Keras documentation: Simple custom layer example: Antirectifierkeras.io圖標

另外就是序列化存儲model的時候需要對自定義layer指定get config的方法,具體可見上。不過其實如果要序列化保存模型,個人覺得保存weight和biase就行,再用的時候定義一樣的結構然後load weight,這樣可以避免很多麻煩的問題。


自定義model:

自定義model其實很簡單了,本質上就是對常規的keras的model搭建做了一層封裝:

//www.tensorflow.org/tutorials/customization/custom_layerswww.tensorflow.org

class ResnetIdentityBlock(tf.keras.Model):
  def __init__(self, kernel_size, filters):
    super(ResnetIdentityBlock, self).__init__(name='')
    filters1, filters2, filters3 = filters

    self.conv2a = tf.keras.layers.Conv2D(filters1, (1, 1))
    self.bn2a = tf.keras.layers.BatchNormalization()

    self.conv2b = tf.keras.layers.Conv2D(filters2, kernel_size, padding='same')
    self.bn2b = tf.keras.layers.BatchNormalization()

    self.conv2c = tf.keras.layers.Conv2D(filters3, (1, 1))
    self.bn2c = tf.keras.layers.BatchNormalization()

  def call(self, input_tensor, training=False):
    x = self.conv2a(input_tensor)
    x = self.bn2a(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2b(x)
    x = self.bn2b(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2c(x)
    x = self.bn2c(x, training=training)

    x += input_tensor
    return tf.nn.relu(x)


block = ResnetIdentityBlock(1, [1, 2, 3])

自定義model其實就是抄襲了torch的用法,訓練需要人工去寫循環

//www.tensorflow.org/guide/keras/custom_layers_and_models?hl=zh-cnwww.tensorflow.org

個人不是很喜歡這種方法,torch本身我最煩的地方就是這裡了。


「讓Keras更酷一些!」:隨意的輸出和靈活的歸一化 – 科學空間|Scientific Spaceskexue.fm

其它的高級用法


「讓Keras更酷一些!」:分層的學習率和自由的梯度 – 科學空間|Scientific Spaceskexue.fm

分層學習率和更靈活的梯度


當Bert遇上Keras:這可能是Bert最簡單的打開姿勢 – 科學空間|Scientific Spaceskexue.fm圖標

bert+keras


6個派生優化器的簡單介紹及其實現 – 科學空間|Scientific Spaceskexue.fm

更加花哨的adam的實現


Keras:Tensorflow的黃金標準 – 科學空間|Scientific Spaceskexue.fm圖標「讓Keras更酷一些!」:層與模型的重用技巧 – 科學空間|Scientific Spaceskexue.fm


tf.recompute_grad

節省顯示記憶體的重計算技巧也有了Keras版了 – 科學空間|Scientific Spaceskexue.fm


keras中的one cycle學習率和lr finder功能

//github.com/titu1994/keras-one-cyclegithub.com

//github.com/surmenok/keras_lr_findergithub.com


optimizer 優化器的一些資料,暫時用不到太複雜的時間就不看了。。。:

tensorflow裡面name_scope, variable_scope等如何理解?www.zhihu.com圖標//github.com/bojone/accum_optimizer_for_keras/blob/master/accum_optimizer.pygithub.com

Keras 手動設置優化器 設置梯度操作 實現小記憶體大Batch更新blog.csdn.net圖標//github.com/bojone/accum_optimizer_for_keras/blob/master/accum_optimizer.pygithub.com

//github.com/CyberZHG/keras-radam/blob/master/keras_radam/optimizers.pygithub.com


「讓Keras更酷一些!」:層中層與mask – 科學空間|Scientific Spacesspaces.ac.cn

蘇建林大佬可以算是keras的忠實用戶了,他的科學空間部落格中關於keras的一些靈活的用法都是很好的文章,tf keras黨可以多看看,畢竟tf2.X之後基本上都是keras式的api的天下了。。。而keras和tf.keras在api的調用上差異很小。