第3章 TensorFlow构建模型的方法

第2章我们用TensorFlow的自动微分及优化器等方法,实现了一个比较简单的回归问题。在本章,我们将继续对如何高效构建模型、训练模型做进一步的说明。根据构建模型的方式,本章将分为如下3种方法:
♦ 低阶API建模
♦ 中阶API建模
♦ 高阶API建模

3.1 利用低阶API构建模型

用TensorFlow低阶API构建模型主要包括张量操作、计算图及自动微分等操作,这种方法灵活性高,如果构建模型继承tf.Module,还可以轻松实现保存模型及跨平台部署。为提高模型运行效率,我们还可以使用@tf.function装饰相关函数,使之转换为自动图。为了更好地掌握本节的相关内容,这里以分类项目为例。

3.1.1 项目背景

这里以CIFAR-10为数据集,数据导入和预处理使用自定义函数,为更有效地处理数据,这里使用tf.data工具。有关tf.data的详细使用将在第4章将介绍,这里不再详述。构建模型只使用TensorFlow的低阶API,如tf.Variable、tf.nn.relu、自动微分等。然后自定义训练过程,最后保存和恢复模型。
CIFAR-10为小型数据集,一共包含10个类别的 RGB 彩色图像:飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog)、蛙类(frog)、马(horse)、船(ship)和卡车(truck)。图像的尺寸为 32×32(像素),3个通道 ,数据集中一共有 50000 张训练圄片和 10000 张测试图像。CIFAR-10数据集有3个版本,这里使用Python版本。

3.1.2 导入数据

1)导入需要的模块。

2)定义导入单批次的函数。因数据源分成几个批次,这里定义一个导入各批次的函数。

3)导入整个数据集。

4)指定数据文件所在路径。

5)说明类别及对应索引,并随机可视化其中5张图像。

随机抽取CIFAR-10数据集中5张图的结果如图3-1所示。

图3-1 随机抽取CIFAR-10数据集中5张图

3.1.3 预处理数据

1)对数据进行简单处理。对数据进行规范化,并设计相关超参数等。

2)使用TensorFlow的数据预处理工具tf.data,使预处理过程打包成为一个管道。

dataset 中 shuffle()、repeat()、batch()、prefetch()等函数的主要功能如下。
1)repeat(count=None) 表示重复此数据集 count 次,实际上,我们看到 repeat 往往是接在 shuffle 后面的。为何要这么做,而不是反过来,先 repeat 再 shuffle 呢? 如果shuffle 在 repeat 之后,epoch 与 epoch 之间的边界就会模糊,出现未遍历完数据,已经计算过的数据又出现的情况。
2)shuffle(buffer_size, seed=None, reshuffle_each_iteration=None) 表示将数据打乱,数值越大,混乱程度越大。为了完全打乱,buffer_size 应等于数据集的数量。
3)batch(batch_size, drop_remainder=False) 表示按照顺序取出 batch_size 大小数据,最后一次输出可能小于batch ,如果程序指定了每次必须输入进批次的大小,那么应将drop_remainder 设置为 True 以防止产生较小的批次,默认为 False。
4)prefetch(buffer_size) 表示使用一个后台线程以及一个buffer来缓存batch,提前为模型的执行程序准备好数据。一般来说,buffer的大小应该至少和每一步训练消耗的batch数量一致,也就是 GPU/TPU 的数量。我们也可以使用AUTOTUNE来设置。创建一个Dataset便可从该数据集中预提取元素,注意:examples.prefetch(2) 表示将预取2个元素(2个示例),而examples.batch(20).prefetch(2) 表示将预取2个元素(2个批次,每个批次有20个示例),buffer_size 表示预提取时将缓冲的最大元素数返回 Dataset。
使用prefetch可以把数据处理与模型训练的交互方式由图3-2变为图3-3。
图3-2 未使用prefetch的数据处理流程
图3-3 使用prefetch后的数据处理流程

3.1.4 构建模型

使用tf.Module 封装变量及其计算,可以使用任何Python对象,有利于保存模型和跨平台部署使用。因此,可以基于TensorFlow开发任意机器学习模型(而非仅仅是神经网络模型),并实现跨平台部署使用。
1)构建模型。构建一个继承自tf.Module的模型,修改基类的构造函数,把需要初始化的变量放在__init__构造函数中,把参数变量的正向传播过程放在__call__方法中。__call__方法在模型实例化时,将自动调用。为提供更快的运行效率,通常用@tf.function进行装饰。

