Keras 是一个主要由Python 语言开发的开源神经网络计算库,最初由François Chollet编写,它被设计为高度模块化和易扩展的高层神经网络接口,使得用户可以不需要过多的专业知识就可以简洁、快速地完成模型的搭建与训练。Keras 库分为前端和后端,其中后端可以基于现有的深度学习框架实现,如Theano,CNTK,TensorFlow,前端接口即Keras抽象过的统一接口API。用户通过Keras 编写的代码可以轻松的切换不同的后端运行,灵活性较大。正是由于Keras 的高度抽象和易用特性,截止到2019 年,Keras 市场份额达到了26.6%,增长19.7%,同类深度学习框架中仅次于TensorFlow(数据来自KDnuggets)。TensorFlow 与Keras 存在既竞争,又合作的关系,甚至连Keras 创始人都在Google 工作。早在2015 年11 月,TensorFlow 被加入Keras 后端支持。从2017 年开始,Keras 的大部分组件被整合到TensorFlow 框架中。
在TensorFlow 2 版本中,Keras 被正式确定为TensorFlow 的高层API 唯一接口,取代了TensorFlow 1 版本中自带的tf.layers 等高层接口。也就是说,现在只能使用Keras 的接口来完成TensorFlow 层方式的模型搭建与训练。在TensorFlow 中,Keras 被实现在tf.keras 子模块中。那么 Keras 与tf.keras 有什么区别与联系呢?其实Keras 可以理解为一套搭建与训练神经网络的高层API 协议,Keras 本身已经实现了此协议,可以方便的调用TensorFlow,CNTK 等后端完成加速计算;在TensorFlow 中,也实现了一套Keras 协议,即tf.keras,但只能基于TensorFlow 后端计算,并对TensorFlow 的支持更好。对于使用TensorFlow 的开发者来说,tf.keras 可以理解为一个普通的子模块,与其他子模块,如tf.math,tf.data 等并没有什么差别。
1、 tf.keras.layers 命名空间(下文使用layers 指代tf.keras.layers)中提供了大量常见网络层的类接口,如全连接层,激活含水层,池化层,卷积层,循环神经网络层等等。对于这些网络层类,只需要在创建时指定网络层的相关参数,并调用__call__方法即可完成前向计算。在调用__call__方法时,Keras 会自动调用每个层的前向传播逻辑,这些逻辑一般实现在类的call 函数中。
例1、实现softmax层
import tensorflow as tf
# 导入keras 模型,不能使用import keras,它导入的是标准的Keras 库
from tensorflow import keras
from tensorflow.keras import layers # 导入常见网络层类
x = tf.constant([2.,1.,0.1])
#然后创建Softmax 层,并自动调用__call__方法完成前向计算:
layer = layers.Softmax(axis=-1) # 创建Softmax 层
layer(x) # 调用softmax 前向计算
2、Keras 提供的网络容器Sequential 将多个网络层封装成一个大网络模型,只需要调用网络模型的实例一次即可完成数据从第一层到最末层的顺序运算。
# 导入Sequential 容器
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])
network(x) # 输入从第一层开始,逐层传播至最末层
Sequential 容器也可以通过add()方法继续追加新的网络层,实现动态创建网络的功能。通过指定任意的layers_num 参数即可创建对应层数的网络结构,在完成网络创建时,很多类并没有创建内部权值张量等成员变量,此时通过调用类的build 方法并指定输入大小,即可自动创建所有层的内部张量。通过summary()函数可以方便打印出网络结构和参数量:
layers_num = 2 # 堆叠2 次
network = Sequential([]) # 先创建空的网络
for _ in range(layers_num):
network.add(layers.Dense(3)) # 添加全连接层
network.add(layers.ReLU())# 添加激活函数层
network.build(input_shape=(None, 4)) # 创建网络参数
network.summary()
Sequential 对象的trainable_variables 和variables 包含了所有层的待优化张量列表和全部张量列表
for p in network.trainable_variables:
print(p.name, p.shape)
3、在 Keras 中,有2 个比较特殊的类:keras.Model 和keras.layers.Layer 类。其中Layer类是网络层的父类,定义了网络层的一些常见功能,如添加权值,管理权值列表等。Model 类是网络的父类,除了具有Layer 类的功能,还添加了保存、加载模型,训练与测试模型等便捷功能。Sequential 也是Model 的子类,因此具有Model 类的所有功能。
# 创建5 层的全连接层网络
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=(None, 28*28))
network.summary()
# 导入优化器,损失函数模块
from tensorflow.keras import optimizers,losses
# 采用Adam 优化器,学习率为0.01;采用交叉熵损失函数,包含Softmax
network.compile(optimizer=optimizers.Adam(lr=0.01),
loss=losses.CategoricalCrossentropy(from_logits=True),
metrics=['accuracy'] # 设置测量指标为准确率)
# 指定训练集为train_db,验证集为val_db,训练5 个epochs,每2 个epoch 验证一次
# 返回训练信息保存在history中
history = network.fit(train_db, epochs=5, validation_data=val_db,
validation_freq=2)
其中train_db 为tf.data.Dataset 对象,也可以传入Numpy Array 类型的数据;epochs 指定训练迭代的epochs 数;validation_data 指定用于验证(测试)的数据集和验证的频率validation_freq。
运行上述代码即可实现网络的训练与验证的功能,fit 函数会返回训练过程的数据记录
history,其中history.history 为字典对象,包含了训练过程中的loss,测量指标等记录项。
# predict()进行预测
x,y = next(iter(db_test))
print('predict x:', x.shape)
out = network.predict(x) # 模型预测
print(out)
4、模型保存与加载。在 Keras 中,有三种常用的模型保存与加载方法。
4.1 张量方式。通过调用Model.save_weights(path)方法即可讲当前的网络参数保存到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!')
4.2 网络方式。通过Model.save(path)函数可以将模型的结构以及模型的参数保存到一个path 文件上,在不需要网络源文件的条件下,通过keras.models.load_model(path)即可恢复网络结构和网络参数。
# 保存模型结构与模型参数到文件
network.save('model.h5')
print('saved total model.')
del network # 删除网络对象
#此时通过model.h5 文件即可恢复出网络的结构和状态:
# 从文件恢复网络结构与网络参数
network = tf.keras.models.load_model('model.h5')
4.3 SavedModel 方式。TensorFlow 之所以能够被业界青睐,除了优秀的神经网络层API 支持之外,还得益于它强大的生态系统,包括移动端和网页端的支持。当需要将模型部署到其他平台时,采用TensorFlow 提出的SavedModel 方式更具有平台无关性。
# 保存模型结构与模型参数到文件
tf.keras.experimental.export_saved_model(network, 'model-savedmodel')
print('export saved model.')
del network # 删除网络对象
# 从文件恢复网络结构与网络参数
network = tf.keras.experimental.load_from_saved_model('model-savedmodel')
此时在文件系统model-savedmodel 目录上出现了如下网络文件:
在创建自定义网络层类时,需要继承自layers.Layer 基类;创建自定义的网络类,需要继承自keras.Model 基类,这样产生的自定义类才能够方便的利用Layer/Model 基类提供的参数管理功能,同时也能够与其他的标准网络层类交互使用。
对于自定义的网络层,需要实现初始化__init__方法和前向传播逻辑call 方法。
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)
def call(self, inputs, training=None):
# 实现自定义类的前向计算逻辑
# X@W
out = inputs @ self.kernel
# 执行激活函数运算
out = tf.nn.relu(out)
return out
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
在网络训练的过程中,通过Web 端监控网络的训练进度,可视化网络的训练结果对于提高开发效率是非常重要的。TensorFlow 提供了一个专门的可视化工具,叫做TensorBoard,他通过TensorFlow 将监控数据写入到文件系统,并利用Web 后端监控对应
的文件目录,从而可以允许用户从远程查看网络的监控数据。
# 创建监控类,监控数据将写入log_dir 目录
summary_writer = tf.summary.create_file_writer(log_dir)
with summary_writer.as_default():
# 当前时间戳step 上的数据为loss,写入到ID为train-loss 对象中
tf.summary.scalar('train-loss', float(loss), step=step)
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)
需要注意的是,TensorBoard 通过字符串ID 来区分不同类别的监控数据,因此对于误差数据,我们将它命名为”train-loss”,其他类的数据不可写入此对象,防止数据污染。
tensorboard --logdir path 指定Web 后端监控的文件目录path。此时打开浏览器,并输入网址http://localhost:6006 (也可以通过IP 地址远程访问,具体端口号可能会变动,可查看命令提示) 即可监控网络训练进度。