มีคำถาม? เชื่อมต่อกับชุมชนที่ฟอรัม TensorFlow เยี่ยมชมฟอรัม

การเพิ่มข้อมูล

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดสมุดบันทึก

ภาพรวม

บทช่วยสอนนี้สาธิตการเพิ่มข้อมูล: เทคนิคในการเพิ่มความหลากหลายของชุดการฝึกของคุณโดยใช้การแปลงแบบสุ่ม (แต่เหมือนจริง) เช่นการหมุนภาพ คุณจะได้เรียนรู้วิธีใช้การเพิ่มข้อมูลได้สองวิธี ขั้นแรกคุณจะใช้ Keras Preprocessing Layers ถัดไปคุณจะใช้ tf.image

ติดตั้ง

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds

from tensorflow.keras import layers

ดาวน์โหลดชุดข้อมูล

บทช่วยสอนนี้ใช้ชุดข้อมูล tf_flowers เพื่อความสะดวกให้ดาวน์โหลดชุดข้อมูลโดยใช้ TensorFlow Datasets หากคุณต้องการเรียนรู้เกี่ยวกับวิธีการนำเข้าข้อมูลอื่น ๆ โปรดดูบทแนะนำการ โหลดรูปภาพ

(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,
)

ชุดข้อมูลดอกไม้มี 5 คลาส

num_classes = metadata.features['label'].num_classes
print(num_classes)
5

มาดึงภาพจากชุดข้อมูลและใช้เพื่อสาธิตการเพิ่มข้อมูล

get_label_name = metadata.features['label'].int2str

image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))

png

ใช้ Keras ก่อนการประมวลผลเลเยอร์

การปรับขนาดและการปรับขนาด

คุณสามารถใช้เลเยอร์ก่อนการประมวลผลเพื่อ ปรับขนาด รูปภาพของคุณให้มีรูปร่างที่สอดคล้องกันและเพื่อ ปรับ ขนาดค่าพิกเซลอีกครั้ง

IMG_SIZE = 180

resize_and_rescale = tf.keras.Sequential([
  layers.experimental.preprocessing.Resizing(IMG_SIZE, IMG_SIZE),
  layers.experimental.preprocessing.Rescaling(1./255)
])

คุณสามารถดูผลลัพธ์ของการนำเลเยอร์เหล่านี้ไปใช้กับรูปภาพได้

result = resize_and_rescale(image)
_ = plt.imshow(result)

png

คุณสามารถตรวจสอบได้ว่าพิกเซลอยู่ใน [0-1]

print("Min and max pixel values:", result.numpy().min(), result.numpy().max())
Min and max pixel values: 0.0 1.0

การเพิ่มข้อมูล

คุณสามารถใช้เลเยอร์ก่อนการประมวลผลสำหรับการเพิ่มข้อมูลได้เช่นกัน

มาสร้างเลเยอร์ก่อนการประมวลผลสองสามชั้นแล้วใช้ซ้ำกับภาพเดียวกัน

data_augmentation = tf.keras.Sequential([
  layers.experimental.preprocessing.RandomFlip("horizontal_and_vertical"),
  layers.experimental.preprocessing.RandomRotation(0.2),
])
# 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)
  plt.imshow(augmented_image[0])
  plt.axis("off")

png

มี เลเยอร์ ก่อนการประมวลผลมากมายที่คุณสามารถใช้สำหรับการเพิ่มข้อมูลรวมถึง layers.RandomContrast layers.RandomCroplayers.RandomZoom และอื่น ๆ

สองตัวเลือกในการใช้เลเยอร์ก่อนการประมวลผล

มีสองวิธีที่คุณสามารถใช้เลเยอร์ก่อนการประมวลผลเหล่านี้กับการแลกเปลี่ยนที่สำคัญ

ตัวเลือกที่ 1: กำหนดให้เลเยอร์ก่อนการประมวลผลเป็นส่วนหนึ่งของโมเดลของคุณ

model = tf.keras.Sequential([
  resize_and_rescale,
  data_augmentation,
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  # Rest of your model
])

