常见功能模块

常见网络层类

tf.keras.layers类中包含了全连接层、 激活函数层、 池化层、 卷积层、 循环神经网络层等常见网络层的类,可以直接使用这些类创建网络层,这里以softmax为例:

1
2
3
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers#导入常见网络层类
1
2
3
x=tf.constant([2.,1.,0.1])
layer=layers.Softmax(axis=-1)#创建softmax层
out=layer(x)#调用softmax前向计算,输出为out
1
out
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.6590012 , 0.24243298, 0.09856589], dtype=float32)>

当然,也可以使用tf.nn.softmax()函数计算

1
out=tf.nn.softmax(x)
1
out
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.6590012 , 0.24243298, 0.09856589], dtype=float32)>

看,结果是一样的

网络容器

Keras提供的网络容器Sequential将多个网络层封装成一个大网络模型,只需要调用网络模型的实例一次即可完成数据从第一层到最末层的顺序传播运算

1
2
3
4
5
6
7
8
9
10
from tensorflow.keras import layers,Sequential
#2层的全连接层加上单独的激活函数层
network=Sequential([
layers.Dense(3,activation=None),#好像等价于layers.Dense(3,activation=None,input_shape=(3,)),
layers.ReLU(),
layers.Dense(2,activation=None),
layers.ReLU()
])
x=tf.random.normal([4,3])
out=network(x)
1
2
out

<tf.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[0.        , 0.84483266],
       [0.        , 0.4448954 ],
       [0.6126369 , 0.30855933],
       [0.        , 0.        ]], dtype=float32)>

可以通过add()方法追加新的网络层,实现动态创建网络

1
2
3
4
5
6
7
8
9
10
11
from tensorflow.keras import layers,Sequential
layers_num=2
network=Sequential([])#空的网络容器,好像等价于network=Sequential()
for _ in range(layers_num):
network.add(layers.Dense(3))#3是输出的维度,输入的维度自动确定??不,是根据下面的build确定的
network.add(layers.ReLU())
#在完成网络创建时,网络层类并没有创建内部权值张量等成员变量,
#此时通过调用类的 build 方法并指定输入大小,即可自动创建所有层的内部张量
network.build(input_shape=(4,4))#创建网络参数
network.summary()

Model: "sequential_4"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_12 (Dense)             multiple                  15        
_________________________________________________________________
re_lu_12 (ReLU)              multiple                  0         
_________________________________________________________________
dense_13 (Dense)             multiple                  12        
_________________________________________________________________
re_lu_13 (ReLU)              multiple                  0         
=================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0
_________________________________________________________________

Sequential 对象的 trainable_variablesvariables 包含了所有层的待优化张量列表和全部张量列表

1
2
3
for p in network.trainable_variables:
print(p.name,'||',p.shape)

dense_12/kernel:0 || (4, 3)
dense_12/bias:0 || (3,)
dense_13/kernel:0 || (3, 3)
dense_13/bias:0 || (3,)

第一层的shape(4, 3),因为输入是(4,4),第一层的输出为(*,3),于是自动检测到*为输入的4,所以该层参数的shape(4,3),同时bias也是shape3

同理,第二层的 (3, 3)中,其输入为刚刚上一层的输出shape,即(4,3),由于该层的输出shape(*,3),于是自动检测出*3,所以该层参数的shape(3,3),同时biasshape也是3

这种提前搭建好网络结构,最后一次性传入值的方式可以不在第一层指定输入维度的大小?

是的,因为使用了网络容器?

可以发现,这里的shape(x,y)中,xy其实是上一层的神经元个数和本层的神经元个数

image.png

模型装配、 训练与测试

模型装配

Keras 中,有 2 个比较特殊的类: eras.Modelkeras.layers.Layer 类。其中 Layer类是网络层的母类,定义了网络层的一些常见功能,如添加权值、 管理权值列表等。Model 类是网络的母类,除了具有 Layer 类的功能,还添加了保存模型、加载模型、 训练与测试模型等便捷功能。

Sequential也是Model的子类

1
2
3
4
5
6
7
8
network=Sequential([layers.Dense(256,activation='relu'),
layers.Dense(128,activation='relu'),
layers.Dense(64,activation='relu'),
layers.Dense(32,activation='relu'),
layers.Dense(10)])
network.build(input_shape=(4,28*28))
network.summary()

Model: "sequential_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_14 (Dense)             multiple                  200960    
_________________________________________________________________
dense_15 (Dense)             multiple                  32896     
_________________________________________________________________
dense_16 (Dense)             multiple                  8256      
_________________________________________________________________
dense_17 (Dense)             multiple                  2080      
_________________________________________________________________
dense_18 (Dense)             multiple                  330       
=================================================================
Total params: 244,522
Trainable params: 244,522
Non-trainable params: 0
_________________________________________________________________

