TensorFlow定义网络层

Define Layer

背景介绍

  Define Layer(定义网络层):是深度学习中的基础内容,想使用深度学习方法解决实际问题,首先就需要建立一个网络层,今天以LeNet-5模型为例,给入门的小伙伴们提供TensorFlow2.0两种定义网络层的方法。

layer

第一种方法

只用TensorFlow中定义好的网络层,直接使用keras.layer下的网络层类即可,是最简单的网络层创建方法,缺点也很明显,灵活度很低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tensorflow.keras as keras


if __name__ == '__main__':

model = keras.models.Sequential([keras.layers.Input(shape=(28, 28, 1), name='input'),
keras.layers.Conv2D(6, kernel_size=(5, 5), padding='same', activation='relu', name='conv1'),
keras.layers.MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='maxpool1'),
keras.layers.Conv2D(16, kernel_size=(5, 5), padding='valid', activation='relu', name='conv2'),
keras.layers.MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='maxpool2'),
keras.layers.Flatten(name='flatten'),
keras.layers.Dense(120, activation='relu', name='dense1'),
keras.layers.Dense(84, activation='relu', name='dense2'),
keras.layers.Dense(10, activation='softmax', name='dense3')], name='LeNet-5')

model.build(input_shape=(None, 28, 28, 1))
model.summary()

model1

第二种方法

通过继承keras.layer.Layer来创建模型,是最灵活的方式,可以满足任何网络层的定义,但是难度也更大,必须通过重载call函数自定义调用方式
__init__函数,是自定义层的构造函数,可以在这里准备一些和输入尺寸无关的网络层,如定义一些参数,接收构造函数的输入等等
call函数,inputs是上一层网络的输出,return是自定义网络的输出,在call函数中写入本层要实现的内容
build函数,上面两个都是必须的函数,而build函数是根据需要来创建的,一些参数可能需要根据上一层的网络的输出来确定的,如reshape,resize等等操作,因此这些参数的定义无法在__init__中完成,所以需要在build中完成参数的定义,input_shape参数是上一层网络的输出维度。在没有定义build函数时,会默认调用一次空的build函数
dynamic=False参数,在调用父类的构造函数时,可以传入dynamic参数,其中默认为False,搭建静态图,如果需要动态调整参数,则需要写入dynamic=True

因此当我们需要自定义一个网络层时,我们的思路为:

  1. 根据需要判断自定义层是否可以由keras.layer提供的网络层组合而成,如CNN中大量存在Convlution+BN+ReLU层,因此我们可以定义一个层一次性完成三个步骤,而且这三个层都存在于keras.layer中,所以使用卷积层时也不必要使用build函数,直接调用keras.layers.Conv2D接口即可。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Conv_Bn_ReLU(keras.layers.Layer):
    def __init__(self, filters, kernel_size, strides, padding, name):
    super(Conv_Bn_ReLU, self).__init__(name=name)
    self.conv = keras.layers.Conv2D(filters, kernel_size, strides, padding)
    self.bn = keras.layers.BatchNormalization()
    self.relu = keras.layers.ReLU()

    def call(self, inputs, **kwargs):
    conv = self.conv(inputs)
    bn = self.bn(conv)
    output = self.relu(bn)

    return output
  2. 根据需要判断是否必须通过上一层输出的尺寸进行操作,如果是则需要定义build函数,如果不需要根据上一层尺寸来判断,则可以在__init__中创建

定义参数说明:

  • __init__中定义参数,可以使用self.add_weight()创建指定形状的参数,但是无法创建和输入尺寸相关的参数,**也可以使用tf.Variable()创建指定形状的参数,其中括号里可以为numpy数组,也可以为EagerTensor(动态张量)**。
  • build中定义参数,可以使用self.add_weight()创建指定形状的参数,而且可以创建和输入尺寸相关的参数,也可以使用tf.Variable()创建指定形状的参数,但是使用Input层,build方法或者使用fit和train_on_batch训练时会传入输入参数尺寸,因为没有具体数值,因此会自动启动静态图,在静态模式下无法产生EagerTensor(动态张量),所以不能在Variable的参数列表中传入动态张量,但是仍然可以传入numpy格式的参数。
  • 因此推荐在__init__中搭建和输入不相关的参数,在build中搭建和输入相关的参数