2)定义损失函数和评估函数。

3.1.5 训练模型

1)实现反向传播。这里使用自动微分机制,并使用优化器实现梯度的自动更新,具体过程如下:
• 打开一个 GradientTape() 作用域;
• 在此作用域内,调用模型(正向传播)并计算损失;
• 在作用域之外,检索模型权重相对于损失的梯度;
• 根据梯度使用优化器来更新模型的权重;
• 利用优化器进行反向传播(更新梯度)。

2)定义训练过程。

这是最后6次的运行结果:
step: 35000, loss: 0.910988, accuracy: 0.734375
step: 36000, loss: 0.790101, accuracy: 0.765625
step: 37000, loss: 0.753428, accuracy: 0.750000
step: 38000, loss: 0.658011, accuracy: 0.781250
step: 39000, loss: 0.817612, accuracy: 0.718750
step: 40000, loss: 0.723336, accuracy: 0.718750
3)可视化运行过程。

随着迭代次数增加,模型准确率的变化如图3-4所示。

图3-4 随着迭代次数增加,模型准确率的变化

3.1.6 测试模型

对模型进行测试。

运行结果如下:
0.535256
构建模型只使用了全连接层,没有对网络进行优化,测试能达到这个效果也不错。后续我们将采用数据增强、卷积神经网络等方法进行优化。

3.1.7 保存恢复模型

1)保存模型。

2)恢复模型。

3)利用恢复的模型进行测试。

运行结果如下:
0.535256
由结果可知,它与原模型的测试结果完全一样!

3.2 利用中阶API构建模型

用TensorFlow的中阶API构建模型,主要使用TensorFlow或tf.keras提供的各种模型层、损失函数、优化器、数据管道、特征列等,无须自己定义网络层、损失函数等。如果定制性要求不高,这将大大提高构建模型的效率。利用中阶API构建模型时需要继承tf.Module,它是各种模型层的基类。为更好地掌握相关内容,还是使用3.1节的数据集,架构相同,只是把定义层改为直接使用tf.keras提供的层、优化器、评估函数等。
利用中阶API构建模型的导入数据、预处理数据等部分,与3.1节的相应部分一样,这里不再赘述。下面主要介绍两种方法的不同之处。

3.2.1构建模型

用TensorFlow的中阶API构建模型,需要使用TensorFlow或tf.keras提供的各种模型层、损失函数、优化器、数据管道、特征列等,无须自己定义网络层、损失函数等。因数据为图像,这里使用全连接层,故第一层使用Flatten层,把数据展平。

3.2.2创建损失评估函数

损失函数使用tf.keras.metrics.Mean类,评估函数使用tf.keras.metrics.SparseCategoricalAccuracy类,使用该类标签无须转换为独热编码。

3.2.3训练模型

1)定义训练函数。

2)训练模型。

最后6次的运行结果:
train: step 45001, Loss: 1.279, Accuracy: 54.51%
train: step 46001, Loss: 1.276, Accuracy: 54.63%
train: step 47001, Loss: 1.272, Accuracy: 54.76%
train: step 48001, Loss: 1.269, Accuracy: 54.88%
train: step 49001, Loss: 1.266, Accuracy: 55.00%
train: step 50001, Loss: 1.263, Accuracy: 55.12%
3)可视化训练结果。

使用中阶API构建模型的可视化结果如图3-5所示。

图3-5 使用中阶API构建模型的可视化结果
4)测试模型。

运行结果如下:
Test Loss: 1.385, Test Accuracy: 52.14%

利用中阶API构建模型、实现训练,比直接使用低阶API简化了不少,但编码量还是比较大,尤其是定义评估函数、训练过程等,接下来我们介绍一种更简单、高效的方法,即下节将介绍的利用高阶API构建模型。

3.3利用高阶API构建模型

TensorFlow的高阶API主要是指tf.keras.models提供的模型的类接口。目前tf.keras为官方推荐的高阶API。 使用ff.keras接口构建模型的方式有3种。
• 序列API(Sequential API)模式,把多个网络层的线性堆叠构建模型。
• 函数式API(Functional API)模式,可构建任意结构模型。
• 子类模型API(Model Subclassing API)模式,使用继承Model基类的子类模型可构建自定义模型。
这里先使用序列API按层顺序构建模型,其他模式构建模型将在第7章详细介绍。
导入与数据预处理过程与3.1节一样,这里不再赘述,具体请参考本书代码及数据部分。

