第2章我们用TensorFlow的自动微分及优化器等方法,实现了一个比较简单的回归问题。在本章,我们将继续对如何高效构建模型、训练模型做进一步说明。根据构建模型的方式,本章将分为如下3种方法:
❑低阶API建模
❑中阶API建模
❑高阶API建模
用TensorFlow低阶API构建模型主要包括张量、计算图及自动微分等操作,这种方法灵活性高,如果构建模型继承tf.Module,还可以轻松实现保存模型及跨平台部署。为提高模型运行效率,我们还可以使用@tf.function装饰相关函数,将其转换为自动图。为了更好地掌握本节的相关内容,这里以分类项目为例进行举例说明。
这里以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版本。
1)导入需要的模块。
2)定义导入单批次的函数。因数据源分成几个批次,这里定义一个导入各批次的函数。
3)导入整个数据集。
4)指定数据文件所在路径。
5)说明类别及对应索引,并随机可视化其中5张图像。
随机抽取CIFAR-10数据集中5张图的结果如图3-1所示。
图3-1 随机抽取CIFAR-10数据集中5张图的结果
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后的数据处理流程
使用tf.Module封装变量及其计算,可以使用任何Python对象,有利于保存模型和跨平台部署使用。因此,可以基于TensorFlow开发任意机器学习模型(而非仅仅神经网络模型),并实现跨平台部署使用。
1)构建模型。构建一个继承自tf.Module的模型,修改基类的构造函数,把需要初始化的变量放在__init__构造函数中,把参数变量的正向传播过程放在__call__方法中。__call__方法在模型实例化时将被自动调用。为提供更好的运行效率,通常用@tf.function进行装饰。
2)定义损失函数和评估函数。
1)实现反向传播。这里使用自动微分机制,并使用优化器实现梯度的自动更新,具体过程如下:
❑打开一个GradientTape()作用域;
❑在此作用域内,调用模型(正向传播)并计算损失;
❑在作用域之外,检索模型权重相对于损失的梯度;
❑根据梯度使用优化器来更新模型的权重;
❑利用优化器进行反向传播(更新梯度)。
2)定义训练过程。
这是最后6次的运行结果:
3)可视化运行过程。
随着迭代次数增加,模型准确率的变化如图3-4所示。
图3-4 随着迭代次数增加,模型准确率的变化
对模型进行测试。
运行结果如下:
构建模型只使用了全连接层,没有对网络进行优化,但测试能达到这个效果也不错。后续我们将采用数据增强、卷积神经网络等方法进行优化。
1)保存模型。
2)恢复模型。
3)利用恢复的模型进行测试。
运行结果如下:
由结果可知,它与原模型的测试结果完全一样!
用TensorFlow的中阶API构建模型,主要使用TensorFlow或tf.keras提供的各种模型层、损失函数、优化器、数据管道、特征列等,无须自己定义网络层、损失函数等。如果定制性要求不高,这种方法将大大提高构建模型的效率。利用中阶API构建模型时需要继承tf.Module,它是各种模型层的基类。为更好地掌握相关内容,还是使用3.1节的数据集,架构相同,只是把定义层改为直接使用tf.keras提供的层、优化器、评估函数等。
利用中阶API构建模型的导入数据、预处理数据等部分,与3.1节的相应部分一样,这里不再赘述。下面主要介绍两种方法的不同之处。
用TensorFlow的中阶API构建模型,需要使用TensorFlow或tf.keras提供的各种模型层、损失函数、优化器、数据管道、特征列等,无须自己定义网络层、损失函数等。因数据为图像,这里使用全连接层,故第一层使用Flatten层,把数据展平。
损失函数使用tf.keras.metrics.Mean类,评估函数使用tf.keras.metrics.SparseCategoricalAccuracy类,使用该类标签无须转换为独热编码。
1)定义训练函数。
2)训练模型。
最后6次的运行结果:
3)可视化训练结果。
使用中阶API构建模型的可视化结果如图3-5所示。
图3-5 使用中阶API构建模型的可视化结果
4)测试模型。
运行结果如下:
利用中阶API构建模型、实现训练,比直接使用低阶API简化了不少,但编码量还是比较大,尤其是定义评估函数、训练过程等,接下来我们介绍一种更简单、高效的方法,即利用高阶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节一样,这里不再赘述。
这里主要使用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)。
在训练模型之前,我们需要对模型进行编译配置,这是通过compile方法完成的。它接收3个参数。
❑优化器(optimizer):可以是内置优化器的字符串标识符,如rmsprop或adagrad,也可以是Optimizer类的对象。
❑损失函数(loss):模型最小化的目标函数。它可以是内置损失函数的字符串标识符,如categorical_crossentropy或mse等,也可以是自定义的损失函数。
❑评估标准(metrics):可以是内置的标准字符串标识符,也可以是自定义的评估标准函数。对于分类问题,一般设置metrics=['accuracy']。
1)编译模型。
2)训练模型。这里采用回调机制定时保存模型。
最后两次的迭代结果:
最后,根据模型训练的结果自动保存最好的那个模型。
3)可视化运行结果。
使用高阶API构建模型的可视化结果如图3-8所示。
图3-8 使用高阶API构建模型的可视化结果
输入如下代码测试模型。
运行结果如下:
1)选择最好的模型参数恢复模型。
2)查看网络结构。
运行结果与图3-7的网络结构完全一致。
3)检查其准确率(accuracy)。
运行结果如下:
由结果可知,其结果与预测结果完全一致!
本章介绍了几种常用的API构建网络和训练模型的方法。这些API从封装程度来划分,可分为低阶API、中阶API和高阶API。低阶API基本用TensorFlow实现,如自定义层、损失函数等;中阶API使用层模块(如tf.keras.layers.Dense)、内置的损失函数、优化器等构建模型;高阶API的封装程度最高,使用tf.keras构建模型、训练模型。这3种方法各有优缺点,低阶API代码量稍多一些,但定制能力较高,而用高阶API构建模型和训练模型的代码比较简洁,但定制能力稍弱。实际上,我们的大部分任务基本都可以用高阶API来实现,对初学者来说,使用高阶API构建模型、训练模型是首选。