各层的Param # 解释如下:

28*28 *256+256=200960

256*128+128=32896

128*64+64=8256

64*32+32=2080

32*10+10=320

现在进行模型装配

1
2
from tensorflow.keras import optimizers,losses

1
2
3
4
5
6
# 采用 Adam 优化器,学习率为 0.01;采用交叉熵损失函数,包含 Softmax
network.compile(optimizer=optimizers.Adam(lr=0.01),
loss=losses.CategoricalCrossentropy(from_logits=True),
metrics=['accurary']#设置测量指标为准确率
)

模型训练

image.png

模型测试

image.png

模型保存与加载

Keras中,有3种方式

张量方式

image.png

image.png

网络方式

image.png

SavedModel 方式

当需要将模型部署到其他平台时,采用TensorFlow提出的 SavedModel 方式更具有平台无关性

image.png

自定义网络

仅仅使用Keras提供的接口固然方便,但是灵活性不强,所以有时需要自定义网络

对于需要创建自定义逻辑的网络层,可以通过自定义类来实现。在创建自定义网络层类时,需要继承自 layers.Layer 基类; 创建自定义的网络类时,需要继承自 keras.Model 基类, 这样建立的自定义类才能够方便的利用 Layer/Model 基类提供的参数管理等功能,同时也能够与其他的标准网络层类交互使用。

自定义网络层

对于自定义的网络层, 至少需要实现初始化__init__方法和前向传播逻辑 call 方法。 我们以某个具体的自定义网络层为例, 假设需要一个没有偏置向量的全连接层,即 bias0, 同时固定激活函数为ReLU 函数。 尽管这可以通过标准的 Dense 层创建,但我们还是通过实现这个“特别的”网络层类来阐述如何实现自定义网络层

1
2
3
4
5
6
7
8
class MyDense(layers.Layer):
def __init__(self,input_dim,output_dim):
super(MyDense,self).__init__()
# 创建权值张量并添加到类管理列表中,设置为需要优化
#self.add_variable 会返回张量𝑾的 Python 引用,而变量名 name 由TensorFlow 内部维护
# 使用 add_variable(母类方法) 创建可训练变量(即待优化的权重参数),交给模型管理。
self.kernel=self.add_variable('w',[input_dim,output_dim],trainable=True)

1
2
3
#实例化输入为4,输出为3个神经元的自定义层
net=MyDense(4,3)

1
2
3
# 类的全部参数列表
net.variables

[<tf.Variable 'w:0' shape=(4, 3) dtype=float32, numpy=
 array([[ 0.8965106 ,  0.5483446 ,  0.6216595 ],
        [-0.45314264, -0.00154513, -0.5543232 ],
        [ 0.40444565, -0.27985066, -0.66103315],
        [-0.16541952,  0.82652354, -0.9168886 ]], dtype=float32)>]
1
2
3
#类的待优化参数列表
net.trainable_variables

[<tf.Variable 'w:0' shape=(4, 3) dtype=float32, numpy=
 array([[ 0.8965106 ,  0.5483446 ,  0.6216595 ],
        [-0.45314264, -0.00154513, -0.5543232 ],
        [ 0.40444565, -0.27985066, -0.66103315],
        [-0.16541952,  0.82652354, -0.9168886 ]], dtype=float32)>]

可以看到𝑾张量被自动纳入类的参数列表

通过修改为 self.kernel = self.add_variable('w', [input_dim, output_dim], trainable=False),我们可以设置𝑾张量不需要被优化,此时再来观测张量的管理状态

1
2
3
4
5
6
7
class MyDense(layers.Layer):
def __init__(self,input_dim,output_dim):
super(MyDense,self).__init__()
# 创建权值张量并添加到类管理列表中,设置为需要优化
#self.add_variable 会返回张量𝑾的 Python 引用,而变量名 name 由TensorFlow 内部维护
self.kernel=self.add_variable('w',[input_dim,output_dim],trainable=False)

1
2
3
#实例化输入为4,输出为3个神经元的自定义层
net=MyDense(4,3)

1
2
3
# 类的全部参数列表
net.variables

[<tf.Variable 'w:0' shape=(4, 3) dtype=float32, numpy=
 array([[ 0.25570762,  0.81627464,  0.60139644],
        [ 0.45930374,  0.49217367,  0.87035775],
        [-0.0026539 , -0.05813193,  0.43993485],
        [ 0.22716558, -0.19094968, -0.73896277]], dtype=float32)>]
