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使用fitAPI训练模型。可以选择在每个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.TensorBoardcallback函数提供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
https://www.cs.toronto.edu/~kriz/cifar.html