In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.
В этом руководстве приведен простой пример загрузки датасета изображений с использованием tf.data
.
Набор данных, используемый в этом примере, распространяется в виде папок изображений, с одним классом изображений в каждой папке.
In [ ]:
import tensorflow as tf
In [ ]:
AUTOTUNE = tf.data.experimental.AUTOTUNE
In [ ]:
import pathlib
data_root_orig = tf.keras.utils.get_file(origin='https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
fname='flower_photos', untar=True)
data_root = pathlib.Path(data_root_orig)
print(data_root)
После скачивания 218MB, вам теперь доступна копия изображений цветов:
In [ ]:
for item in data_root.iterdir():
print(item)
In [ ]:
import random
all_image_paths = list(data_root.glob('*/*'))
all_image_paths = [str(path) for path in all_image_paths]
random.shuffle(all_image_paths)
image_count = len(all_image_paths)
image_count
In [ ]:
all_image_paths[:10]
In [ ]:
import os
attributions = (data_root/"LICENSE.txt").open(encoding='utf-8').readlines()[4:]
attributions = [line.split(' CC-BY') for line in attributions]
attributions = dict(attributions)
In [ ]:
import IPython.display as display
def caption_image(image_path):
image_rel = pathlib.Path(image_path).relative_to(data_root)
return "Image (CC BY 2.0) " + ' - '.join(attributions[str(image_rel)].split(' - ')[:-1])
In [ ]:
for n in range(3):
image_path = random.choice(all_image_paths)
display.display(display.Image(image_path))
print(caption_image(image_path))
print()
Выведите на экран все доступные метки:
In [ ]:
label_names = sorted(item.name for item in data_root.glob('*/') if item.is_dir())
label_names
Присвойте индекс каждой метке:
In [ ]:
label_to_index = dict((name, index) for index, name in enumerate(label_names))
label_to_index
Создайте список всех файлов и индексов их меток:
In [ ]:
all_image_labels = [label_to_index[pathlib.Path(path).parent.name]
for path in all_image_paths]
print("First 10 labels indices: ", all_image_labels[:10])
TensorFlow включает все инструменты которые вам могут понадобиться для загрузки и обработки изображений:
In [ ]:
img_path = all_image_paths[0]
img_path
Вот сырые данные:
In [ ]:
img_raw = tf.io.read_file(img_path)
print(repr(img_raw)[:100]+"...")
Преобразуйте ее в тензор изображения:
In [ ]:
img_tensor = tf.image.decode_image(img_raw)
print(img_tensor.shape)
print(img_tensor.dtype)
Измените ее размер для вашей модели:
In [ ]:
img_final = tf.image.resize(img_tensor, [192, 192])
img_final = img_final/255.0
print(img_final.shape)
print(img_final.numpy().min())
print(img_final.numpy().max())
Оберните их в простые функции для будущего использования.
In [ ]:
def preprocess_image(image):
image = tf.image.decode_jpeg(image, channels=3)
image = tf.image.resize(image, [192, 192])
image /= 255.0 # normalize to [0,1] range
return image
In [ ]:
def load_and_preprocess_image(path):
image = tf.io.read_file(path)
return preprocess_image(image)
In [ ]:
import matplotlib.pyplot as plt
image_path = all_image_paths[0]
label = all_image_labels[0]
plt.imshow(load_and_preprocess_image(img_path))
plt.grid(False)
plt.xlabel(caption_image(img_path))
plt.title(label_names[label].title())
print()
Простейший способ построения tf.data.Dataset
это использование метода from_tensor_slices
.
Нарезка массива строк, приводит к датасету строк:
In [ ]:
path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)
Параметры shapes
и types
описывают содержимое каждого элемента датасета. В этом случае у нас множество скалярных двоичных строк
In [ ]:
print(path_ds)
Сейчас создадим новый датасет который загружает и форматирует изображения на лету пройдясь с preprocess_image
по датасету с путями к файлам.
In [ ]:
image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)
In [ ]:
import matplotlib.pyplot as plt
plt.figure(figsize=(8,8))
for n, image in enumerate(image_ds.take(4)):
plt.subplot(2,2,n+1)
plt.imshow(image)
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.xlabel(caption_image(all_image_paths[n]))
plt.show()
Используя тот же метод from_tensor_slices
вы можете собрать датасет меток:
In [ ]:
label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels, tf.int64))
In [ ]:
for label in label_ds.take(10):
print(label_names[label.numpy()])
Поскольку датасеты следуют в одном и том же порядке, вы можете просто собрать их вместе при помощи функции zip в набор данных пары (image, label)
:
In [ ]:
image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))
Shapes
и types
нового датасета это кортежи размерностей и типов описывающие каждое поле:
In [ ]:
print(image_label_ds)
Примечание. Если у вас есть такие массивы, как «all_image_labels» и «all_image_paths», альтернативой «tf.data.dataset.Dataset.zip» является срез (slice) пары массивов.
In [ ]:
ds = tf.data.Dataset.from_tensor_slices((all_image_paths, all_image_labels))
# Кортежи распаковываются в позиционные аргументы отображаемой функции
def load_and_preprocess_from_path_label(path, label):
return load_and_preprocess_image(path), label
image_label_ds = ds.map(load_and_preprocess_from_path_label)
image_label_ds
Для обучения модели на этом датасете, вам необходимо, чтобы данные:
Эти свойства могут быть легко добавлены с помощью tf.data
api.
In [ ]:
BATCH_SIZE = 32
# Установка размера буфера перемешивания, равного набору данных, гарантирует
# полное перемешивание данных.
ds = image_label_ds.shuffle(buffer_size=image_count)
ds = ds.repeat()
ds = ds.batch(BATCH_SIZE)
# `prefetch` позволяет датасету извлекать пакеты в фоновом режиме, во время обучения модели.
ds = ds.prefetch(buffer_size=AUTOTUNE)
ds
Здесь необходимо отметить несколько вещей:
Важна последовательность действий.
.shuffle
после .repeat
перемешает элементы вне границ эпох (некоторые элементы будут увидены дважды в то время как другие ни разу)..shuffle
после .batch
перемешает порядок пакетов, но не перемешает элементы внутри пакета.Используйте buffer_size
такого же размера как и датасет для полного перемешивания. Вплоть до размера набора данных, большие значения обеспечивают лучшую рандомизацию, но используют больше памяти.
Из буфера перемешивания не выбрасываются элементы пока он не заполнится. Поэтому большой размер buffer_size
может стать причиной задержки при запуске Dataset
.
Перемешанный датасет не сообщает об окончании, пока буфер перемешивания полностью не опустеет. Dataset
перезапускается с помощью.repeat
, вызывая еще одно ожидание заполнения буфера перемешивания.
Последний пункт может быть решен использованием метода tf.data.Dataset.apply
вместе со слитой функцией tf.data.experimental.shuffle_and_repeat
:
In [ ]:
ds = image_label_ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE)
ds = ds.prefetch(buffer_size=AUTOTUNE)
ds
In [ ]:
mobile_net = tf.keras.applications.MobileNetV2(input_shape=(192, 192, 3), include_top=False)
mobile_net.trainable=False
Эта модель предполагает нормализацию входных данных в диапазоне [-1,1]
:
help(keras_applications.mobilenet_v2.preprocess_input)
... Эта функция применяет препроцессинг "Inception" который преобразует RGB значения из [0, 255] в [-1, 1] ...
Перед передачей входных данных в модель MobilNet, вам нужно конвертировать их из диапазона [0,1]
в [-1,1]
:
In [ ]:
def change_range(image,label):
return 2*image-1, label
keras_ds = ds.map(change_range)
MobileNet возвращает 6x6
сетку признаков для каждого изображения.
Передайте ей пакет изображений чтобы увидеть:
In [ ]:
# Датасету может понадобиться несколько секунд для старта пока заполняется буфер перемешивания.
image_batch, label_batch = next(iter(keras_ds))
In [ ]:
feature_map_batch = mobile_net(image_batch)
print(feature_map_batch.shape)
Постройте модель обернутую вокруг MobileNet и используйте tf.keras.layers.GlobalAveragePooling2D
для усреднения по этим размерностям пространства перед выходным слоем tf.keras.layers.Dense
:
In [ ]:
model = tf.keras.Sequential([
mobile_net,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(len(label_names), activation = 'softmax')])
Сейчас он производит выходные данные ожидаемых размеров:
In [ ]:
logit_batch = model(image_batch).numpy()
print("min logit:", logit_batch.min())
print("max logit:", logit_batch.max())
print()
print("Shape:", logit_batch.shape)
Скомпилируйте модель чтобы описать процесс обучения:
In [ ]:
model.compile(optimizer=tf.keras.optimizers.Adam(),
loss='sparse_categorical_crossentropy',
metrics=["accuracy"])
Есть две переменные для обучения - Dense weights
и bias
:
In [ ]:
len(model.trainable_variables)
In [ ]:
model.summary()
Вы готовы обучать модель.
Отметим, что для демонстрационных целей вы запустите только 3 шага на эпоху, но обычно вам нужно указать действительное число шагов, как определено ниже, перед передачей их в model.fit()
:
In [ ]:
steps_per_epoch=tf.math.ceil(len(all_image_paths)/BATCH_SIZE).numpy()
steps_per_epoch
In [ ]:
model.fit(ds, epochs=1, steps_per_epoch=3)
Примечание: Этот раздел лишь показывает пару простых приемов которые могут помочь производительности. Для более глубокого изучения см. Производительность входного конвейера.
Простые конвейеры, использованные выше, прочитывают каждый файл отдельно во время каждой эпохи. Это подходит для локального обучения на CPU, может быть недостаточно для обучения на GPU и абсолютно неприемлемо для любого вида распределенного обучения.
Чтобы исследовать производительность наших датасетов, сперва постройте простую функцию:
In [ ]:
import time
default_timeit_steps = 2*steps_per_epoch+1
def timeit(ds, steps=default_timeit_steps):
overall_start = time.time()
# Выберем один пакет для передачи в пайплайн (заполнение буфера перемешивания),
# перед запуском таймера
it = iter(ds.take(steps+1))
next(it)
start = time.time()
for i,(images,labels) in enumerate(it):
if i%10 == 0:
print('.',end='')
print()
end = time.time()
duration = end-start
print("{} batches: {} s".format(steps, duration))
print("{:0.5f} Images/s".format(BATCH_SIZE*steps/duration))
print("Total time: {}s".format(end-overall_start))
Производительность данного датасета равна:
In [ ]:
ds = image_label_ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)
ds
In [ ]:
timeit(ds)
Используйте tf.data.Dataset.cache, чтобы с легкостью кэшировать вычисления от эпохи к эпохе. Это особенно эффективно если данные помещаются в память.
Здесь изображения кэшируются после предварительной обработки (перекодирования и изменения размера):
In [ ]:
ds = image_label_ds.cache()
ds = ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)
ds
In [ ]:
timeit(ds)
Одним из недостатков использования кэша памяти является то, что кэш должен перестраиваться при каждом запуске, давая одинаковую начальную задержку при каждом запуске датасета:
In [ ]:
timeit(ds)
Если данные не помещаются в памяти, используйте файл кэша:
In [ ]:
ds = image_label_ds.cache(filename='./cache.tf-data')
ds = ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds = ds.batch(BATCH_SIZE).prefetch(1)
ds
In [ ]:
timeit(ds)
Также у файла кеша есть преимущество использования быстрого перезапуска датасета без перестраивания кеша. Обратите внимание, насколько быстрее это происходит во второй раз:
In [ ]:
timeit(ds)
TFRecord файлы - это простой формат для хранения двоичных блобов (blob). Упаковывая несколько примеров в один файл, TensorFlow может читать несколько элементов за раз, что особенно важно для производительности особенно при использовании удаленного сервиса хранения, такого как GCS.
Сперва построим файл TFRecord из необработанных данных изображений:
In [ ]:
image_ds = tf.data.Dataset.from_tensor_slices(all_image_paths).map(tf.io.read_file)
tfrec = tf.data.experimental.TFRecordWriter('images.tfrec')
tfrec.write(image_ds)
Затем построим датасет, который прочитывает файл TFRecord и обрабатывает изображения с использованием функции preprocess_image
, которую вы задали ранее:
In [ ]:
image_ds = tf.data.TFRecordDataset('images.tfrec').map(preprocess_image)
Объедините этот датасет с датасетом меток, который вы определили ранее, чтобы получить пару из (image,label)
:
In [ ]:
ds = tf.data.Dataset.zip((image_ds, label_ds))
ds = ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds=ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)
ds
In [ ]:
timeit(ds)
Это медленнее cache
версии, поскольку обработанные изображения не кешируются.
Чтобы сохранить некоторый препроцессинг в файл TFRecord сперва, как и ранее, создайте датасет обработанных изображений:
In [ ]:
paths_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)
image_ds = paths_ds.map(load_and_preprocess_image)
image_ds
Сейчас вместо датасета строк .jpeg
, у вас датасет тензоров.
Чтобы сериализовать это в файл TFRecord сперва сконвертируйте датасет тензоров в датасет строк:
In [ ]:
ds = image_ds.map(tf.io.serialize_tensor)
ds
In [ ]:
tfrec = tf.data.experimental.TFRecordWriter('images.tfrec')
tfrec.write(ds)
С кешированным препроцессингом данные могут быть выгружены из TFrecord файла очень эффективно - не забудьте только десериализовать тензор перед использованием:
In [ ]:
ds = tf.data.TFRecordDataset('images.tfrec')
def parse(x):
result = tf.io.parse_tensor(x, out_type=tf.float32)
result = tf.reshape(result, [192, 192, 3])
return result
ds = ds.map(parse, num_parallel_calls=AUTOTUNE)
ds
Сейчас добавьте метки и примените те же стандартные операции, что и ранее:
In [ ]:
ds = tf.data.Dataset.zip((ds, label_ds))
ds = ds.apply(
tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
ds=ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)
ds
In [ ]:
timeit(ds)