มีประเด็นสำคัญสองประการที่ต้องระวังในกรณีนี้:

  • การเพิ่มข้อมูลจะทำงานบนอุปกรณ์พร้อมกันกับเลเยอร์ที่เหลือของคุณและได้รับประโยชน์จากการเร่งความเร็วของ GPU

  • เมื่อคุณส่งออกโมเดลของคุณโดยใช้ model.save เลเยอร์ก่อนการประมวลผลจะถูกบันทึกพร้อมกับโมเดลที่เหลือของคุณ หากคุณปรับใช้โมเดลนี้ในภายหลังโมเดลนี้จะกำหนดรูปภาพให้เป็นมาตรฐานโดยอัตโนมัติ (ตามการกำหนดค่าของเลเยอร์ของคุณ) ซึ่งจะช่วยให้คุณไม่ต้องใช้ความพยายามในการติดตั้งตรรกะฝั่งเซิร์ฟเวอร์อีกครั้ง

ตัวเลือกที่ 2: ใช้เลเยอร์ก่อนการประมวลผลกับชุดข้อมูลของคุณ

aug_ds = train_ds.map(
  lambda x, y: (resize_and_rescale(x, training=True), y))

ด้วยวิธีนี้คุณสามารถใช้ Dataset.map เพื่อสร้างชุดข้อมูลที่ให้ผลลัพธ์เป็นชุดของภาพเสริม ในกรณีนี้:

  • การเพิ่มข้อมูลจะเกิดขึ้นแบบอะซิงโครนัสบน CPU และไม่มีการปิดกั้น คุณสามารถซ้อนทับการฝึกโมเดลของคุณบน GPU ด้วยการประมวลผลข้อมูลล่วงหน้าโดยใช้ Dataset.prefetch แสดงด้านล่าง
  • ในกรณีนี้เลเยอร์การประมวลผลล่วงหน้าจะไม่ถูกเอ็กซ์พอร์ตพร้อมกับโมเดลเมื่อคุณเรียก model.save คุณจะต้องแนบเข้ากับโมเดลของคุณก่อนที่จะบันทึกหรือติดตั้งใหม่ทางฝั่งเซิร์ฟเวอร์ หลังจากการฝึกอบรมคุณสามารถแนบเลเยอร์ก่อนการประมวลผลก่อนที่จะส่งออก

คุณสามารถดูตัวอย่างของตัวเลือกแรกได้ในบทแนะนำการ จัดหมวดหมู่รูปภาพ มาสาธิตตัวเลือกที่สองที่นี่

นำเลเยอร์ก่อนการประมวลผลไปใช้กับชุดข้อมูล

กำหนดค่าชุดข้อมูลการฝึกการตรวจสอบความถูกต้องและการทดสอบด้วยชั้นก่อนการประมวลผลที่คุณสร้างไว้ด้านบน นอกจากนี้คุณยังกำหนดค่าชุดข้อมูลสำหรับประสิทธิภาพโดยใช้การอ่านแบบขนานและการดึงข้อมูลล่วงหน้าที่บัฟเฟอร์เพื่อให้ได้แบทช์จากดิสก์โดยที่ I / O ไม่บล็อก คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับประสิทธิภาพของชุดข้อมูลในประสิทธิภาพที่ ดีขึ้นด้วยคู่มือ tf.data API

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 prefecting 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)

ฝึกโมเดล

เพื่อความสมบูรณ์ตอนนี้คุณจะฝึกโมเดลโดยใช้ชุดข้อมูลเหล่านี้ โมเดลนี้ไม่ได้รับการปรับแต่งเพื่อความแม่นยำ (เป้าหมายคือการแสดงกลไกให้คุณเห็น)

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)
])
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
epochs=5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/5
92/92 [==============================] - 13s 100ms/step - loss: 1.3428 - accuracy: 0.4080 - val_loss: 1.1263 - val_accuracy: 0.5395
Epoch 2/5
92/92 [==============================] - 3s 26ms/step - loss: 1.0597 - accuracy: 0.5691 - val_loss: 0.9779 - val_accuracy: 0.6403
Epoch 3/5
92/92 [==============================] - 3s 27ms/step - loss: 0.9903 - accuracy: 0.6015 - val_loss: 0.9722 - val_accuracy: 0.6485
Epoch 4/5
92/92 [==============================] - 3s 26ms/step - loss: 0.9320 - accuracy: 0.6209 - val_loss: 0.9687 - val_accuracy: 0.6403
Epoch 5/5
92/92 [==============================] - 3s 26ms/step - loss: 0.8999 - accuracy: 0.6413 - val_loss: 0.9872 - val_accuracy: 0.6076
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)
12/12 [==============================] - 1s 13ms/step - loss: 0.9761 - accuracy: 0.5640
Accuracy 0.5640326738357544