3.3.1 构建模型

这里主要使用tf.keras.models及tf.keras.layers高阶类,构建模型采用序列API方法,这种方法就像搭积木一样,非常直观和简单。首先实例化Sequential类,然后需要使用add方法把各层按序叠加在一起,并基于优化器、损失函数等方法编译模型,最后输入数据训练模型。这个过程可用图3-6来直观描述。

图3-6 序列API构建模型流程图
导入需要的库,构建模型。

显示模型各层及其结构,如图3-7所示。:
图3-7 显示模型各层及其结构
构建Sequential模型时,第一层需要知道输入数据的形状,其他各层只要指明输出形状即可,输入形状可自动推导。因此,Sequential的第一层需要接收一个关于输入数据形状(shape)的参数。那么,如何指定第一层的输入形状呢?有以下几种方法来为第一层指定输入数据的形状。
• 传递一个input_shape参数给第一层。它是一个表示形状的元组 (一个由整数或None组成的元组,其中 None表示可能为任何正整数)。在input_shape中不包含数据批次(batch)大小。
• 有些2维层,如全连接层(Dense),支持通过指定其输入维度input_dim来隐式指定输入数据的形状,input_dim是一个整数类型的数据。一些3维的时域层支持通过参数input_dim和input_length来指定输入shape。
• 对某些网络层,如果需要为输入指定一个固定大小的批量值(batch_size),可以传递batch_size参数到一个层中。例如你想指定输入张量的batch大小是32,数据shape是(6,8),则需要传递batch_size=32和input_shape=(6,8)给一个层,那么每一批输入的形状就为 (32,6,8)。

3.3.2 编译及训练模型

训练模型之前,需要对模型进行编译配置,这是通过compile方法完成的。它接收三个参数。
• 优化器(optimizer):可以是内置优化器的字符串标识符,如 rmsprop 或 adagrad,也可以是Optimizer类的对象。
• 损失函数(loss):模型最小化的目标函数。它可以是内置损失函数的字符串标识符,如 categorical_crossentropy 或 mse等,也可以是自定义的损失函数。
• 评估标准(metrics):可以是内置的标准字符串标识符,也可以是自定义的评估标准函数。对于分类问题,一般将评估标准设置为 metrics = ['accuracy']。
1)编译模型。

2)训练模型。这里采用回调机制定时保存模型。

最后两次的迭代结果:
Epoch 19/20
704/704 - 2s - loss: 1.6917 - accuracy: 0.3892 - val_loss: 1.6291 - val_accuracy: 0.4120
Epoch 00019: val_loss did not improve from 1.62898
Epoch 20/20
704/704 - 2s - loss: 1.6903 - accuracy: 0.3869 - val_loss: 1.6163 - val_accuracy: 0.4154
Epoch 00020: val_loss improved from 1.62898 to 1.61627, saving model to MLP.best_weights.hdf5

最后,根据模型训练的结果自动保存最好的那个模型。
3)可视化运行结果。

使用高阶API构建模型的可视化结果如图3-8所示。

图3-8 使用高阶API的运行结果

3.3.3测试模型

输入如下代码测试模型。

运行结果如下:
Test accuracy:0.4329

3.3.4 保存恢复模型

1)选择最好的模型参数恢复模型。

2)查看网络结构。

运行结果与图3-7的网络结构完全一致。
3)检查其准确率(accuracy)。

运行结果如下:
313/313 - 1s - loss: 1.6150 - accuracy: 0.4329
Restored model, accuracy: 43.29%
由结果可知,其结果与预测结果完全一致!

3.4 小结

本章介绍了用几种常用的API构建网络和训练模型的方法。这些API从封装程度来划分,可分为低阶API、中阶API和高阶API。低阶API基本用TensorFlow实现,自定义层、损失函数等;中阶API使用层模块(如tf.keras.layers.Dense)、内置的损失函数、优化器等构建模型;高阶API的封装程度最高,使用tf.keras构建模型、训练模型。这3种方法各有优缺点,低阶API代码量稍多一些,但定制能力较高,而用高阶API构建模型和训练模型的代码比较简洁,但定制能力稍弱。实际上,我们的大部分任务基本都可以用高阶API来实现,尤其对初学者,使用高阶API构建模型、训练模型是首选。