目录
1.Keras简介
2.常见功能模块
2.1 常见网络层类
2.2 网络容器
3.模型装配、训练、测试
3.1 模型装配
3.2 模型训练
3.3 模型测试
4.模型的保存与加载
4.1 张量方式
4.2 网络方式
4.3 SaveModel方式
5.自定义网络
5.1 自定义网络层
5.2 自定义网络
6.测量工具
7.可视化
7.1 模型端
7.2 浏览器端
Keras是一个主要由 Python语言开发的开源神经网络计算库,最初由 François Chollet
编写,它被设计为高度模块化和易扩展的高层神经网络接口,使得用户可以不需要过多的专业知识就可以简洁、快速地完成模型的搭建与训练。Keras库分为前端和后端,其中后端一般是调用现有的深度学习框架实现底层运算,如 Theano、CNTK、TensorFlow等,前端接口是 Keras抽象过的一组统一接口函数。用户通过 Keras编写的代码可以轻松的切换不同的后端运行,灵活性较大。
Keras与 tf.keras有什么区别与联系呢?
其实 Keras可以理解为一套搭建与训练神经网络的高层 API协议,Keras本身已经实现了此协议,安装标准的 Keras库就可以方便地调用TensorFlow、CNTK等后端完成加速计算;
在 TensorFlow中,也实现了一套 Keras协议,即 tf.keras,它与 TensorFlow深度融合,且只能基于 TensorFlow后端运算,并对TensorFlow的支持更完美。对于使用 TensorFlow的开发者来说,tf.keras可以理解为一个普通的子模块,与其他子模块,如 tf.math,tf.data等并没有什么差别。
Keras提供了一系列高层的神经网络相关类和函数,如经典数据集加载函数、网络层
类、模型容器、损失函数类、优化器类、经典模型类等。
对于常见的神经网络层,可以使用张量方式的底层接口函数来实现,这些接口函数一般在 tf.nn模块中。更常用地,对于常见的网络层,我们一般直接使用层方式来完成模型的搭建,在tf.keras.layers命名空间(下文使用 layers指代 tf.keras.layers)中提供了大量常见网络层的类,如全连接层、激活函数层、池化层、卷积层、循环神经网络层等。
对于这些网络层类,只需要在创建时指定网络层的相关参数,并调用__call__方法即可完成前向计算。在调用__call__方法时,Keras会自动调用每个层的前向传播逻辑,这些逻辑一般实现在类的call函数中。
以 Softmax层为例,它既可以使用 tf.nn.softmax函数在前向传播逻辑中完成 Softmax运算,也可以通过 layers.Softmax(axis)类搭建 Softmax网络层,其中 axis参数指定进行
softmax运算的维度。代码如下:import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers #创建Softmax层,并调用__call__方法实现前向传播 x = tf.constant([2.,1.,0.1]) #创建输入张量 layer = layers.Softmax(axis=-1) #创建Softmax层 out = layer(x) #调用softmax前向计算,输出为out print(out) #直接使用函数 out_func = tf.nn.softmax(x) print(out_func) #打印out tf.Tensor([0.6590011 0.24243298 0.09856588], shape=(3,), dtype=float32) tf.Tensor([0.6590011 0.24243298 0.09856588], shape=(3,), dtype=float32)
对于常见的网络,需要手动调用每一层的类实例完成前向传播运算,当网络层数变得较深时,这一部分代码显得非常臃肿。
可以通过 Keras提供的网络容器 Sequential将多个网络层封装成一个大网络模型,只需要调用网络模型的实例一次即可完成数据从第一层到最末层的顺序传播运算。如:
#导入Sequential容器 import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers,Sequential #封装网络 network = Sequential([ layers.Dense(3,activation=None), #全连接层,未使用激活函数 layers.ReLU(), #激活函数层 layers.Dense(2,activation=None), layers.ReLU() ]) #创建输入张量 x = tf.random.normal([4,3]) #输出,输入从第一层开始,逐层传播至输出层,并返回输出层的输出 out = network(x) # print(out) #Sequential容器也可以通过add()方法追加新的网络层 #堆叠两次 layers_num = 2 #先创建空的网络容器 network = Sequential([]) for i in range(layers_num): network.add(layers.Dense(3)) #添加全连接层 network.add(layers.ReLU()) #添加激活函数 network.build(input_shape=(4,4)) #创建网络参数 network.summary() #通过 summary()函数可以方便打印出网络结构和参数量 #输出: Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_2 (Dense) (4, 3) 15 re_lu_2 (ReLU) (4, 3) 0 dense_3 (Dense) (4, 3) 12 re_lu_3 (ReLU) (4, 3) 0 ================================================================= Total params: 27 Trainable params: 27 Non-trainable params: 0 _________________________________________________________________
Layer列为每层的名字,这个名字由 TensorFlow内部维护,与 Python的对象名并不一样。
Param#列为层的参数个数,
Total params项统计出了总的参数量,
Trainable params为总的待优化参数量,
Non-trainable params为总的不需要优化的参数量。
当我们通过 Sequential容量封装多个网络层时,每层的参数列表将会自动并入Sequential容器的参数列表中,不需要人为合并网络参数列表,这也是 Sequential容器的便捷之处。
在训练网络时,一般的流程是通过前向计算获得网络的输出值,再通过损失函数计算
网络误差,然后通过自动求导工具计算梯度并更新,同时间隔性地测试网络的性能。对于
这种常用的训练逻辑,可以直接通过 Keras提供的模型装配与训练等高层接口实现,简洁
清晰。
在 Keras中,有 2个比较特殊的类:keras.Model和 keras.layers.Layer类。
Layer类是网络层的母类,定义了网络层的一些常见功能,如添加权值、管理权值列表等。
Model类是网络的母类,除了具有 Layer类的功能,还添加了保存模型、加载模型、训练
与测试模型等便捷功能。Sequential也是 Model的子类,因此具有 Model类的所有功能。
如Model及其子类的模型装配与训练功能。我们以 Sequential容器封装的网络为例,首先创建 5层的全连接网络,用于 MNIST手写数字图片识别:
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers,Sequential #建立网络 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,activation='relu'), ]) network.build(input_shape=(4,28*28)) #创建网络参数 network.summary() #打印所有层及参数 #输出 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (4, 256) 200960 dense_1 (Dense) (4, 128) 32896 dense_2 (Dense) (4, 64) 8256 dense_3 (Dense) (4, 32) 2080 dense_4 (Dense) (4, 10) 330 ================================================================= Total params: 244,522 Trainable params: 244,522 Non-trainable params: 0 _________________________________________________________________
创建网络后,正常的流程是循环迭代数据集多个 Epoch,每次按批产生训练数据、前向计算,然后通过损失函数计算误差值,并反向传播自动计算梯度、更新网络参数。这一部分
逻辑由于非常通用,在 Keras中提供了 compile()和 fit()函数方便实现上述逻辑。首先通过
compile函数指定网络使用的优化器对象、损失函数类型,评价指标等设定,这一步称为装
配。 如:#导入优化器,损失函数 from tensorflow.keras import optimizers,losses #模型装配 #采用Adam优化器,学习率为0.01;采用交叉熵损失,包含Softmax network.compile( optimizer=optimizers.Adam(learning_rate=0.01), loss=losses.CategoricalCrossentropy(from_logits=True), metrics=['accuracy'] #设置测量指标为准确率 )
模型装配完成后,即可通过 fit()函数送入待训练的数据集和验证用的数据集,这一步
称为模型训练。如:#制定训练集为train_db,验证集为val_db,训练五个epochs,每两个epoch验证一次 #返回训练轨迹信息保存在history中 history = network.fit(train_db,epochs=5,validation_data=val_db,validation_freq=2) history.history #打印训练记录
train_db为 tf.data.Dataset对象,也可以传入 Numpy Array类型的数据;
epochs参数指定训练迭代的 Epoch数量;
validation_data参数指定用于验证(测试)的数据集;
validation_freq参数指定验证的频率fit()函数的运行代表了网络的训练过程,因此会消耗相当的训练时间,并在训练结束后才返回,训练中产生的历史数据可以通过返回值对象取得。可以看到通过 compile&fit方
式实现的代码非常简洁和高效,大大缩减了开发时间。
network.evaluate(db_test) #模型测试,测试在db_test上的性能表现
Model基类除了可以便捷地完成网络的装配与训练、验证,还可以非常方便的预测和
测试。通过 Model.predict(x)方法即可完成模型的预测,如:
# 加载一个 batch的测试数据 x,y = next(iter(db_test)) print('predict x:', x.shape) # 打印当前 batch的形状 out = network.predict(x) # 模型预测,预测结果保存在 out中 print(out)
out即为网络的输出。通过上述代码即可使用训练好的模型去预测新样本的标签信息。
如果只是简单的测试模型的性能,可以通过 Model.evaluate(db)循环测试完 db数据集
上所有样本,并打印出性能指标,如:network.evaluate(db_test) # 模型测试,测试在 db_test上的性能表现
在 Keras中,有三种常用的模型保存与加载方法。
网络的状态主要体现在网络的结构以及网络层内部张量数据上,因此在拥有网络结构
源文件的条件下,直接保存网络张量参数到文件系统上是最轻量级的一种方式。通过调用 Model.save_weights(path)方法即可将当前的网络参数保存到 path文件上,如:
network.save_weights('weights.ckpt') # 保存模型的所有张量数据
在需要的时候,先创建好网络对象,然后调用网络对象的 load_weights(path)方法即可将指定的模型文件中保存的张量数值写入到当前网络参数中去,如:
# 保存模型参数到文件上 network.save_weights('weights.ckpt') print('saved weights.') del network # 删除网络对象 # 重新创建相同的网络结构 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.compile(optimizer=optimizers.Adam(lr=0.01), loss=tf.losses.CategoricalCrossentropy(from_logits=True), metrics=['accuracy'] ) # 从参数文件中读取数据并写入当前网络 network.load_weights('weights.ckpt') print('loaded weights!')
这种保存与加载网络的方式最为轻量级,文件中保存的仅仅是张量参数的数值,并没有其它额外的结构参数。但是它需要使用相同的网络结构才能够正确恢复网络状态,因此一般
在拥有网络源文件的情况下使用。
不需要网络源文件,仅仅需要模型参数文件即可恢复出网络模型的方法;
通过 Model.save(path)函数可以将模型的结构以及模型的参数保存到 path文件上;
在不需要网络源文件的条件下,通过 keras.models.load_model(path)即可恢复网络结构和网络参数。 如:
# 保存模型结构与模型参数到文件 network.save('model.h5') print('saved total model.') del network # 删除网络对象 # 从文件恢复网络结构与网络参数 network = keras.models.load_model('model.h5')
model.h5文件除了保存了模型参数外,还应保存了网络结构信息,不需要提前
创建模型即可直接从文件中恢复出网络 network对象。
当需要将模型部署到其他平台时,采用 TensorFlow提出的 SavedModel方式更具有平台无关性。
通过 tf.saved_model.save (network, path)即可将模型以 SavedModel方式保存path目录中,用户无需关心文件的保存格式,只需要通过 tf.saved_model.load函数即可恢复出模型对象,如:
# 保存模型结构与模型参数到文件 tf.saved_model.save(network, 'model-savedmodel') print('saving savedmodel.') del network # 删除网络对象 print('load savedmodel from file.') # 从文件恢复网络结构与网络参数 network = tf.saved_model.load('model-savedmodel') # 准确率计量器 acc_meter = metrics.CategoricalAccuracy() for x,y in ds_val: # 遍历测试集 pred = network(x) # 前向计算 acc_meter.update_state(y_true=y, y_pred=pred) # 更新准确率统计 # 打印准确率 print("Test Accuracy:%f" % acc_meter.result())
对于需要创建自定义逻辑的网络层,可以通过自定义类来实现。
在创建自定义网络层类时,需要继承自 layers.Layer基类;
创建自定义的网络类时,需要继承自 keras.Model基类,这样建立的自定义类才能够方便的利用 Layer/Model基类提供的参数管理等功能,同时也能够与其他的标准网络层类交互使用。
对于自定义的网络层,至少需要实现初始化__init__方法和前向传播逻辑 call方法。
如:
首先创建类,并继承自 Layer基类。创建初始化方法,并调用母类的初始化函数,由
于是全连接层,因此需要设置两个参数:输入特征的长度 inp_dim和输出特征的长度
outp_dim,并通过 self.add_variable(name, shape)创建 shape大小,名字为 name的张量W,并设置为需要优化。class MyDense(layers.Layer): # 自定义网络层 def __init__(self, inp_dim, outp_dim): super(MyDense, self).__init__() # 创建权值张量并添加到类管理列表中,设置为需要优化 self.kernel = self.add_variable('w', [inp_dim, outp_dim], trainable=True)
self.add_variable会返回张量W的 Python引用,而变量名 name由TensorFlow内部维护,使用的比较少。实例化 MyDense类,并查看其参数列表:
net = MyDense(4,3) # 创建输入为 4,输出为 3节点的自定义层 net.variables,net.trainable_variables # 查看自定义层的参数列表 # 类的全部参数列表 ([
通过修改为 self.kernel = self.add_variable('w', [inp_dim, outp_dim], trainable=False),可以设置W张量不需要被优化。
完成自定义类的初始化工作后,我们来设计自定义类的前向运算逻辑,对于这个例子,只需要完成O=X@W矩阵运算,并通过固定的 ReLU激活函数即可,如:
def call(self, inputs, training=None): # 实现自定义类的前向计算逻辑 # X@W out = inputs @ self.kernel # 执行激活函数运算 out = tf.nn.relu(out) return out
自定义类的前向运算逻辑实现在 call(inputs, training=None)函数中;
inputs代表输入,由用户在调用时传入;
training参数用于指定模型的状态:training为 True时执行训练模式,training为 False时执行测试模式,默认参数为 None,即测试模式。
自定义网络类可以和其他标准类一样,通过 Sequential容器方便地封装成一个网络模
型:network = Sequential([MyDense(784, 256), # 使用自定义的层 MyDense(256, 128), MyDense(128, 64), MyDense(64, 32), MyDense(32, 10)]) network.build(input_shape=(None, 28*28)) network.summary()
Sequential容器适合于数据按序从第一层传播到第二层,再从第二层传播到第三层,以
此规律传播的网络模型。对于复杂的网络结构,例如第三层的输入不仅是第二层的输出,
还有第一层的输出,此时使用自定义网络更加灵活。下面来创建自定义网络类,首先创建类,并继承自 Model基类,分别创建对应的网络层对象,并实现自定义网络的前向运算逻辑:
class MyModel(keras.Model): # 自定义网络类,继承自 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): # 自定义前向运算逻辑 x = self.fc1(inputs) x = self.fc2(x) x = self.fc3(x) x = self.fc4(x) x = self.fc5(x) return x
在网络的训练过程中,经常需要统计准确率、召回率等测量指标,除了可以通过手动计算的方式获取这些统计数据外,Keras提供了一些常用的测量工具,位于 keras.metrics模
块中,专门用于统计训练过程中常用的指标数据。
Keras的测量工具的使用方法一般有 4个主要步骤:新建测量器,写入数据,读取统
计数据和清零测量器。
①新建测量器
在 keras.metrics模块中,提供了较多的常用测量器类,如统计平均值的 Mean类,统
计准确率的 Accuracy类,统计余弦相似度的 CosineSimilarity类等。以统计误差值为例。在前向运算时,会得到每一个 Batch的平均误差,但是希望统计每个Step的平均误差,因此选择使用 Mean测量器。新建一个平均测量器,如:
# 新建平均测量器,适合 Loss数据 loss_meter = metrics.Mean()# 新建平均测量器,适合 Loss数据
②写入数据
通过测量器的 update_state函数可以写入新的数据,测量器会根据自身逻辑记录并处理采样数据。例如,在每个 Step结束时采集一次 loss值,如:
# 记录采样的数据,通过 float()函数将张量转换为普通数值 loss_meter.update_state(float(loss))
采样代码放置在每个Batch运算结束后,测量器会自动根据采样的数据来统计平均值。
③读取统计信息
在采样多次数据后,可以选择在需要的地方调用测量器的 result()函数,来获取统计值。例如,间隔性统计 loss均值,如:
# 打印统计期间的平均 loss print(step, 'loss:', loss_meter.result())
④清零测量器
由于测量器会统计所有历史记录的数据,因此在启动新一轮统计时,有必要清除历史状态。通过 reset_states()即可实现清除状态功能。例如,在每次读取完平均误差后,清零统计信息,以便下一轮统计的开始,如:
if step % 100 == 0: # 打印统计的平均 loss print(step, 'loss:', loss_meter.result()) loss_meter.reset_states() # 打印完后,清零测量器
网络训练的过程中,通过 Web端远程监控网络的训练进度,可视化网络的训练结果,对于提高开发效率和实现远程监控是非常重要的。TensorFlow提供了一个专门的可视化工具,叫做 TensorBoard,它通过 TensorFlow将监控数据写入到文件系统,并利用 Web后端监控对应的文件目录,从而可以允许用户从远程查看网络的监控数据。
TensorBoard的使用需要模型代码和浏览器相互配合。
安装:pip install tensorboard
在模型端,需要创建写入监控数据的 Summary类,并在需要的时候写入监控数据。首
先通过 tf.summary.create_file_writer创建监控对象类实例,并指定监控数据的写入目录,如:# 创建监控类,监控数据将写入 log_dir目录 summary_writer = tf.summary.create_file_writer(log_dir)
在前向计算完成后,对于误差这种标量数据,通过 tf.summary.scalar函数记录监控数据,并指定时间戳 step参数。这里的 step参数类似于每个数据对应的时间刻度信息,也可以理解为数据曲线的x坐标,因此不宜重复。每类数据通过字符串名字来区分,同类的数据需要写入相同名字的数据库中。如:
with summary_writer.as_default(): # 写入环境 # 当前时间戳 step上的数据为 loss,写入到名为 train-loss数据库中 tf.summary.scalar('train-loss', float(loss), step=step)
TensorBoard通过字符串 ID来区分不同类别的监控数据,因此对于误差数据,我们将它命
名为”train-loss”,其它类别的数据不可写入,防止造成数据污染。对于图片类型的数据,可以通过 tf.summary.image函数写入监控图片数据。例如,在
训练时,可以通过 tf.summary.image函数可视化样本图片。由于 TensorFlow中的张量一般
包含了多个样本,因此 tf.summary.image函数接受多个图片的张量数据,并通过设置
max_outputs参数来选择最多显示的图片数量,如:with summary_writer.as_default():# 写入环境 # 写入测试准确率 tf.summary.scalar('test-acc', float(total_correct/total), step=step) # 可视化测试用的图片,设置最多可视化 9张图片 tf.summary.image("val-onebyone-images:", val_images, max_outputs=9, step=step)
在运行程序时,监控数据被写入到指定文件目录中。如果要实时远程查看、可视化这
些数据,还需要借助于浏览器和 Web后端。首先是打开 Web后端,通过在 cmd终端运行
tensorboard --logdir path指定 Web后端监控的文件目录 path,即可打开 Web后端监控进
程,如:此时打开浏览器,并输入网址 http://localhost:6006 (也可以通过 IP地址远程访问,具体端口号可能会变动,可查看命令提示) 即可监控网络训练进度。TensorBoard可以同时显示
多条监控记录,在监控页面的左侧可以选择监控记录 ,如:在监控页面的上端可以选择不同类型数据的监控页面,比如标量监控页面SCALARS、图片可视化页面 IMAGES等。
除了监控标量数据和图片数据外,TensorBoard还支持通过 tf.summary.histogram查看张量数据的直方图分布,以及通过 tf.summary.text打印文本信息等功能。如:
with summary_writer.as_default(): # 当前时间戳 step上的数据为 loss,写入到 ID位 train-loss对象中 tf.summary.scalar('train-loss', float(loss), step=step) # 可视化真实标签的直方图分布 tf.summary.histogram('y-hist',y, step=step) # 查看文本信息 tf.summary.text('loss-text',str(float(loss)))