下面是完全使用自定义网络层完成的LeNet-5网络模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import tensorflow as tf
import tensorflow.keras as keras


class MyDense(keras.layers.Layer):
def __init__(self, units, name):
super(MyDense, self).__init__(name=name)
self.units = units

def build(self, input_shape):
self.w = self.add_weight(name='kernel', shape=(input_shape[-1], self.units), initializer=keras.initializers.GlorotUniform())
self.b = self.add_weight(name='bias', shape=(self.units,), initializer=keras.initializers.Zeros())

def call(self, inputs, **kwargs):

return tf.matmul(inputs, self.w) + self.b


class MyConv(keras.layers.Layer):
def __init__(self, filters, kernel_size, strides, padding, name):
super(MyConv, self).__init__(name=name)
self.filters = filters
self.kernel_size = kernel_size
self.strides = strides
self.padding = padding

def build(self, input_shape):
self.w = self.add_weight(name='kernel', shape=(self.kernel_size, self.kernel_size, input_shape[-1], self.filters), initializer=keras.initializers.GlorotUniform())
self.b = self.add_weight(name='bias', shape=(self.filters,), initializer=keras.initializers.zeros)

def call(self, inputs, **kwargs):

return tf.nn.bias_add(tf.nn.conv2d(inputs, self.w, (1, self.strides, self.strides, 1), self.padding), self.b)


class MyRelu(keras.layers.Layer):
def __init__(self, name):
super(MyRelu, self).__init__(name=name)

def call(self, inputs, **kwargs):

return tf.nn.relu(inputs)


class MyFlatten(keras.layers.Layer):
def __init__(self, name):
super(MyFlatten, self).__init__(name=name)

def call(self, inputs, **kwargs):

return tf.reshape(inputs, (-1, tf.reduce_prod(inputs.shape[1:])))


class MySoftmax(keras.layers.Layer):
def __init__(self, name):
super(MySoftmax, self).__init__(name=name)

def call(self, inputs, **kwargs):

return tf.nn.softmax(inputs)


class MyMaxpool(keras.layers.Layer):
def __init__(self, kernel_size, strides, padding, name):
super(MyMaxpool, self).__init__(name=name)
self.kernel_size = kernel_size
self.strides = strides
self.padding = padding

def call(self, inputs, **kwargs):

return tf.nn.max_pool2d(inputs, (1, self.kernel_size, self.kernel_size, 1), (1, self.strides, self.strides, 1), self.padding)


if __name__ == '__main__':

model = keras.models.Sequential([keras.layers.Input(shape=(28, 28, 1), name='input'),
MyConv(filters=6, kernel_size=3, strides=1, padding='SAME', name='conv1'),
MyRelu(name='relu1'),
MyMaxpool(kernel_size=2, strides=2, padding='SAME', name='maxpool1'),
MyConv(filters=16, kernel_size=3, strides=1, padding='SAME', name='conv2'),
MyRelu(name='relu2'),
MyMaxpool(kernel_size=2, strides=2, padding='SAME', name='maxpool2'),
MyConv(filters=120, kernel_size=3, strides=1, padding='SAME', name='conv3'),
MyRelu(name='relu3'),
MyFlatten(name='flatten'),
MyDense(units=84, name='dense1'),
MyRelu(name='relu4'),
MyDense(units=10, name='dense2'),
MySoftmax(name='softmax')], name='LeNet-5')

model.build(input_shape=(None, 28, 28, 1))
model.summary()

layer2

小结

  网络层的第二种定义方式和模型定义的第三种定义方式非常类似,在call函数中定义符合自己需要的网络层,一般的顺序是先从keras.layer中寻找自己需要的层是否已经提供,如卷积层,池化层,全连接层等等,这时不需要我们手动定义,但是如果某些层非常复杂,如SE注意力层,需要我们手动定义,希望小伙伴们可以多多练习。

-------------本文结束感谢您的阅读-------------
0%