TensorFlow 2现已上线!本教程将引导您完成使用深度学习构建简单的CIFAR-10图像分类器的过程。在本教程中,我们将:
- 定义模型
- 建立数据管道
- 训练模型
- 多个GPU加快训练速度
- 添加callback以监视进度/更新学习进度
本教程中的代码可在此处获得。
定义模型
TensorFlow 2使用Keras作为其高级API。Keras提供了两种定义模型的方法:顺序API和功能API。
使用Keras序列API定义模型
from tf.keras.models import Sequential from tf.keras.layers import Conv2, MaxPooling2D, Flatten, Dense model = Sequential([ Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)), MaxPooling2D(pool_size=(2, 2)), Flatten(), Dense(10, activation='softmax') ])
使用Keras的功能性API定义相同的模型
from tf.keras.models import Model from tf.keras.layers import Input, Conv2, MaxPooling2D, Flatten, Dense inputs = Input(shape=(32, 32, 3)) x = Conv2D(32, (3, 3), activation='relu')(inputs) x = MaxPooling2D(pool_size=(2, 2))(x) x = Flatten()(x) x = Dense(10, activation='softmax')(x) model = Model(inputs=inputs, outputs=x)
顺序API与功能API
这些API之间的主要区别在于,顺序API要求为其提供第一层input_shape
,而功能性API要求为其提供第一层,tf.keras.layers.Input
并且需要tf.keras.models.Model
在最后调用构造函数。
顺序API更简洁,而功能性API更灵活,因为它允许模型为非顺序模型。例如,在ResNet中具有跳过连接。本教程改编了TensorFlow的ResNet的官方Keras实现,该实现使用了功能性API。
input_shape = (32, 32, 3) img_input = Input(shape=input_shape) model = resnet_cifar_model.resnet56(img_input, classes=10)
建立数据管道
现在,我们定义了一个模型。要训练该模型,我们需要一个数据管道来为其提供标记的训练数据。数据管道执行以下任务:
- 加载:将数据集(例如图像和标签)从存储设备复制到程序的内存中。
- 预处理:转换数据集。例如,在图像分类中,我们可能会调整图像的大小,变白,混洗或批处理。
- 馈送:将示例从数据集中铲入训练循环。
从存储中加载数据
首先,我们将CIFAR-10从存储加载到numpy ndarrays
:
(x, y), (x_test, y_test) = keras.datasets.cifar10.load_data()
注意:
- 首次引用时
keras.datasets.cifar10.load_data
,CIFAR-10将从互联网上下载到~/.keras/datasets/cifar-10-batches-py.tar.gz.
后续引用不涉及网络。 x
代表50,000张图像,尺寸为32 x 32 x 3(宽度,高度和三个RGB通道)。y
代表这50,000张图片的标签。
print(type(x), type(y)) (<type 'numpy.ndarray'>, <type 'numpy.ndarray'>)
print(x.shape, y.shape) ((50000, 32, 32, 3), (50000, 1))
从理论上讲,我们可以简单地将这些原始numpy.ndarray
对象馈送到训练循环中,并将其称为数据管道。但是,为了获得更高的模型精度,我们需要对数据进行预处理(即在使用前对其进行某些转换)。为此,我们利用Tensorflow的Dataset类。
tf.data.Dataset类
TensorFlow数据集类主要用于两个目的:
- 它充当保存训练数据的容器。
- 它可用于对训练数据的元素进行更改。
我们实例化一个tensorflow.data.Dataset
表示CIFAR-10数据集的对象,如下所示:
# Load data from storage to memory. (x, y), (x_test, y_test) = keras.datasets.cifar10.load_data() # Instantiate the Dataset class. train_dataset = tf.data.Dataset.from_tensor_slices((x, y))
在training期间,train_dataset
将通过take()
迭代器访问存储在其中的CIFAR-10训练示例:
for image, label in train_dataset.take(1): (image.shape, label.shape)
照原样,我们不执行任何数据预处理。调用take()
只是发出原始的CIFAR-10图片;前20张图片如下:
资料扩充
增强通常用于“膨胀”训练数据集,这可以提高泛化性能。
让我们通过对每个图像执行以下步骤来扩充CIFAR-10数据集:
- 用黑色的四像素边框填充图像。
- 从填充的图像中随机裁剪32 x 32区域。
- 翻转硬币以确定是否应水平翻转图像。
为此,我们首先定义一个给定图像的函数,该函数执行上述步骤1-3:
def augmentation(x, y): x = tf.image.resize_with_crop_or_pad( x, HEIGHT + 8, WIDTH + 8) x = tf.image.random_crop(x, [HEIGHT, WIDTH, NUM_CHANNELS]) x = tf.image.random_flip_left_right(x) return x, y
接下来,我们调用方法map
;此调用返回一个新Dataset
对象,该对象包含将CIFAR-10中的每个图像传递到的结果augmentation
。这个新对象将以原始顺序发出变换后的图像:
train_dataset = train_dataset.map(augmentation)
这些是增强后的前20张图像:
注意:扩充只能应用于训练集;在推理过程中应用增强将导致不确定的预测和验证分数。
Shuffling(改组)
我们随机地对数据集进行洗牌。TensorFlow数据集具有一种shuffle
方法,可以将其链接到我们的扩充,如下所示:
train_dataset = (train_dataset .map(augmentation) .shuffle(buffer_size=50000))
为了进行完美的混洗,的值buffer_size
应大于或等于数据集的大小(在这种情况下为50,000);对于大型数据集,这是不可能的。
改组后,以下是来自数据集的20张图像:
Normalization(正常化)
规范化数据是常见的做法。在这里,定义一个线性缩放每个图像以具有零均值和单位方差的函数:
def normalize(x, y): x = tf.image.per_image_standardization(x) return x, y
接下来,我们将其与我们的扩充和混排操作链接起来:
train_dataset = (train_dataset .map(augmentation) .shuffle(buffer_size=50000) .map(normalize))
Batching(批处理)
最后,我们batch
是数据集。我们设置drop_remainder
为True
删除足够的训练示例,以使训练集的大小可被整除batch_size
。
train_dataset = (train_dataset.map(augmentation) .map(normalize) .shuffle(50000) .batch(128, drop_remainder=True)
现在,我们有了完整的数据管道。现在我们可以开始训练了。
训练模型
训练之前需要先编译Keras模型。编译本质上定义了三件事:损失函数,优化器和评估指标
model.compile( loss='sparse_categorical_crossentropy', optimizer=keras.optimizers.SGD(learning_rate=0.1, momentum=0.9), metrics=['accuracy'])
请注意,我们在此处使用sparse_categorical_crossentropy
和sparse_categorical_accuracy
,因为每个标签都由单个整数(类的索引)表示。每个人都应该使用categorical_crossentropy
和categorical_accuracy
如果一个热矢量表示每个标签。
Keras使用fit
API训练模型。可以选择在每个validation_freq
训练时期在验证数据集上测试模型。
请注意,我们仅将测试数据集用于验证,因为CIFAR-10本身并不提供验证集。模型的验证应在与训练集分离的一组数据上进行。
model.fit(train_dataset, epochs=60, validation_data=test_dataset, validation_freq=1)
请注意,在此示例中,该fit
函数采用TensorFlow Dataset对象(train_dataset
和test_dataset
)。如前所述,它也可以将numpy ndarrays作为输入。使用数组的缺点是缺乏将变换应用于数据集的灵活性。
model.fit(x, y, batch_size=128, epochs=5, shuffle=True, validation_data=(x_test, y_test))
要评估模型,请evaluate
使用测试数据集调用该方法:
model.evaluate(test_loader)
多GPU
到目前为止,我们已经展示了如何使用TensorFlow的Dataset API创建数据管道,以及如何使用Keras API定义模型并进行训练和评估。下一步是使代码在多个GPU上运行。
实际上,Tensorflow 2使将单GPU实现转换为可与多个GPU一起运行变得非常容易。您需要做的就是定义一个分发策略并在该策略的范围内创建模型:
mirrored_strategy = tf.distribute.MirroredStrategy() with mirrored_strategy.scope(): model = resnet.resnet56(classes=NUM_CLASSES) model.compile( optimizer=keras.optimizers.SGD(learning_rate=0.1, momentum=0.9), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
我们MirroredStrategy
在这里使用,它在一台机器上支持在多个GPU上进行同步分布式训练。默认情况下,它使用NVIDIA NCCL作为多GPU全缩减实现。
请注意,您将要batch
根据使用的GPU数量,使用数据管道的方法来缩放批处理大小。
train_loader = train_loader.map(preprocess).shuffle(50000).batch(BS_PER_GPU*NUM_GPUS) test_loader = test_loader.map(preprocess).batch(BS_PER_GPU*NUM_GPUS)
Adding callbacks
我们经常需要在训练期间执行自定义操作。例如,您可能希望在训练期间记录统计信息,以进行调试或优化。实施学习率表,以提高训练效率;或在过滤器汇聚时保存它们的可视快照。在TensorFlow 2中,您可以在训练期间使用callback功能来实现自定义事件。
Tensorboard
TensorBoard主要用于在训练期间记录和可视化信息。这对于检查模型的性能非常方便。通过tensorflow.keras.callbacks.TensorBoard
callback函数提供Tensorboard支持:
from tensorflow.keras.callbacks import TensorBoard log_dir="logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") tensorboard_callback = TensorBoard( log_dir=log_dir, update_freq='batch', histogram_freq=1) model.fit(..., callbacks=[tensorboard_callback])
在上面的示例中,我们首先创建一个TensorBoardcallback,记录每个训练步骤的数据(通过update_freq=batch
),然后将此callback附加到fit
函数。TensorFlow将生成tfevents
文件,可以使用TensorBoard可视化。例如,这是训练过程中分类准确性的可视化(蓝色是训练准确性,红色是验证准确性):
Learning Rate Schedule
通常,随着训练的进行,我们希望对学习率有很好的控制。可以将自定义学习率进度表实现为callback函数。在这里,我们创建了一个自定义schedule
函数,该函数使用步进函数(第30个时期和第45个时期)来降低学习率。此计划将转换为keras.callbacks.LearningRateScheduler
并附加到该fit
函数。
from tensorflow.keras.callbacks import LearningRateScheduler BASE_LEARNING_RATE = 0.1 LR_SCHEDULE = [(0.1, 30), (0.01, 45)] def schedule(epoch): initial_learning_rate = BASE_LEARNING_RATE * BS_PER_GPU / 128 learning_rate = initial_learning_rate for mult, start_epoch in LR_SCHEDULE: if epoch >= start_epoch: learning_rate = initial_learning_rate * mult else: break tf.summary.scalar('learning rate', data=learning_rate, step=epoch) return learning_rate lr_schedule_callback = LearningRateScheduler(schedule) model.fit(..., callbacks=[..., lr_schedule_callback])
这些是在60周期训练中自定义学习率的统计信息:
总结
本教程以图像分类为例说明TensorFlow 2.0的基础。我们介绍了:
- 使用TensorFlow 2的数据集API的数据管道
- 使用Keras训练,评估,保存和还原模型(TensorFlow 2的官方高级API)
- 具有分布式策略的多GPU
- 带有callback的定制训练
以下是本教程的完整代码。您还可以使用此Tensorflow 2.0教程存储库复制有关TensorFlow 2.0的教程。
import datetime import tensorflow as tf from tensorflow import keras from tensorflow.keras.callbacks import TensorBoard, LearningRateScheduler import resnet NUM_GPUS = 2 BS_PER_GPU = 128 NUM_EPOCHS = 60 HEIGHT = 32 WIDTH = 32 NUM_CHANNELS = 3 NUM_CLASSES = 10 NUM_TRAIN_SAMPLES = 50000 BASE_LEARNING_RATE = 0.1 LR_SCHEDULE = [(0.1, 30), (0.01, 45)] def preprocess(x, y): x = tf.image.per_image_standardization(x) return x, y def augmentation(x, y): x = tf.image.resize_with_crop_or_pad( x, HEIGHT + 8, WIDTH + 8) x = tf.image.random_crop(x, [HEIGHT, WIDTH, NUM_CHANNELS]) x = tf.image.random_flip_left_right(x) return x, y def schedule(epoch): initial_learning_rate = BASE_LEARNING_RATE * BS_PER_GPU / 128 learning_rate = initial_learning_rate for mult, start_epoch in LR_SCHEDULE: if epoch >= start_epoch: learning_rate = initial_learning_rate * mult else: break tf.summary.scalar('learning rate', data=learning_rate, step=epoch) return learning_rate (x,y), (x_test, y_test) = keras.datasets.cifar10.load_data() train_dataset = tf.data.Dataset.from_tensor_slices((x,y)) test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test)) tf.random.set_seed(22) train_dataset = train_dataset.map(augmentation).map(preprocess).shuffle(NUM_TRAIN_SAMPLES).batch(BS_PER_GPU * NUM_GPUS, drop_remainder=True) test_dataset = test_dataset.map(preprocess).batch(BS_PER_GPU * NUM_GPUS, drop_remainder=True) input_shape = (32, 32, 3) img_input = tf.keras.layers.Input(shape=input_shape) opt = keras.optimizers.SGD(learning_rate=0.1, momentum=0.9) if NUM_GPUS == 1: model = resnet.resnet56(img_input=img_input, classes=NUM_CLASSES) model.compile( optimizer=opt, loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy']) else: mirrored_strategy = tf.distribute.MirroredStrategy() with mirrored_strategy.scope(): model = resnet.resnet56(img_input=img_input, classes=NUM_CLASSES) model.compile( optimizer=opt, loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy']) log_dir="logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") file_writer = tf.summary.create_file_writer(log_dir + "/metrics") file_writer.set_as_default() tensorboard_callback = TensorBoard( log_dir=log_dir, update_freq='batch', histogram_freq=1) lr_schedule_callback = LearningRateScheduler(schedule) model.fit(train_dataset, epochs=NUM_EPOCHS, validation_data=test_dataset, validation_freq=1, callbacks=[tensorboard_callback, lr_schedule_callback]) model.evaluate(test_dataset) model.save('model.h5') new_model = keras.models.load_model('model.h5') new_model.evaluate(test_dataset)
给大家介绍一下租用GPU做实验的方法,我们是在智星云租用的GPU,使用体验很好。具体大家可以参考:智星云官网: http://www.ai-galaxy.cn/,淘宝店:https://shop36573300.taobao.com/公众号: 智星AI,
参考文献:
https://lambdalabs.com/blog/tensorflow-2-0-tutorial-01-image-classification-basics/
https://github.com/lambdal/TensorFlow2-tutorial/tree/master/01-basic-image-classification