TF01-06:Keras的Layer使用

本文主要说明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'>
Model卷积运算结果比较

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的运算

  • 既然Layer承担了运算的职责,本身等同于函数或者过程。

1. 定制的Layer的编程模式

  • Layer定制需要搞清楚Layer相关成员函数作用与工作流程。

1.1. 理解Layer运算的相关接口说明

  • Keras 层的工作机制就是一个调用流程,并通过继承覆盖某些成员函数就可以实现Layer工作过程的干预,其中与计算有关的只需要覆盖实现三个方法即可:
    1. build(input_shape): 这是你定义权重的地方。这个方法必须设 self.built = True,可以通过调用 super([Layer], self).build() 完成。
    2. call(x): 这里是编写层的功能逻辑的地方。你只需要关注传入 call 的第一个参数:输入张量,除非你希望你的层支持masking。
    3. compute_output_shape(input_shape): 如果你的层更改了输入张量的形状,你应该在这里定义形状变化的逻辑,这让Keras能够自动推断各层的形状。
  1. build函数接口

    • 覆盖这个函数,在函数中实现权重的定义。
    • 这个方法必须设 self.built = True,可以通过调用super([Layer], self).build() 完成。
    • 该函数无需返回数据,主要用来初始化self.weights
    build(input_shape)
  1. call函数接口
    • 覆盖这个函数,在函数中实现功能逻辑的运算;
    • 这个函数的第一个参数inputs就是输入张量;一般使用这个参数就足够;如果需要支持masking运算,可以使用更多的参数;
    • 返回一个张量,或者输出值;
    call(
        inputs,
        **kwargs
    )
  1. 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的工作流程说明

  1. 使用对象调用

    • 使用对象调用,会先调用build函数,构建权重矩阵;
    • 对象调用主要用来构建计算环境;
    • build的参数类型为:<class 'tensorflow.python.framework.tensor_shape.TensorShape'>
  2. 使用call调用

    • 不会调用build函数,直接计算;
    • 通过对象调用,传递的参数类型是:<class 'tensorflow.python.framework.ops.Tensor'>
    • 通过call函数调用,传递的参数类型就是传递的原始类型。
  3. 在Model中使用的情况

    • call的参数类型为:<class 'tensorflow.python.framework.ops.EagerTensor'>
    • Model返回的是<Tensor>
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>
  1. 更多的说明,可以参考源代码
    • 从上面代码的运行,我们可以得知compute_output_shape函数没有被调用。
    • Dense实现的代码地址:https://github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/keras/layers/core.py#L913-L1079

2. 运行官方文档提供的例子

  • 在官方的文档中提供了一个例子,运行的时候,需要注意几个问题:

    1. 数据类型的问题;
    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
  • 只需要在上述代码上修改即可。

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函数!
定制Layer卷积运算结果比较

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容