การเพิ่มข้อมูลที่กำหนดเอง

คุณยังสามารถสร้างเลเยอร์การเพิ่มข้อมูลแบบกำหนดเองได้อีกด้วย บทแนะนำนี้แสดงให้เห็นสองวิธีในการดำเนินการดังกล่าว ขั้นแรกคุณจะสร้างเลเยอร์เลเยอร์ layers.Lambda นี่เป็นวิธีที่ดีในการเขียนโค้ดที่กระชับ จากนั้นคุณจะเขียนเลเยอร์ใหม่ผ่าน คลาสย่อย ซึ่งช่วยให้คุณควบคุมได้มากขึ้น เลเยอร์ทั้งสองจะสุ่มสลับสีในรูปภาพตามความน่าจะเป็น

def random_invert_img(x, p=0.5):
  if  tf.random.uniform([]) < p:
    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")

png

จากนั้นใช้เลเยอร์ที่กำหนดเองโดยการ คลาสย่อย

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.imshow(RandomInvert()(image)[0])

png

ทั้งสองเลเยอร์นี้สามารถใช้ได้ตามที่อธิบายไว้ในตัวเลือก 1 และ 2 ข้างต้น

ใช้ tf.image

ชั้นด้านบนยูทิลิตี้การ layers.preprocessing มีความสะดวก เพื่อการควบคุมที่ละเอียดยิ่งขึ้นคุณสามารถเขียนไปป์ไลน์หรือเลเยอร์การเพิ่มข้อมูลของคุณเองโดยใช้ tf.data และ tf.image คุณอาจต้องการตรวจสอบ TensorFlow Addons Image: Operations และ TensorFlow I / O: Color Space Conversions

เนื่องจากก่อนหน้านี้ชุดข้อมูลดอกไม้ได้รับการกำหนดค่าด้วยการเพิ่มข้อมูลเราจึงนำเข้ามาใหม่เพื่อเริ่มต้นใหม่

(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))

png

ลองใช้ฟังก์ชั่นต่อไปนี้เพื่อแสดงภาพและเปรียบเทียบภาพต้นฉบับและภาพเสริมแบบเคียงข้างกัน

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)

การเพิ่มข้อมูล

พลิกภาพ

พลิกภาพในแนวตั้งหรือแนวนอน

flipped = tf.image.flip_left_right(image)
visualize(image, flipped)

png

ภาพสีเทา

ภาพสีเทา

grayscaled = tf.image.rgb_to_grayscale(image)
visualize(image, tf.squeeze(grayscaled))
_ = plt.colorbar()

png

ทำให้ภาพอิ่มตัว

ทำให้ภาพอิ่มตัวโดยระบุปัจจัยความอิ่มตัว

saturated = tf.image.adjust_saturation(image, 3)
visualize(image, saturated)

png

เปลี่ยนความสว่างของภาพ

เปลี่ยนความสว่างของภาพโดยให้ปัจจัยด้านความสว่าง

bright = tf.image.adjust_brightness(image, 0.4)
visualize(image, bright)

png

ครอบตัดรูปภาพตรงกลาง

ครอบตัดรูปภาพจากกึ่งกลางขึ้นไปยังส่วนของรูปภาพที่คุณต้องการ

cropped = tf.image.central_crop(image, central_fraction=0.5)
visualize(image,cropped)

png

หมุนภาพ

หมุนภาพ 90 องศา

rotated = tf.image.rot90(image)
visualize(image, rotated)

png

การแปลงแบบสุ่ม

การใช้การแปลงแบบสุ่มกับรูปภาพสามารถช่วยอธิบายและขยายชุดข้อมูลได้มากขึ้น tf.image API ปัจจุบันมีการดำเนินการอิมเมจแบบสุ่ม 8 รูปแบบ (ops):

ตัวเลือกรูปภาพแบบสุ่มเหล่านี้ใช้งานได้อย่างหมดจด: ouput ขึ้นอยู่กับอินพุตเท่านั้น ทำให้ง่ายต่อการใช้งานในท่ออินพุตที่กำหนดประสิทธิภาพสูง พวกเขาต้องการค่า seed จะป้อนในแต่ละขั้นตอน เมื่อได้ seed เดียวกันพวกมันจะให้ผลลัพธ์เหมือนกันโดยไม่ขึ้นกับจำนวนครั้งที่เรียก