1
2
3
#类的待优化参数列表
net.trainable_variables

[]

可以看到,此时张量并不会被 trainable_variables 管理

此外,在类初始化中创建为tf.Variable类型的成员变量也会自动纳入张量管理中

1
2
3
4
5
6
7
class MyDense(layers.Layer):
def __init__(self,input_dim,output_dim):
super(MyDense,self).__init__()
# 创建权值张量并添加到类管理列表中,设置为需要优化
#self.add_variable 会返回张量𝑾的 Python 引用,而变量名 name 由TensorFlow 内部维护
self.kernel=tf.Variable(tf.random.normal([input_dim,output_dim]),trainable=False)

1
2
3
#实例化输入为4,输出为3个神经元的自定义层
net=MyDense(4,3)

1
2
3
# 类的全部参数列表
net.variables

[<tf.Variable 'Variable:0' shape=(4, 3) dtype=float32, numpy=
 array([[-0.8323358 ,  0.59394956, -0.02408018],
        [ 0.31579047, -1.4819138 , -1.163358  ],
        [ 1.4938904 , -0.949228  , -0.80765593],
        [-0.23702401,  2.0464106 , -1.0387905 ]], dtype=float32)>]
1
2
3
#类的待优化参数列表
net.trainable_variables

[]

接下来自定义的类的前向逻辑运算,代码更新如下

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyDense(layers.Layer):
def __init__(self,input_dim,output_dim):
super(MyDense,self).__init__()
# 创建权值张量并添加到类管理列表中,设置为需要优化
#self.add_variable 会返回张量𝑾的 Python 引用,而变量名 name 由TensorFlow 内部维护
self.kernel=self.add_variable('w',[input_dim,output_dim],trainable=True)
print(self.kernel)
#前向运算
def call(self,inputs,training=None):
out=inputs@self.kernel
out=tf.nn.relu(out)
return out

1
2
3
#实例化输入为4,输出为3个神经元的自定义层
net=MyDense(4,3)

<tf.Variable 'w:0' shape=(4, 3) dtype=float32, numpy=
array([[ 0.0827105 , -0.20058179, -0.8756637 ],
       [-0.29692435,  0.9056848 , -0.517593  ],
       [ 0.5766784 , -0.7444072 , -0.06552321],
       [-0.30638957,  0.2780875 , -0.51893866]], dtype=float32)>
1
2
3
4
5
#试一下
X=np.arange(1,9)
X=X.reshape(2,4)
net.call(X)

<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0.        , 0.48991632, 0.        ],
       [0.2176385 , 1.4450493 , 0.        ]], dtype=float32)>

自定义网络

自定义网络类可以和其他标准类一样,通过 Sequential 容器方便地封成一个网络模型

1
2
3
4
5
6
network=Sequential([MyDense(784,256),
MyDense(256,128),
MyDense(128,64),
MyDense(64,32),
MyDense(32,10)])

1
2
network.build(input_shape=(None,28*28))

1
2
network.summary()

Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
my_dense_18 (MyDense)        multiple                  200704    
_________________________________________________________________
my_dense_19 (MyDense)        multiple                  32768     
_________________________________________________________________
my_dense_20 (MyDense)        multiple                  8192      
_________________________________________________________________
my_dense_21 (MyDense)        multiple                  2048      
_________________________________________________________________
my_dense_22 (MyDense)        multiple                  320       
=================================================================
Total params: 244,032
Trainable params: 244,032
Non-trainable params: 0
_________________________________________________________________

这里和之前调用layers.Dense创建的网络几乎是一样的,区别仅在于自定义的网络没有加偏置项

下面开始创建自定义网络类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyModel(keras.Model):
def __init__(self):
super(MyModel,self).__init__()
self.fc1 = MyDense(28*28, 256)
self.fc2 = MyDense(256, 128)
self.fc3 = MyDense(128, 64)
self.fc4 = MyDense(64, 32)
self.fc5 = MyDense(32, 10)

def call(self,inputs,training=None):
# 自定义前向运算逻辑
#疑问:这里等价于self.fc1.call(inputs),后面同理?
x = self.fc1(inputs)
x = self.fc2(x)
x = self.fc3(x)
x = self.fc4(x)
x = self.fc5(x)
return x

1
2
3
#实例化
net=MyModel()

1
2
3
4
5
6
net.compile(optimizer=optimizers.Adam(lr=0.01),
loss=tf.losses.CategoricalCrossentropy(from_logits=True),
metrics=['accuracy']
)


这里没看明白orz~

总结一下:我好菜啊