本文主要说明Keras中Layer的使用,更希望能通过应用理解Layer的实现原理,主要内容包含:
1. 通过Model来调用Layer的运算;
2. 直接使用Layer的运算;
3. 使用Layer封装定制运算;
一.使用Layer做运算
-
Layer主要是对操作与操作结果存储的封装,比如对图像执行卷积运算;运算的执行两种方式;
- 通过Model执行;
- 通过Layer的call执行;
下面使用图像的卷积运算处理来说明Layer承担的计算功能。
1. Conv2D层说明
Conv2D主要是构造器,属性与函数都是来自Layer。
构造器说明
__init__(
filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format=None,
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs
)
2. 使用Model来运算层
- 使用Model来运算,需要使用Layer构建一个Model;
% matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import numpy as np
# 1. 加载需要处理的图像
img = plt.imread('./04imgs/bird.png')
# 2. 初始化器
def kernel_initializer(shape, dtype=None):
print(shape)
# 浮雕卷积核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[ 0.0, 1.0, 1.0]
]
)
kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
kernel[:, :, 0, 0] = kernel_value
kernel[:, :, 1, 0] = kernel_value
kernel[:, :, 2, 0] = kernel_value
kernel[:, :, 3, 0] = kernel_value
return kernel
# 3. 输入层
# InputLayer一定按照Conv2D约定的数据格式定义input_shape
input_layer = keras.Input(shape=img.shape, dtype=tf.float32)
# 4. 卷积层
conv_layer = layers.Conv2D(
filters=1,
kernel_size=(3, 3),
strides=1,
padding='same',
kernel_initializer=kernel_initializer,
dtype=tf.float32
)
# 5. 构建模型
conv = conv_layer(input_layer)
model = keras.Model(inputs=input_layer, outputs=conv)
(3, 3, 4, 1)
# 6. 模型计算
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
conv_result = model(data) # 返回的类型是:class 'tensorflow.python.framework.ops.EagerTensor'
# 7. 显示计算前后图像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
print(type(conv_result))
cov_ax.imshow(conv_result[0, :, :, 0] , cmap='gray')
plt.show()
<class 'tensorflow.python.framework.ops.EagerTensor'>
3. Layer的运算
如果是单纯的使用Layer来实现计算,不需要动用Model,Model主要封装了优化训练函数fit等。
Layer的单纯计算通过call调用实现;
% matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import numpy as np
# 1. 加载需要处理的图像
img = plt.imread('./04imgs/bird.png')
# 2. 初始化器
def kernel_initializer(shape, dtype=None):
print(shape)
# 浮雕卷积核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[ 0.0, 1.0, 1.0]
]
)
kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
kernel[:, :, 0, 0] = kernel_value
kernel[:, :, 1, 0] = kernel_value
kernel[:, :, 2, 0] = kernel_value
kernel[:, :, 3, 0] = kernel_value
return kernel
# 3. 输入层
# InputLayer一定按照Conv2D约定的数据格式定义input_shape
input_layer = keras.Input(shape=img.shape, dtype=tf.float32)
# 4. 卷积层
conv_layer = layers.Conv2D(
filters=1,
kernel_size=(3, 3),
strides=1,
padding='same',
kernel_initializer=kernel_initializer,
dtype=tf.float32
)
# 层的链式调用
conv_layer(input_layer)
# 6. 模型计算
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
conv_result = conv_layer.call(data)
# 7. 显示计算前后图像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
cov_ax.imshow(conv_result[0, :, :, 0] , cmap='gray')
plt.show()
(3, 3, 4, 1)
二. 定制Layer的运算
- 既然Layer承担了运算的职责,本身等同于函数或者过程。
1. 定制的Layer的编程模式
- Layer定制需要搞清楚Layer相关成员函数作用与工作流程。
1.1. 理解Layer运算的相关接口说明
- Keras 层的工作机制就是一个调用流程,并通过继承覆盖某些成员函数就可以实现Layer工作过程的干预,其中与计算有关的只需要覆盖实现三个方法即可:
- build(input_shape): 这是你定义权重的地方。这个方法必须设 self.built = True,可以通过调用 super([Layer], self).build() 完成。
- call(x): 这里是编写层的功能逻辑的地方。你只需要关注传入 call 的第一个参数:输入张量,除非你希望你的层支持masking。
- compute_output_shape(input_shape): 如果你的层更改了输入张量的形状,你应该在这里定义形状变化的逻辑,这让Keras能够自动推断各层的形状。
-
build函数接口
- 覆盖这个函数,在函数中实现权重的定义。
- 这个方法必须设 self.built = True,可以通过调用
super([Layer], self).build()
完成。 - 该函数无需返回数据,主要用来初始化self.weights
build(input_shape)
- call函数接口
- 覆盖这个函数,在函数中实现功能逻辑的运算;
- 这个函数的第一个参数inputs就是输入张量;一般使用这个参数就足够;如果需要支持masking运算,可以使用更多的参数;
- 返回一个张量,或者输出值;
call(
inputs,
**kwargs
)
- compute_output_shape函数接口
- 可选:如果需要层更改输入张量的形状,则覆盖这个函数,在函数中实现形状变化的逻辑,定义新的形状;
- 该函数可以让Keras能够自动推断各层的形状;
compute_output_shape(input_shape)
1.2. 理解Layer的定制工作流程
-
通过程序的输出,观察如下几个方面,来理解Layer的工作流程:
- 输出的顺序;
- 输出的参数;
代码与执行输出如下:
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class WorkflowLayer(layers.Layer):
# 1. 覆盖build函数
def build(self, input_shape):
print('build权重初始化:', type(input_shape))
self.built=True
# super(WorkflowLayer, self).build(input_shape)
# 2. 覆盖call
def call(self, inputs, **kwargs):
print('call运算逻辑实现:', type(inputs), kwargs)
return inputs
# 3. 覆盖compute_output_shape
def compute_output_shape(self, input_shape):
print('......')
return super(WorkflowLayer,self).compute_output_shape(input_shape)
input_layer = keras.Input(shape=(None,))
workflow_layer = WorkflowLayer()
workflow_layer(input_layer) # 触发build与call函数调用
result = workflow_layer.call([1]) # 只触发call函数调用,不触发build函数调用
print(type(result))
build权重初始化: <class 'tensorflow.python.framework.tensor_shape.TensorShape'>
call运算逻辑实现: <class 'tensorflow.python.framework.ops.Tensor'> {}
call运算逻辑实现: <class 'list'> {}
<class 'list'>
1.3. Layer的工作流程说明
-
使用对象调用
- 使用对象调用,会先调用build函数,构建权重矩阵;
- 对象调用主要用来构建计算环境;
- build的参数类型为:
<class 'tensorflow.python.framework.tensor_shape.TensorShape'>
-
使用call调用
- 不会调用build函数,直接计算;
- 通过对象调用,传递的参数类型是:
<class 'tensorflow.python.framework.ops.Tensor'>
- 通过call函数调用,传递的参数类型就是传递的原始类型。
-
在Model中使用的情况
- call的参数类型为:
<class 'tensorflow.python.framework.ops.EagerTensor'>
- Model返回的是<Tensor>
- call的参数类型为:
input_layer = keras.Input(shape=(None,))
workflow_layer = WorkflowLayer()
workflow = workflow_layer(input_layer) # 触发build与call函数调用
model = keras.Model(inputs=input_layer, outputs=workflow)
model([1])
build权重初始化: <class 'tensorflow.python.framework.tensor_shape.TensorShape'>
call运算逻辑实现: <class 'tensorflow.python.framework.ops.Tensor'> {}
call运算逻辑实现: <class 'tensorflow.python.framework.ops.EagerTensor'> {}
<tf.Tensor: id=76, shape=(), dtype=int32, numpy=1>
- 更多的说明,可以参考源代码
- 从上面代码的运行,我们可以得知
compute_output_shape
函数没有被调用。 - Dense实现的代码地址:
https://github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/keras/layers/core.py#L913-L1079
- 从上面代码的运行,我们可以得知
2. 运行官方文档提供的例子
-
在官方的文档中提供了一个例子,运行的时候,需要注意几个问题:
- 数据类型的问题;
- 输入的数据需要使用张量Tensor类型;
下面是代码及其运行
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class MyLayer(layers.Layer):
def __init__(self, output_dim, **kwargs):
self.output_dim = output_dim
super(MyLayer, self).__init__(**kwargs)
def build(self, input_shape):
print('build函数!')
# 为该层创建一个可训练的权重
self.kernel = self.add_weight(name='kernel',
shape=(input_shape[1], self.output_dim),
initializer='uniform',
trainable=True, dtype=tf.float32) # <-----指定数据类型
super(MyLayer, self).build(input_shape) # 或者self.built = True
def call(self, x):
print('call函数!')
return K.dot(x, self.kernel) # 内积运算
#
def compute_output_shape(self, input_shape):
print('compute_output_shape函数!')
return (input_shape[0], self.output_dim)
layer = MyLayer(1)
input_layer = keras.Input(shape=(2,))
v_layer = layer(input_layer)
result = layer.call(tf.constant(np.array([[1.0, 2.0]], dtype=np.float32))) # <-----指定数据类型,而且需要是Tensor类型
print(result.numpy())
build函数!
call函数!
call函数!
[[0.07733139]]
3. 使用Layer封装实现backend的卷积操作
-
尽管这种封装没有多大意义,甚至是多此一举,唯一的好处就是练习下Layer的定制封装与Layer的作用。
- 在tenorflow中tensorflow.keras.backend中提供了基础的卷积运算:
conv2d
;
- 在tenorflow中tensorflow.keras.backend中提供了基础的卷积运算:
只需要在上述代码上修改即可。
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class ConvLayer(layers.Layer):
def build(self, input_shape):
print('build函数!')
# 构建卷积运算需要的核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[0.0, 1.0, 1.0]
]
)
self.kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
self.kernel[:, :, 0, 0] = kernel_value
self.kernel[:, :, 1, 0] = kernel_value
self.kernel[:, :, 2, 0] = kernel_value
self.kernel[:, :, 3, 0] = kernel_value
super(ConvLayer, self).build(input_shape) # 或者self.built = True
def call(self, x):
print('call函数!')
return K.conv2d(x, self.kernel, padding='same',strides=(1, 1, 1, 1))
# 加载图像
img = plt.imread('./04imgs/bird.png')
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
# 定制的Layer
layer = ConvLayer()
# 输入
input_layer = keras.Input(shape=img.shape)
# 输入调用
layer(input_layer)
result = layer.call(tf.constant(data, dtype=np.float32))
# print(result.numpy()) # 输出numpy的数组格式
# 显示计算前后图像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
cov_ax.imshow(result[0, :, :, 0], cmap='gray')
plt.show()
build函数!
call函数!
call函数!