ในส่วนต่อไปนี้เราจะ:

  1. ดูตัวอย่างการใช้การดำเนินการรูปภาพแบบสุ่มเพื่อแปลงรูปภาพและ
  2. สาธิตวิธีใช้การแปลงแบบสุ่มกับชุดข้อมูลการฝึกอบรม

เปลี่ยนความสว่างของภาพแบบสุ่ม

เปลี่ยนความสว่างของ image แบบสุ่มโดยให้ปัจจัยด้านความสว่างและ seed ปัจจัยความสว่างจะถูกเลือกแบบสุ่มในช่วง [-max_delta, max_delta) และสัมพันธ์กับ seed กำหนด

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)

png

png

png

เปลี่ยนความคมชัดของภาพแบบสุ่ม

เปลี่ยนความเปรียบต่างของ image แบบสุ่มโดยระบุช่วงคอนทราสต์และ seed ช่วงคอนทราสต์จะถูกเลือกแบบสุ่มในช่วงเวลา [lower, upper] และสัมพันธ์กับ seed กำหนด

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)

png

png

png

ครอบตัดรูปภาพแบบสุ่ม

ครอบตัด image แบบสุ่มโดยระบุ size และ seed เป้าหมาย ส่วนที่ถูกครอบตัดออกจาก image จะอยู่ที่ออฟเซ็ตที่เลือกแบบสุ่มและเชื่อมโยงกับ seed กำหนด

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)

png

png

png

ใช้การเสริมกับชุดข้อมูล

ก่อนอื่นให้ดาวน์โหลดชุดข้อมูลรูปภาพอีกครั้งในกรณีที่มีการแก้ไขในส่วนก่อนหน้า

(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

เรามากำหนดฟังก์ชั่นการ augment ที่สามารถใช้การแปลงแบบสุ่มกับรูปภาพได้ ฟังก์ชันนี้จะใช้กับชุดข้อมูลในขั้นตอนถัดไป

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

ตัวเลือกที่ 1: การใช้ tf.data.experimental.Counter()

สร้าง tf.data.experimental.Counter() (เรียกว่า counter ) และ zip ชุดข้อมูลด้วย (counter, counter) สิ่งนี้จะช่วยให้มั่นใจได้ว่าแต่ละภาพในชุดข้อมูลจะเชื่อมโยงกับค่าที่ไม่ซ้ำกัน (ของรูปร่าง (2,) ) ตามตัว counter ซึ่งต่อมาสามารถส่งผ่านไปยังฟังก์ชันการ augment เป็นค่า seed สำหรับการแปลงแบบสุ่ม

# Create counter and zip together with train dataset
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)
)

ทางเลือกที่ 2: การใช้ tf.random.Generator

สร้างออบเจ็กต์ tf.random.Generator ด้วยค่า intial seed การเรียกใช้ฟังก์ชัน make_seeds บนอ็อบเจ็กต์ตัวสร้างเดียวกันจะส่งคืนค่า seed ใหม่ที่ไม่ซ้ำกันเสมอ กำหนดฟังก์ชัน wrapper ที่ 1) เรียกใช้ฟังก์ชัน make_seeds และ 2) ส่งผ่านค่า seed สร้างขึ้นใหม่ไปยังฟังก์ชัน augment สำหรับการแปลงแบบสุ่ม

# Create a generator
rng = tf.random.Generator.from_seed(123, alg='philox')
# 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

แมปฟังก์ชัน wrapper f กับชุดข้อมูลการฝึกอบรม

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 Preprocessing Layers และ tf.image หากต้องการเรียนรู้วิธีรวมเลเยอร์ก่อนการประมวลผลไว้ในโมเดลของคุณโปรดดูบทแนะนำการ จัดหมวดหมู่รูปภาพ คุณอาจสนใจที่จะเรียนรู้ว่าเลเยอร์ก่อนการประมวลผลสามารถช่วยคุณจัดประเภทข้อความได้อย่างไรดังที่แสดงในบทแนะนำการ จัดประเภทข้อความพื้นฐาน คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ tf.data ใน คู่มือ นี้และคุณสามารถเรียนรู้วิธีกำหนดค่าไปป์ไลน์อินพุตของคุณเพื่อประสิทธิภาพได้ ที่นี่