总体介绍

上一讲主要讲解了 TensorFlow 中的一些基本概念与一些低阶 API ,在工业使用时,为了能够快速搭建出模型,人们往往更愿意去选择高阶 API。因此,本次主要讲解 TensorFlow 中常用的高阶 API。

知识点

  • TensorFlow
  • Keras

简介

TensorFlow 中的高阶 API 主要有 Keras 和 Estimators 这两个模块。由于 Keras 入门简单,功能强大,所以本次主要讲解 Keras。

img

Keras

Keras 是一个用于构建和训练深度学习模型的高阶 API。它可用于快速设计原型、高级研究和生产,具有以下三个主要优势:

  • 方便用户使用:Keras 具有针对常见用例做出优化的简单而一致的界面。它可针对用户错误提供切实可行的清晰反馈。
  • 模块化和可组合:将可配置的构造块连接在一起就可以构建 Keras 模型,并且几乎不受限制。
  • 易于扩展:可以编写自定义构造块以表达新的研究创意,并且可以创建新层、损失函数并开发先进的模型。

导入 tf.keras

tf.keras 是 TensorFlow 对 Keras API 规范 的实现。这是一个用于构建和训练模型的高阶 API,包含对 TensorFlow 特定功能(例如 Eager Executiontf.data 管道和 Estimator)的顶级支持。 tf.keras 使 TensorFlow 更易于使用,并且不会牺牲灵活性和性能。

首先,导入 tf.keras 以设置 TensorFlow 程序:

1
2
3
4
5
import tensorflow as tf
from tensorflow.keras import layers

print(tf.VERSION)
print(tf.keras.__version__)

tf.keras 可以运行任何与 Keras 兼容的代码,但请注意:

构建简单的模型

在 Keras 中,可以通过组合层来构建模型。模型通常是由层构成的图。最常见的模型类型是层的堆叠:tf.keras.Sequential 模型。

现在我们使用 tf.keras.Sequential() 来构建一个简单的全连接网络,代码如下:

1
2
3
4
5
6
model = tf.keras.Sequential()  # 定义模型
# 添加 Dense 层,输入为 100,输出为 64 个单元,激活函数选用 Relu 函数
model.add(layers.Dense(64, input_shape=(100,), activation='relu'))
model.add(layers.Dense(64, activation='relu'))
# 添加 Dense 层,输出为 10 个单元,激活函数选用 softmax 函数
model.add(layers.Dense(10, activation='softmax'))

同通过上面仅 4 行代码,我们已经构建出了一个简单的神经网络模型,下面我们可以通过 summary 方法来查看所构建的模型信息。

1
model.summary()

配置层

上面主要讲述使用 tf.keras.Sequential() 来构建模型,现在讲解另一种 Keras 常用的构建模型方法: tf.keras.layers,与 tf.keras.Sequential() 方法差不多,这两者都具有一些相同的构造函数参数:

  • activation:设置层的激活函数。此参数由内置函数的名称指定,或指定为可调用对象。默认情况下,系统不会应用任何激活函数。
  • kernel_initializerbias_initializer:创建层权重的初始化方案。此参数是一个名称或可调用对象,默认为 "Glorot uniform" 初始化器。
  • kernel_regularizerbias_regularizer:应用层权重的正则化方案,例如 L1 或 L2 正则化。默认情况下,系统不会应用正则化函数。

以下代码使用构造函数参数实例化 tf.keras.layers. Dense 层:

1
2
3
4
5
6
7
8
# 创建一个层,输出单元为 64 ,激活函数为 sigmoid 函数
layers.Dense(64, activation='sigmoid')
# 创建一个层,使用 L1 正则化
layers.Dense(64, kernel_regularizer=tf.keras.regularizers.l1(0.01))
# 创建一个层,使用 L2 正则化
layers.Dense(64, bias_regularizer=tf.keras.regularizers.l2(0.01))
# 创建一个层,偏置参数的初始值设置为 2
layers.Dense(64, bias_initializer=tf.keras.initializers.constant(2.0))

训练和评估

上面主要讲述 Keras 两种常用构建神经网络层的方法。两者也可以合起来使用,如下:

1
2
3
4
5
6
model = tf.keras.Sequential([
# 添加一个 Dense 层,输出为 64 ,激活函数为 Rule
layers.Dense(64, activation='relu'),
layers.Dense(64, activation='relu'),
# 添加一个 Dense 层,输出为 10 ,激活函数为 softmax
layers.Dense(10, activation='softmax')])

构建好模型后,通过调用 compile 方法配置该模型的学习流程:

1
2
3
model.compile(optimizer=tf.train.AdamOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

tf.keras.Model.compile 主要含有三个重要参数:

以下代码展示了配置模型以进行训练的几个示例:

1
2
3
model.compile(optimizer=tf.train.AdamOptimizer(0.01),  # 使用 Adam 优化算法
loss='mse', # 损失函数选用均方差损失
metrics=['mse']) # 使用均方差来评估模型

输入 NumPy 数据

当构建好模型之后,需要对其进行训练,而训练时需要加载数据。对于小型数据集,可以直接使用 NumPy 数组训练和评估模型。使用 fit 方法来训练模型:

1
2
3
4
5
6
import numpy as np

data = np.random.random((1000, 32))
labels = np.random.random((1000, 10))

model.fit(data, labels, epochs=10, batch_size=32)

在训练时输出的日志中,sample - loss: 11.6251 表示损失值,categorical_accuracy: 0.1100 表示正确率,在该例子中,使用的数据集是随机产生的,因此准确率并不高。

tf.keras.Model.fit 采用三个重要参数:

  • epochs:以周期为单位进行训练。一个周期是对整个输入数据的一次迭代(以较小的批次完成迭代)。
  • batch_size:当传递 NumPy 数据时,模型将数据分成较小的批次,并在训练期间迭代这些批次。此整数指定每个批次的大小。请注意,如果样本总数不能被批次大小整除,则最后一个批次可能更小。
  • validation_data:在对模型进行原型设计时,你需要轻松监控该模型在某些验证数据上达到的效果。传递此参数(输入和标签元组)可以让该模型在每个周期结束时以推理模式显示所传递数据的损失和指标。即我们通常所说的验证集或测试集。

下面是使用 validation_data 的示例:

1
2
3
4
5
6
7
8
9
10
import numpy as np

data = np.random.random((1000, 32))
labels = np.random.random((1000, 10))

val_data = np.random.random((100, 32))
val_labels = np.random.random((100, 10))

model.fit(data, labels, epochs=10, batch_size=32,
validation_data=(val_data, val_labels))

输入 tf.data 数据集

传入数据集还有另一种方法,就是使用 TensorFlow 提供的接口 tf.data。 其提供的 Datasets API 可扩展为大型数据集或多设备训练。将 tf.data.Dataset 实例传递到 fit 方法:

1
2
3
4
5
6
7
#  加载数据集
dataset = tf.data.Dataset.from_tensor_slices((data, labels))
dataset = dataset.batch(32)
dataset = dataset.repeat()

# 训练模型
model.fit(dataset, epochs=10, steps_per_epoch=30)

在上方代码中,fit 方法使用了 steps_per_epoch 参数表示模型在进入下一个周期之前运行的训练步数。由于 Dataset 会生成批次数据,因此该代码段不需要设置 batch_size。

同样该方法传入的数据集也可用于验证:

1
2
3
4
5
6
7
8
9
dataset = tf.data.Dataset.from_tensor_slices((data, labels))
dataset = dataset.batch(32).repeat()

val_dataset = tf.data.Dataset.from_tensor_slices((val_data, val_labels))
val_dataset = val_dataset.batch(32).repeat()

model.fit(dataset, epochs=10, steps_per_epoch=30,
validation_data=val_dataset,
validation_steps=3)

评估和预测

当模型训练完,我们需要对所训练完的模型进行评估时,可以使用 tf.keras.Model.evaluatetf.keras.Model.predict 方法,并且这两种方法同样可以使用 NumPy 数据和 tf.data.Dataset 提供的数据。

要评估所提供数据的推理模式损失和指标,可以运行以下代码:

1
2
3
4
data = np.random.random((1000, 32))
labels = np.random.random((1000, 10))

model.evaluate(data, labels, batch_size=32)

同样的方法,采用 tf.data.Dataset 提供的数据。

1
model.evaluate(dataset, steps=30)

要在所提供数据(采用 NumPy 数组形式)的推理中预测最后一层的输出,可以运行以下代码:

1
2
result = model.predict(data, batch_size=32)
result.shape

构建高级模型

上面所讲述的 tf.keras.Sequential 模型是层的简单堆叠,这种方法构建模型简单,但往往无法构建出复杂模型。在 Keras 中,通常使用 Keras 函数式 API 来构建复杂的模型,例如:

  • 多输入模型,
  • 多输出模型,
  • 具有共享层的模型(同一层被调用多次),
  • 具有非序列数据流的模型(例如,剩余连接)。

使用函数式 API 构建的模型具有以下特征:

  1. 层实例可调用并返回张量。
  2. 输入张量和输出张量用于定义 tf.keras.Model 实例。
  3. 此模型的训练方式和 Sequential 模型一样。

以下示例使用函数式 API 构建一个简单的全连接网络:

1
2
3
4
5
6
7
inputs = tf.keras.Input(shape=(32,))  # 创建一个输入层

# 创建 Dense 层
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dense(64, activation='relu')(x)
predictions = layers.Dense(10, activation='softmax')(x)
predictions

在给定输入和输出的情况下实例化模型。

1
2
3
4
5
6
7
8
9
10
# 实例化模型
model = tf.keras.Model(inputs=inputs, outputs=predictions)

# 编译模型,指的优化算法,损失函数等相关的参数
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

# 训练模型
model.fit(data, labels, batch_size=32, epochs=5)

模型子类化

我们也可以通过对 tf.keras.Model 进行子类化并定义前向传播来构建完全可自定义的模型。这种方法需要在 __init__ 方法中创建层并将它们设置为类实例的属性。此外,需要在 call 方法中定义前向传播。

在启用 Eager Execution 时,模型子类化特别有用,因为可以命令式地编写前向传播。

虽然模型子类化较为灵活,但代价是复杂性更高且用户出错率更高。如果可能,请首选函数式 API。

以下示例展示了使用自定义前向传播进行子类化的 tf.keras.Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyModel(tf.keras.Model):

def __init__(self, num_classes=10):
super(MyModel, self).__init__(name='my_model')
self.num_classes = num_classes
# 定义所想要构建模型的层
self.dense_1 = layers.Dense(32, activation='relu')
self.dense_2 = layers.Dense(num_classes, activation='sigmoid')

def call(self, inputs):
# 定义模型的前向传播
x = self.dense_1(inputs)
return self.dense_2(x)

def compute_output_shape(self, input_shape):
# 计算模型的输出
shape = tf.TensorShape(input_shape).as_list()
shape[-1] = self.num_classes
return tf.TensorShape(shape)

实例化新模型类:

1
2
3
4
5
6
7
8
9
model = MyModel(num_classes=10)  # 模型实例化

# 编译模型,指的优化算法,损失函数等相关的参数
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

# 训练模型
model.fit(data, labels, batch_size=32, epochs=5)

自定义层

在我们构建模型时,有时候 Keras 提供的接口并不能满足我们的要求。因此可以通过对 tf.keras.layers.Layer 进行子类化并实现以下方法来创建自定义层:

  • build:创建层的权重。使用 add_weight 方法添加权重。
  • call:定义前向传播。
  • compute_output_shape:指定在给定输入形状的情况下如何计算层的输出形状。
  • 或者,可以通过实现 get_config 方法和 from_config 类方法序列化层。

下面是一个使用核矩阵实现输入 matmul 的自定义层示例:

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
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):
shape = tf.TensorShape((input_shape[1], self.output_dim))
# 创建一个层所使用的权重值.
self.kernel = self.add_weight(name='kernel',
shape=shape,
initializer='uniform',
trainable=True)
# 需要对其进行声明
super(MyLayer, self).build(input_shape)

