Keras是一个高度封装的库,它的优点是可以进行快速的建模,缺点是它不处理底层运算,如张量内积等。Keras提供众多常见的已编写好的函数和层对象,例如常见的K.reshape函数、K.sigmoid函数、卷积层、池化层等,可以直接通代码调用。由于新的论文中使用的算法比较新颖、涉及的方法千方百态,Keras不可能涉及地面面俱到。此外,在实现算法过程中,往往会根据具体项目的一些独特性设计一些函数和层。所以,在这个时候,应该在算法中使用Keras自定义一些函数和层。
有时候我们会遇到tensorflow或者Keras中没有定义的Op操作,需要自定义。这时候需要借助tf.py_func(func, inp, Tout, stateful=True, name=None),更高版本的tf使用tf.py_function()。tf.py_func()接收的是tensor,然后将其转化为numpy array送入我们自定义的my_func函数,最后再将my_func函数输出的numpy array转化为tensor返回。
在使用tf.py_func的过程中,主要核心是使用前三个参数。第一个参数func,也是最重要的,是一个用户自定制的函数,输入numpy array,输出也是numpy array,在该函数中,可以自由使用np操作。第二个参数inp,是func函数接收的输入,是一个列表。第三个参数Tout,指定了func函数返回的numpy array转化成tensor后的格式,如果是返回多个值,就是一个列表或元组;如果只有一个返回值,就是一个单独的dtype类型(当然也可以用列表括起来)。
def my_func(a, b):
return a + b, a-b
x, y = tf.py_func(my_func,[a, b],[tf.float32, tf.float32])
注意:第一就是func函数的返回值类型一定要和Tout指定的tensor类型一致。第二就是, tf.py_func中的func是脱离Graph的。在func中不能定义可训练的参数参与网络训练(反传)。
Keras中的层大致上分为两种类型:
第一种是带有训练参数的:比如BachNormalization层、Dense层、Conv2D层等等,在网络模型训练的过程中需要更新层的权重和偏置等参数项;
第二种是不带训练参数的:比如Add层、AveragePooling层、DroupOut层等等,在网络模型中不需要更新它的权重,只需要对输入进行加工处理再输出就行了。
在项目开发过程中自定义的层无非就是上面两种,一种是带有参数的,一种是不带参数的,不管是哪一种,Keras对自定义层都提供了良好的支持。
对于简单、无状态的自定义操作,可以通过 keras.layers.Lambda 层来实现。即使用keras.layers.Lambda()。如果算法自定义层中不包含可训练的权重,而只是对上一层输出做一些函数变换,那么可以直接使用lambda函数:
keras.layers.Lambda(function, output_shape=None, mask=None, arguments=None)
参数说明: function:要实现的函数,该函数仅接受一个变量,即上一层的输出 。output_shape:函数应该返回的值的shape,可以是一个tuple,也可以是一个根据输入shape计算输出shape的函数。mask: 掩膜。arguments:可选,字典,用来记录向函数中传递的其他关键字参数。
x = Lambda(my_func,output_shape=(4,1),arguments={‘params’:0})(x)
注意:也不是说对于没有训练参数的层就一定要用Lambda层,也可以使用自定义的层,只不过是没必要那么复杂而已。此外,Lambda 是可以进行参数传递的。
但是对于那些包含了可训练权重的自定义层,应该自己实现这种层。在这种情况下,需要定义的是一个全新的、拥有可训练权重的层,这个时候就需要使用下面的方法。即通过编写自定义层,从Layer中继承。(下面的内容是使用Keras 2.2.4)
要定制自己的层,需要实现下面三个方法:
虽然在自定义类的时候,上面的三个方法,build、call、compute_outout_shape 是必须要有的,但是还有很多其他的方法和属性可以帮助我们更好的实现自定义层。
from keras import backend as K
from keras.layers import Layer, BatchNormalization
class MyLayer(Layer):
def __init__(self, output_dim, **kwargs):
self.output_dim = output_dim
self.bn = BatchNormalization ()
super(MyLayer, self).__init__(**kwargs)
def build(self, input_shape):
self.kernel = self.add_weight(name='kernel',
shape=(input_shape[1], self.output_dim),
initializer='uniform',
trainable=True)
self.bn.build(input_shape)
self. trainable_weights += self.bn. trainable_weights
super(MyLayer, self).build(input_shape) # Be sure to call this at the end
def call(self, x):
x = self.bn(x)
return K.dot(x, self.kernel)
def compute_output_shape(self, input_shape):
return (input_shape[0], self.output_dim)
对于新命名的函数,需要重新申明,否则在编译或者模型加载过程中会报错。申明的方法可以是以下两种方式:
方式1:
keras.utils.generic_utils.get_custom_object().update({‘new_layer’:new_layer})
keras.utils.generic_utils.get_custom_object().update({‘new_function’:new_function})
方式2:
keras.models.load_model(‘model.hdf5’,custom_objects={‘new_layer’:new_layer})
keras.models.load_model(‘model.hdf5’,custom_objects={‘new_function’:new_function})