导入所需要的包:
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
from tensorflow.keras import layers
注意这里的tensorflow_datasets
是一个开箱即用的数据集集合,包含数十种常用的机器学习数据集。通过简单的几行代码即可将数据以 tf.data.Dataset
的格式载入。关于 tf.data.Dataset
的使用可参考 tf.data。
然后,最基础的用法是使用 tfds.load
方法,载入所需的数据集。例如,以下三行代码分别载入了 MNIST、猫狗分类和 tf_flowers 三个图像分类数据集:
dataset = tfds.load("mnist", split=tfds.Split.TRAIN, as_supervised=True)
dataset = tfds.load("cats_vs_dogs", split=tfds.Split.TRAIN, as_supervised=True)
dataset = tfds.load("tf_flowers", split=tfds.Split.TRAIN, as_supervised=True)
当第一次载入特定数据集时,TensorFlow Datasets 会自动从云端下载数据集到本地,并显示下载进度。在本例中,我们使用的是tf_flowers
数据集。
(train_ds, val_ds, test_ds), metadata = tfds.load( # 用load下载
'tf_flowers',
split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True, # 返回二元组
as_supervised=True,
)
若 as_supervised=True
,则根据数据集的特性,将数据集中的每行元素整理为有监督的二元组 (input, label)
(即 “数据 + 标签”)形式。下载进度如下所示:
数据集分割split=['train[:80%]', 'train[80%:90%]', 'train[90%:]']
进度如下所示:
其在服务器中的文件格式为:
数据集中有5类。
num_classes = metadata.features['label'].num_classes
# print(num_classes) # 5
我们可以通过take()查看数据集前两个数据样式:
for image, label in train_ds.take(2):
plt.figure()
plt.imshow(image)
plt.title(get_label_name(label))
plt.show()
我们可以使用 Keras 预处理层将图像大小调整为一致的形状(使用 tf.keras.layers.Resizing
),并重新缩放像素值(使用 tf.keras.layers.Rescaling
)。
IMG_SIZE = 180
resize_and_rescale = tf.keras.Sequential([
layers.Resizing(IMG_SIZE, IMG_SIZE), # 调整成一致的形状
layers.Rescaling(1./255) # 重新缩放像素值
])
Note: The rescaling layer above standardizes pixel values to the [0,1] range. If instead you wanted it to be [-1, 1], you would write tf.keras.layers.Rescaling(1./127.5, offset=-1).
我们可视化应用这些层于图像的结果。
验证一下像素值是否在0-1之间:
print("Min and max pixel values:", result.numpy().min(), result.numpy().max())
# Min and max pixel values: 0.0 1.0
我们也可以使用 Keras 预处理层进行数据增强,例如 tf.keras.layers.RandomFlip
和 tf.keras.layers.RandomRotation
。
让我们创建一些预处理层并将它们重复应用于同一图像。
# 数据增强:RandomFlip + RandomRotation
data_augmentation = tf.keras.Sequential([
layers.RandomFlip("horizontal_and_vertical"),
layers.RandomRotation(0.2),
])
将第一张图片加入一个batch中查看数据增强的效果:
# Add the image to a batch
image = tf.expand_dims(image, 0)
plt.figure(figsize=(10, 10))
for i in range(9):
augmented_image = data_augmentation(image)
ax = plt.subplot(3, 3, i+1) # 控制9个子图
plt.imshow(augmented_image[0])
plt.axis("off")
plt.show()
有多种预处理层可用于数据增强,包括 tf.keras.layers.RandomContrast
、tf.keras.layers.RandomCrop
、tf.keras.layers.RandomZoom
等。
使用 Keras 预处理层的两个方法: Make the preprocessing layers part of your model 和 Apply the preprocessing layers to your dataset。
第一种方法: 使预处理层成为模型的一部分。
model = tf.keras.Sequential([
# Add the preprocessing layers you created earlier.
resize_and_rescale,
data_augmentation,
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
# Rest of your model.
])
在这种情况下需要注意两点:
数据增强将在设备上运行,与其他层同步,并受益于GPU加速。
当你使用model.save
导出你的模型时,预处理层将与模型的其余部分一起保存。如果您稍后部署这个模型,它将自动标准化映像(根据您的层的配置)。这可以使您不必重新实现逻辑服务器端。
数据增强在测试时处于非活动状态,因此输入图像只会在调用 Model.fit
(而不是 Model.evaluate
或 Model.predict
)期间被增强。
第二种方法: 将预处理层应用于您的数据集。
aug_ds = train_ds.map(
lambda x, y: (resize_and_rescale(x, training=True), y)) # 传入2个参数的函数
使用这种方法,您将使用Dataset.map
以创建产生成批增强图像的数据集。在这种情况下:
Dataset.prefetch
将GPU上的模型训练与数据预处理重叠。如下所示。model.save
时,预处理层将不会随模型一起导出。在保存模型或在服务器端重新实现它们之前,您需要将它们附加到模型上。训练结束后,可以在导出之前附加预处理层。您可以在图像分类教程中找到第一种方法的示例。在这里演示第二种方案。
使用前面创建的Keras预处理层配置训练、验证和测试数据集。您还将配置数据集以提高性能,使用并行读取和缓冲预取从磁盘生成批处理,而不会造成I/O阻塞。
注意:数据增强应该只应用于训练集。
batch_size = 32
AUTOTUNE = tf.data.AUTOTUNE
def prepare(ds, shuffle=False, augment=False):
# Resize and rescale all datasets.
ds = ds.map(lambda x, y: (resize_and_rescale(x), y),
num_parallel_calls=AUTOTUNE)
if shuffle:
ds = ds.shuffle(1000)
# Batch all datasets.
ds = ds.batch(batch_size)
# Use data augmentation only on the training set.
if augment:
ds = ds.map(lambda x, y: (data_augmentation(x, training=True), y),
num_parallel_calls=AUTOTUNE)
# Use buffered prefetching on all datasets.
return ds.prefetch(buffer_size=AUTOTUNE)
train_ds = prepare(train_ds, shuffle=True, augment=True) # 数据增强只应用于训练集
val_ds = prepare(val_ds)
test_ds = prepare(test_ds)
完整起见,我们现在将使用刚刚准备的数据集训练模型。
Sequential模型包含三个卷积块(tf.keras.layers.Conv2D
),每个卷积块中都有一个最大池化层(tf.keras.layers.MaxPooling2D
)。这是一个有128个unit的全连接层(tf.keras.layers.Dense
),由ReLU激活功能激活。这个模型还没有调整到精确度(这里的目标是展示机制)。
model = tf.keras.Sequential([
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(num_classes)
])
在这里,我们选择 tf.keras.optimizers.Adam
优化方法和tf.keras.losses.SparseCategoricalCrossentropy
损失函数。为了查看每个epoch的训练集和验证集准确性,需将metrics
参数传递给 Model.compile
。
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
训练几个 epoch:
epochs=5
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
训练过程如下:
Epoch 1/5
92/92 [==============================] - 19s 96ms/step - loss: 1.2814 - accuracy: 0.4632 - val_loss: 1.1246 - val_accuracy: 0.5804
Epoch 2/5
92/92 [==============================] - 3s 26ms/step - loss: 1.0684 - accuracy: 0.5753 - val_loss: 0.9848 - val_accuracy: 0.6213
Epoch 3/5
92/92 [==============================] - 3s 28ms/step - loss: 0.9701 - accuracy: 0.6100 - val_loss: 1.0106 - val_accuracy: 0.6185
Epoch 4/5
92/92 [==============================] - 3s 26ms/step - loss: 0.9046 - accuracy: 0.6502 - val_loss: 0.9455 - val_accuracy: 0.6076
Epoch 5/5
92/92 [==============================] - 3s 27ms/step - loss: 0.8676 - accuracy: 0.6717 - val_loss: 0.9243 - val_accuracy: 0.6512
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)
结果为:
12/12 [==============================] - 1s 14ms/step - loss: 0.8008 - accuracy: 0.6839
Accuracy 0.6839237213134766
我们同样可以自定义数据增强层。
本节将展示两种方法:
tf.keras.layers.Lambda
层。这是编写简洁代码的好方法。这两个图层都将根据一定的概率随机反转图像中的颜色。
def random_invert_img(x, p=0.5):
if tf.random.uniform([]) < p: # tf.random.uniform函数从均匀分布中输出随机值
x = (255-x)
else:
x
return x
def random_invert(factor=0.5):
return layers.Lambda(lambda x: random_invert_img(x, factor))
random_invert = random_invert()
plt.figure(figsize=(10, 10))
for i in range(9):
augmented_image = random_invert(image)
ax = plt.subplot(3, 3, i + 1)
plt.imshow(augmented_image[0].numpy().astype("uint8"))
plt.axis("off")
class RandomInvert(layers.Layer):
def __init__(self, factor=0.5, **kwargs):
super().__init__(**kwargs)
self.factor = factor
def call(self, x):
return random_invert_img(x)
展示经过自定义数据增强层处理后的图片:
plt.figure()
_ = plt.imshow(RandomInvert()(image)[0])
plt.show()
上面的Keras预处理实用程序是方便的。但是,为了更好地控制,您可以使用 tf.data
和tf.image
编写自己的数据增强管道或层。
由于花卉的数据集之前配置了数据增强,让我们导入它以重新开始:
(train_ds, val_ds, test_ds), metadata = tfds.load(
'tf_flowers',
split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True,
as_supervised=True,
)
检索要使用的图像:
image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))
def visualize(original, augmented):
fig = plt.figure()
plt.subplot(1,2,1)
plt.title('Original image')
plt.imshow(original)
plt.subplot(1,2,2)
plt.title('Augmented image')
plt.imshow(augmented)
使用 tf.image.flip_left_right
垂直或水平翻转图像:
flipped = tf.image.flip_left_right(image)
visualize(image, flipped)
我们可以使用 tf.image.rgb_to_grayscale
对图像进行灰度化:
grayscaled = tf.image.rgb_to_grayscale(image)
visualize(image, tf.squeeze(grayscaled))
_ = plt.colorbar()
saturated = tf.image.adjust_saturation(image, 3)
visualize(image, saturated)
tf.image.adjust_brightness
通过提供一个亮度因子来改变图像的亮度:
bright = tf.image.adjust_brightness(image, 0.4)
visualize(image, bright)
使用 tf.image.central_crop
将图像从中心裁剪到您想要的图像部分:
cropped = tf.image.central_crop(image, central_fraction=0.5)
visualize(image, cropped)
使用 tf.image.rot90 将图像旋转 90 度:
rotated = tf.image.rot90(image)
visualize(image, rotated)
对图像应用随机变换可以进一步帮助泛化和扩展数据集。 当前的 tf.image API
提供了八种这样的随机图像操作(ops):
这些随机图像操作是纯功能性的:输出只依赖于输入。这使得它们在高性能、确定性的输入管道中很容易使用。它们需要每一步输入一个seed
值。对于相同的种子,它们将返回与调用次数无关的相同结果。(seed
是一个形状为 (2,) 的张量,其值为任意整数。)
在以下部分中, 我们将演示使用随机图像操作来转换图像的示例。 演示如何将随机变换应用于训练数据集。
使用 tf.image.stateless_random_brightness
通过其提供的亮度因子和种子随机改变图像的亮度。亮度因子在[-max_delta, max_delta]
范围内随机选择,并与给定的种子相关联。
for i in range(3):
seed = (i, 0) # tuple of size (2,)
stateless_random_brightness = tf.image.stateless_random_brightness(
image, max_delta=0.95, seed=seed)
visualize(image, stateless_random_brightness)
通过提供对比度范围和种子,使用 tf.image.stateless_random_contrast
随机更改图像的对比度。对比度范围是在区间 [lower, upper]
中随机选择的,并与给定的种子相关联。
for i in range(3):
seed = (i, 0) # tuple of size (2,)
stateless_random_contrast = tf.image.stateless_random_contrast(
image, lower=0.1, upper=0.9, seed=seed)
visualize(image, stateless_random_contrast)
通过提供目标大小和种子,使用 tf.image.stateless_random_crop
随机裁剪图像。从图像中裁剪出来的部分位于随机选择的偏移处,并与给定的种子相关联。
for i in range(3):
seed = (i, 0) # tuple of size (2,)
stateless_random_crop = tf.image.stateless_random_crop(
image, size=[210, 300, 3], seed=seed)
visualize(image, stateless_random_crop)
首先,让我们再次下载图像数据集,以防它们在前面的部分中被修改。
(train_datasets, val_ds, test_ds), metadata = tfds.load(
'tf_flowers',
split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True,
as_supervised=True,
)
接下来,定义一个用于调整图像大小和重新缩放图像的实用函数。此函数将用于统一数据集中图像的大小和比例:
def resize_and_rescale(image, label):
image = tf.cast(image, tf.float32)
image = tf.image.resize(image, [IMG_SIZE, IMG_SIZE])
image = (image / 255.0)
return image, label
我们还定义可以将随机变换应用于图像的增强函数。下一步将在数据集上使用此函数。
def augment(image_label, seed):
image, label = image_label
image, label = resize_and_rescale(image, label)
image = tf.image.resize_with_crop_or_pad(image, IMG_SIZE + 6, IMG_SIZE + 6)
# Make a new seed.
new_seed = tf.random.experimental.stateless_split(seed, num=1)[0, :]
# Random crop back to the original size.
image = tf.image.stateless_random_crop(
image, size=[IMG_SIZE, IMG_SIZE, 3], seed=seed)
# Random brightness.
image = tf.image.stateless_random_brightness(
image, max_delta=0.5, seed=new_seed)
image = tf.clip_by_value(image, 0, 1)
return image, label
方案一:使用tf.data.experimental.Counter
创建一个tf.data.experimental.Counter
对象(我们叫它counter)和Dataset .zip
(counter, counter)。这将确保数据集中的每个图像都与一个唯一的值(形状(2,))相关联,这是基于counter
,稍后可以作为随机转换的seed
值传递到augment
函数。
# Create a `Counter` object and `Dataset.zip` it together with the training set.
counter = tf.data.experimental.Counter()
train_ds = tf.data.Dataset.zip((train_datasets, (counter, counter)))
将augment
函数应用于数据集:
train_ds = (
train_ds
.shuffle(1000)
.map(augment, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
val_ds = (
val_ds
.map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
test_ds = (
test_ds
.map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
方案二:使用tf.random.Generator
tf.random.Generator
对象。在同一个生成器对象上调用make_seeds
函数总是会返回一个新的、唯一的种子值。make_seeds
函数;2)将新生成的种子值传递给增广函数进行随机转换。(注意:tf.random.Generator
对象将 RNG 状态存储在 tf.Variable
中,这意味着它可以保存为检查点或 SavedModel)
# Create a generator.
rng = tf.random.Generator.from_seed(123, alg='philox')
# Create a wrapper function for updating seeds.
def f(x, y):
seed = rng.make_seeds(2)[0]
image, label = augment((x, y), seed)
return image, label
将包装函数 f 映射到训练数据集,并将 resize_and_rescale 函数映射到验证集和测试集:
train_ds = (
train_datasets
.shuffle(1000)
.map(f, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
val_ds = (
val_ds
.map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
test_ds = (
test_ds
.map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
.batch(batch_size)
.prefetch(AUTOTUNE)
)
这些数据集现在可用于训练模型,如上所示。
下一步:
本教程演示了使用Keras预处理层和tf.image进行数据增强。要了解如何在模型中包含预处理层,请参阅图像分类教程。您可能还对学习预处理层如何帮助您对文本进行分类感兴趣,如基本文本分类教程所示。你可以学到更多关于tf的知识。您可以在这里学习如何配置您的输入管道以提高性能。