def call(self, inputs):
return tf.matmul(inputs, self.kernel)

def compute_output_shape(self, input_shape):
shape = tf.TensorShape(input_shape).as_list()
shape[-1] = self.output_dim
return tf.TensorShape(shape)

def get_config(self):
base_config = super(MyLayer, self).get_config()
base_config['output_dim'] = self.output_dim
return base_config

@classmethod
def from_config(cls, config):
return cls(**config)

使用自定义层创建模型:

1
2
3
4
5
6
7
8
9
10
11
model = tf.keras.Sequential([
MyLayer(10),
layers.Activation('softmax')])

# 编译模型,指定优化算法等参数
model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

# 训练模型
model.fit(data, labels, batch_size=32, epochs=5)

回调

回调是传递给模型的对象,用于在训练期间记录模型日志或防止模型出现意外等。你可以编写自定义回调,也可以使用包含以下方法的内置 tf.keras.callbacks

要使用 tf.keras.callbacks.Callback,请将其传递给模型的 fit 方法:

1
2
3
4
5
6
7
8
callbacks = [
# 如果在 2 epochs 内损失值没有变化,则中断训练
tf.keras.callbacks.EarlyStopping(patience=2, monitor='val_loss'),
# 记录日志
tf.keras.callbacks.TensorBoard(log_dir='./logs')
]
model.fit(data, labels, batch_size=32, epochs=5, callbacks=callbacks,
validation_data=(val_data, val_labels))

保存和恢复

在 Keras 中,可以使用 tf.keras.Model.save_weights 保存并加载模型的权重:

1
2
3
4
5
6
7
8
9
10
11
12
13
model = tf.keras.Sequential([
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')])

model.compile(optimizer=tf.train.AdamOptimizer(0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

# 保存模型
model.save_weights('./weights/my_model')

# 加载模型
model.load_weights('./weights/my_model')

默认情况下,会以 TensorFlow 检查点 文件格式保存模型的权重。权重也可以另存为 Keras HDF5 格式(Keras 多后端实现的默认格式):

1
2
3
4
5
# S 保存为 HDF5 文件
model.save_weights('my_model.h5', save_format='h5')

# 加载模型
model.load_weights('my_model.h5')

保存配置

可以保存模型的配置,此操作会对模型架构(不含任何权重)进行序列化。即使没有定义原始模型的代码,保存的配置也可以重新创建并初始化相同的模型。Keras 支持 JSON 和 YAML 序列化格式:

1
2
3
# 序列化模型为 json 形式
json_string = model.to_json()
json_string

输出为:

1
'{"class_name": "Sequential", "config": {"name": "sequential_6", "layers": [{"class_name": "Dense", "config": {"name": "dense_24", "trainable": true, "dtype": null, "units": 64, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null, "dtype": "float32"}}, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_25", "trainable": true, "dtype": null, "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null, "dtype": "float32"}}, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}, "keras_version": "2.1.6-tf", "backend": "tensorflow"}'
1
2
3
import json
import pprint
pprint.pprint(json.loads(json_string))

输出为:

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
{'backend': 'tensorflow',
'class_name': 'Sequential',
'config': {'layers': [{'class_name': 'Dense',
'config': {'activation': 'relu',
'activity_regularizer': None,
'bias_constraint': None,
'bias_initializer': {'class_name': 'Zeros',
'config': {'dtype': 'float32'}},
'bias_regularizer': None,
'dtype': None,
'kernel_constraint': None,
'kernel_initializer': {'class_name': 'GlorotUniform',
'config': {'dtype': 'float32',
'seed': None}},
'kernel_regularizer': None,
'name': 'dense_24',
'trainable': True,
'units': 64,
'use_bias': True}},
{'class_name': 'Dense',
'config': {'activation': 'softmax',
'activity_regularizer': None,
'bias_constraint': None,
'bias_initializer': {'class_name': 'Zeros',
'config': {'dtype': 'float32'}},
'bias_regularizer': None,
'dtype': None,
'kernel_constraint': None,
'kernel_initializer': {'class_name': 'GlorotUniform',
'config': {'dtype': 'float32',
'seed': None}},
'kernel_regularizer': None,
'name': 'dense_25',
'trainable': True,
'units': 10,
'use_bias': True}}],
'name': 'sequential_6'},
'keras_version': '2.1.6-tf'}

从 json 重新创建模型(刚刚初始化)。

1
2
# 加载模型
fresh_model = tf.keras.models.model_from_json(json_string)

将模型序列化为 YAML 格式。

1
2
yaml_string = model.to_yaml()
print(yaml_string)

输出为:

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
backend: tensorflow
class_name: Sequential
config:
layers:
- class_name: Dense
config:
activation: relu
activity_regularizer: null
bias_constraint: null
bias_initializer:
class_name: Zeros
config: {dtype: float32}
bias_regularizer: null
dtype: null
kernel_constraint: null
kernel_initializer:
class_name: GlorotUniform
config: {dtype: float32, seed: null}
kernel_regularizer: null
name: dense_24
trainable: true
units: 64
use_bias: true
- class_name: Dense
config:
activation: softmax
activity_regularizer: null
bias_constraint: null
bias_initializer:
class_name: Zeros
config: {dtype: float32}
bias_regularizer: null
dtype: null
kernel_constraint: null
kernel_initializer:
class_name: GlorotUniform
config: {dtype: float32, seed: null}
kernel_regularizer: null
name: dense_25
trainable: true
units: 10
use_bias: true
name: sequential_6
keras_version: 2.1.6-tf

从 YAML 重新创建模型

1
2
fresh_model = tf.keras.models.model_from_yaml(yaml_string)
fresh_model

整个模型

整个模型可以保存到一个文件中,其中包含权重值、模型配置乃至优化器配置。这样就可以对模型设置检查点并稍后从完全相同的状态继续训练,而无需访问原始代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建一个模型
model = tf.keras.Sequential([
layers.Dense(10, activation='softmax',
input_shape=(32,)),
layers.Dense(10, activation='softmax')
])
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(data, labels, batch_size=32, epochs=5)

# 保存整个模型为 HDF5 文件
model.save('my_model.h5')

# 加载整个模型
model = tf.keras.models.load_model('my_model.h5')
model

总结

本次主要讲解了 TensorFlow 常用的高阶 API,并对其集成的 Keras 进行详细的讲解。相对于低阶 API ,高阶 API 往往会更加简单,因为其只要定义层即可。这也是 TensorFlow 官方极力推荐广大开发者使用高阶 API 的原因。