Эта страница была переведа с помощью Cloud Translation API.
Switch to English

tf.data: сборка входных конвейеров TensorFlow

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

API tf.data позволяет создавать сложные конвейеры ввода из простых, повторно используемых частей. Например, конвейер для модели изображения может агрегировать данные из файлов в распределенной файловой системе, применять случайные возмущения к каждому изображению и объединять случайно выбранные изображения в пакет для обучения. Конвейер для текстовой модели может включать извлечение символов из необработанных текстовых данных, преобразование их во встраиваемые идентификаторы с помощью таблицы поиска и группирование последовательностей разной длины. API tf.data позволяет обрабатывать большие объемы данных, читать из различных форматов данных и выполнять сложные преобразования.

API tf.data представляет абстракцию tf.data.Dataset которая представляет последовательность элементов, в которой каждый элемент состоит из одного или нескольких компонентов. Например, в конвейере изображений элемент может быть единственным обучающим примером с парой компонентов тензора, представляющих изображение и его метку.

Есть два разных способа создать набор данных:

  • Источник данных создает Dataset из данных , хранящихся в памяти или в одном или нескольких файлах.

  • Преобразование данных создает набор данных из одного или нескольких объектов tf.data.Dataset .

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

Базовая механика

Чтобы создать конвейер ввода, вы должны начать с источника данных. Например, чтобы создать Dataset из данных в памяти, вы можете использовать tf.data.Dataset.from_tensors() или tf.data.Dataset.from_tensor_slices() . В качестве альтернативы, если ваши входные данные хранятся в файле в рекомендуемом формате TFRecord, вы можете использовать tf.data.TFRecordDataset() .

Когда у вас есть объект Dataset , вы можете преобразовать его в новый Dataset , tf.data.Dataset вызовы tf.data.Dataset объекте tf.data.Dataset . Например, вы можете применять преобразования для каждого элемента, такие как Dataset.map() , и многоэлементные преобразования, такие как Dataset.batch() . См. Документацию для tf.data.Dataset для получения полного списка преобразований.

Объект Dataset является итерируемым Python. Это позволяет использовать его элементы с помощью цикла for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

Или явно создав итератор Python с помощью iter и потребляя его элементы, используя next :

it = iter(dataset)

print(next(it).numpy())
8

В качестве альтернативы элементы набора данных можно использовать с помощью преобразования reduce , которое сокращает все элементы для получения единого результата. В следующем примере показано, как использовать преобразование reduce для вычисления суммы набора данных целых чисел.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

Структура набора данных

Набор данных содержит элементы, каждый из которых имеет одинаковую (вложенную) структуру, и отдельные компоненты структуры могут быть любого типа, представленного tf.TypeSpec , включая tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray , или tf.data.Dataset .

Свойство Dataset.element_spec позволяет вам проверять тип каждого компонента элемента. Свойство возвращает вложенную структуру объектов tf.TypeSpec , соответствующую структуре элемента, которая может быть отдельным компонентом, кортежем компонентов или вложенным кортежем компонентов. Например:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

Преобразования наборов Dataset поддерживают наборы данных любой структуры. При использовании Dataset.map() и Dataset.filter() , которые применяют функцию к каждому элементу, структура элемента определяет аргументы функции:

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[6 4 2 6 4 7 8 5 2 3]
[3 6 2 3 8 6 1 2 3 4]
[8 4 4 2 7 3 1 7 7 8]
[9 9 3 7 9 1 9 1 1 7]

dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

Чтение входных данных

Использование массивов NumPy

См. Дополнительные примеры в разделе Загрузка массивов NumPy .

Если все ваши входные данные умещаются в памяти, самый простой способ создать из них Dataset - преобразовать их в объекты tf.Tensor и использовать Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/5148 [===============================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step

images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

Использование генераторов Python

Еще один распространенный источник данных, который можно легко принять как tf.data.Dataset - это генератор Python.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Конструктор Dataset.from_generator преобразует генератор Python в полнофункциональный tf.data.Dataset .

Конструктор принимает в качестве входных данных вызываемый объект, а не итератор. Это позволяет перезапустить генератор, когда он достигнет конца. Он принимает необязательный аргумент args , который передается как аргументы вызываемого объекта.

Аргумент output_types является обязательным, поскольку tf.data строит tf.Graph внутри, а ребрам графа требуется tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

Аргумент output_shapes не требуется, но настоятельно рекомендуется, поскольку многие операции тензорного потока не поддерживают тензоры с неизвестным рангом. Если длина конкретной оси неизвестна или переменная, установите для нее значение None в output_shapes .

Также важно отметить, что output_shapes и output_types следуют тем же правилам вложения, что и другие методы набора данных.

Вот пример генератора, который демонстрирует оба аспекта, он возвращает кортежи массивов, где второй массив - это вектор с неизвестной длиной.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [-0.119  -0.1705  0.6298  1.6472]
1 : [0.4102 0.858 ]
2 : [ 1.1845 -0.4004 -0.5682  0.7648 -2.0806  1.2242 -2.0966  0.8446]
3 : [-2.2548  1.4521  0.8092]
4 : [ 0.8081  0.7604 -0.6913  0.5043  1.0684]
5 : [-0.1358  0.3046  0.0346]
6 : []

Первый вывод - это int32 второй - float32 .

Первый элемент - это скаляр shape () , а второй - вектор неизвестной длины shape (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

Теперь его можно использовать как обычный tf.data.Dataset . Обратите внимание, что при пакетной обработке набора данных с переменной формой вам необходимо использовать Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[16 19 15  2  0 23 12  6 27 20]

[[ 1.7982  0.6075  0.8935  1.5496 -0.9427  0.9026 -1.0912  0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3835 -0.7199  0.6697 -0.5535 -1.2751  1.0727 -1.6975 -1.8727  0.6132]
 [-0.7542  2.4467  1.6744  0.6795  0.3545  0.4416  0.      0.      0.    ]
 [-1.1485  0.2186 -0.5212 -0.6324 -1.1361 -2.0172  0.      0.      0.    ]
 [ 0.3903  0.0763  0.1203 -1.5849  0.      0.      0.      0.      0.    ]
 [-0.361   0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3172 -0.3045 -0.4404  1.0413 -2.2203 -0.9137  0.      0.      0.    ]
 [ 0.1888  1.432   1.4547  0.5166 -0.2852  2.3501 -0.3476 -0.1323 -0.0585]
 [ 0.0677  0.5017  1.5934  0.5202  0.      0.      0.      0.      0.    ]]

Для более реалистичного примера попробуйте обернуть preprocessing.image.ImageDataGenerator как tf.data.Dataset .

Сначала загрузите данные:

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 3s 0us/step

Создайте image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.

print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)

ds = tf.data.Dataset.from_generator(
    img_gen.flow_from_directory, args=[flowers], 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds
<FlatMapDataset shapes: ((32, 256, 256, 3), (32, 5)), types: (tf.float32, tf.float32)>

Потребление данных TFRecord

См. « Загрузка TFRecords» для полного примера.

API tf.data поддерживает различные форматы файлов, поэтому вы можете обрабатывать большие наборы данных, не помещающиеся в памяти. Например, формат файла TFRecord - это простой двоичный формат, ориентированный на записи, который многие приложения TensorFlow используют для обучающих данных. Класс tf.data.TFRecordDataset позволяет выполнять tf.data.TFRecordDataset содержимого одного или нескольких файлов TFRecord как части входного конвейера.

Вот пример использования тестового файла из французских уличных указателей (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 0s 0us/step

Аргумент filenames для инициализатора TFRecordDataset может быть строкой, списком строк или tf.Tensor строк. Поэтому, если у вас есть два набора файлов для обучения и проверки, вы можете создать фабричный метод, который производит набор данных, принимая имена файлов в качестве входного аргумента:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

Многие проекты tf.train.Example используют сериализованные записи tf.train.Example в своих файлах TFRecord. Их необходимо декодировать, прежде чем их можно будет проверить:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

Потребление текстовых данных

См. Раздел Загрузка текста для полного примера.

Многие наборы данных распространяются в виде одного или нескольких текстовых файлов. tf.data.TextLineDataset предоставляет простой способ извлечения строк из одного или нескольких текстовых файлов. Учитывая одно или несколько имен файлов, TextLineDataset будет создавать по одному элементу со строковым значением для каждой строки этих файлов.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step

dataset = tf.data.TextLineDataset(file_paths)

Вот несколько первых строк первого файла:

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

Чтобы чередовать строки между файлами, используйте Dataset.interleave . Это упрощает перемешивание файлов вместе. Вот первая, вторая и третья строки из каждого перевода:

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())

b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

По умолчанию TextLineDataset выдает каждую строку каждого файла, что может быть нежелательно, например, если файл начинается со строки заголовка или содержит комментарии. Эти строки можно удалить с помощью Dataset.skip() или Dataset.filter() . Здесь вы пропускаете первую строку, затем фильтруете, чтобы найти только выживших.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step

for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'

def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

Использование данных CSV

Дополнительные примеры см. В разделах Загрузка файлов CSV и Загрузка фреймов данных Pandas .

Формат файла CSV - популярный формат для хранения табличных данных в виде простого текста.

Например:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file, index_col=None)
df.head()

Если ваши данные умещаются в памяти, тот же метод Dataset.from_tensor_slices работает со словарями, позволяя легко импортировать эти данные:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
  'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

Более масштабируемый подход - загрузка с диска по мере необходимости.

Модуль tf.data предоставляет методы для извлечения записей из одного или нескольких файлов CSV, соответствующих RFC 4180 .

Функция experimental.make_csv_dataset - это высокоуровневый интерфейс для чтения наборов файлов csv. Он поддерживает вывод типа столбца и многие другие функции, такие как пакетирование и перемешивание, чтобы упростить использование.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 0 0 1]
features:
  'sex'               : [b'male' b'male' b'female' b'male']
  'age'               : [30. 28. 28. 36.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [0 0 2 1]
  'fare'              : [ 13.       7.8958   7.75   512.3292]
  'class'             : [b'Second' b'Third' b'Third' b'First']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'B']
  'embark_town'       : [b'Southampton' b'Southampton' b'Queenstown' b'Cherbourg']
  'alone'             : [b'y' b'y' b'n' b'n']

Вы можете использовать аргумент select_columns если вам нужно только подмножество столбцов.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 1 0 0]
  'fare'              : [ 7.925 32.5   13.     8.05 ]
  'class'             : [b'Third' b'Second' b'Second' b'Third']

Существует также низкоуровневый experimental.CsvDataset класс .CsvDataset, который обеспечивает более детальный контроль. Он не поддерживает вывод типа столбца. Вместо этого вы должны указать тип каждого столбца.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

Если некоторые столбцы пусты, этот низкоуровневый интерфейс позволяет вам указать значения по умолчанию вместо типов столбцов.

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv

# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

По умолчанию CsvDataset выдает каждый столбец каждой строки файла, что может быть нежелательно, например, если файл начинается со строки заголовка, которую следует игнорировать, или если некоторые столбцы не требуются во входных данных. Эти строки и поля можно удалить с select_cols аргументов header и select_cols соответственно.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

Потребление наборов файлов

Существует множество наборов данных, распространенных в виде наборов файлов, каждый из которых является примером.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

Корневой каталог содержит каталог для каждого класса:

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

Файлы в каждом каталоге классов являются примерами:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4746648726_e37a2de16d_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/2862944799_45bc8e7302.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13513644515_a51470b899.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/506018088_4f7a15a7c5_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/15381511376_fd743b7330_n.jpg'

Прочтите данные с tf.io.read_file функции tf.io.read_file и извлеките метку из пути, возвращая пары (image, label) :

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'sunflowers'

Пакетирование элементов набора данных

Простое дозирование

В простейшей форме пакетирования n последовательных элементов набора данных складываются в один элемент. Преобразование Dataset.batch() делает именно это с теми же ограничениями, что и оператор tf.stack() , применяемым к каждому компоненту элементов: т.е. для каждого компонента i все элементы должны иметь тензор точно такой же формы.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

Хотя tf.data пытается распространить информацию о форме, настройки по умолчанию для Dataset.batch приводят к неизвестному размеру пакета, поскольку последний пакет может быть Dataset.batch . Обратите внимание на None в форме:

batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

Используйте аргумент drop_remainder чтобы игнорировать этот последний пакет и получить полное распространение формы:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

Пакетные тензоры с заполнением

Приведенный выше рецепт работает для тензоров одинакового размера. Однако многие модели (например, модели последовательностей) работают с входными данными, которые могут иметь различный размер (например, последовательности разной длины). Чтобы справиться с этим случаем, преобразование Dataset.padded_batch позволяет вам группировать тензоры различной формы, задав одно или несколько измерений, в которых они могут быть дополнены.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()

[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]


Преобразование Dataset.padded_batch позволяет вам устанавливать разные отступы для каждого измерения каждого компонента, и оно может быть переменной длины (обозначено None в приведенном выше примере) или постоянной длины. Также можно изменить значение заполнения, которое по умолчанию равно 0.

Рабочие процессы обучения

Обработка нескольких эпох

API tf.data предлагает два основных способа обработки одних и тех же данных за несколько эпох.

Самый простой способ перебрать набор данных в несколько эпох - использовать преобразование Dataset.repeat() . Сначала создайте набор титанических данных:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

При применении преобразования Dataset.repeat() без аргументов ввод будет повторяться бесконечно.

Преобразование Dataset.repeat объединяет свои аргументы, не сигнализируя о конце одной эпохи и начале следующей эпохи. Из-за этого Dataset.batch применяемый после Dataset.repeat , даст пакеты, которые Dataset.repeat границы эпох:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

PNG

Если вам нужно четкое разделение эпох, поместите Dataset.batch перед повтором:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

PNG

Если вы хотите выполнить пользовательское вычисление (например, для сбора статистики) в конце каждой эпохи, то проще всего перезапустить итерацию набора данных в каждую эпоху:

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

Случайное перемешивание входных данных

Преобразование Dataset.shuffle() поддерживает буфер фиксированного размера и выбирает следующий элемент равномерно случайным образом из этого буфера.

Добавьте индекс к набору данных, чтобы увидеть эффект:

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Поскольку buffer_size равен 100, а размер пакета равен 20, первый пакет не содержит элементов с индексом более 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[  6  41  35  53  36  50  52  80   1  26  22  29  38  55  84  18  47  68
 100 105]

Как и в случае с Dataset.batch порядок относительно Dataset.repeat имеет значение.

Dataset.shuffle не сигнализирует об окончании эпохи до тех пор, пока буфер перемешивания не станет пустым. Таким образом, перемешивание, помещенное перед повтором, будет показывать каждый элемент одной эпохи перед переходом к следующей:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[594 456 585 615 536 627 514 297 617 620]
[495 530 356 622 395 602 505 593 611 492]
[573 597 575 528 561 616 609 494]
[51 85 41  4 37  9 91 74 89  8]
[ 13 107 104  65  52  83  71  16 101  92]

shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fda80007be0>

PNG

Но повтор перед перемешиванием смешивает границы эпох:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[570 263 533  22  17  14 485 348 517 606]
[501 488 620 611 624 599 534 398 542 521]
[446 596 359 587   6 475 436 610   2 605]
[ 35 583  49  16 547  46  39 576  47   8]
[ 30  26  63  55 496  51   9 575  66 571]
[ 41  57  33 589  43 550 540 404 591 595]
[ 12 512  48 581 600  25  67  19  85  56]
[ 83  65 563 597  52  61  90 585  91  78]
[ 11  37 445 621 618 482  92 103  82  32]
[ 42  79  62  88 543  80  95 598 539 113]
[ 38  86 107 124  50 114  76 535   3  27]
[111  64  71 130  29 129 470 120  94 101]
[425 122 131  18  40  84  87  73 519  21]
[272  58 143 145 331  77  60 612 127  13]
[495  96  99 523 138 627 115 167 135 152]

repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fda545b3908>

PNG

Предварительная обработка данных

Преобразование Dataset.map(f) создает новый набор данных, применяя заданную функцию f к каждому элементу входного набора данных. Он основан на функции map() которая обычно применяется к спискам (и другим структурам) в языках функционального программирования. Функция f принимает объекты tf.Tensor которые представляют один элемент во входных данных, и возвращает объекты tf.Tensor которые будут представлять один элемент в новом наборе данных. Его реализация использует стандартные операции TensorFlow для преобразования одного элемента в другой.

В этом разделе приведены общие примеры использования Dataset.map() .

Расшифровка данных изображения и изменение их размера

При обучении нейронной сети на реальных данных изображения часто необходимо преобразовать изображения разных размеров в общий размер, чтобы их можно было объединить в фиксированный размер.

Восстановите набор данных с именами файлов цветов:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

Напишите функцию, которая управляет элементами набора данных.

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.image.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

Проверьте, что это работает.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

PNG

Сопоставьте его с набором данных.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

PNG

PNG

Применение произвольной логики Python

По соображениям производительности по возможности используйте операции TensorFlow для предварительной обработки данных. Однако иногда бывает полезно вызывать внешние библиотеки Python при анализе ваших входных данных. Вы можете использовать tf.py_function() операции в Dataset.map() преобразования.

Например, если вы хотите применить случайное вращение, то tf.image модуль имеет только tf.image.rot90 , что не очень полезно для увеличения изображения.

Чтобы продемонстрировать tf.py_function , попробуйте вместо этого использовать функцию scipy.ndimage.rotate :

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)

PNG

Чтобы использовать эту функцию с Dataset.map те же предостережения, что и в случае с Dataset.from_generator , вам необходимо описать формы и типы возвращаемых данных при применении функции:

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

PNG

PNG

Разбор tf.Example сообщений буфера протокола

Многие входные конвейеры извлекают сообщения буфера протокола tf.train.Example из формата TFRecord. Каждая запись tf.train.Example содержит одну или несколько «функций», и входной конвейер обычно преобразует эти функции в тензоры.

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

Вы можете работать с tf.train.Example protos вне tf.data.Dataset чтобы понять данные:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

PNG

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...

decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

Окно временных рядов

Пример сквозного временного ряда см. В разделе «Прогнозирование временного ряда» .

Данные временных рядов часто организованы без изменения оси времени.

Используйте простой Dataset.range чтобы продемонстрировать:

range_ds = tf.data.Dataset.range(100000)

Как правило, для моделей, основанных на данных такого рода, требуется непрерывный временной интервал.

Самый простой подход - это пакетирование данных:

Использование batch

batches = range_ds.batch(10, drop_remainder=True)

for batch in batches.take(5):
  print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

Или, чтобы делать точные прогнозы на один шаг в будущее, вы можете сдвинуть признаки и метки на один шаг относительно друг друга:

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

Чтобы прогнозировать все окно вместо фиксированного смещения, вы можете разделить партии на две части:

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Take the first 5 steps
          batch[-5:])   # take the remainder

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

Чтобы обеспечить некоторое перекрытие между функциями одного пакета и метками другого, используйте Dataset.zip :

feature_length = 10
label_length = 5

features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:-5])

predict_5_steps = tf.data.Dataset.zip((features, labels))

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22 23 24]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32 33 34]

Использование window

Хотя использование Dataset.batch работает, бывают ситуации, когда вам может потребоваться более тонкий контроль. Метод Dataset.window дает вам полный контроль, но требует некоторой осторожности: он возвращает Dataset Datasets . См. Подробности в структуре набора данных.

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

Метод Dataset.flat_map может взять набор данных из наборов данных и объединить его в один набор данных:

 for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fda544008c8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7fda544008c8> and will run it as-is.
Cause: could not parse the source code:

for x in windows.flat_map(lambda x: x).take(30):

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 

Почти во всех случаях вам может понадобиться .batch выполнить .batch набора данных:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

Теперь вы можете видеть, что аргумент shift контролирует, на сколько перемещается каждое окно.

Собрав все это вместе, вы можете написать эту функцию:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows

ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

Затем, как и прежде, легко извлечь метки:

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

Повторная выборка

При работе с набором данных, который очень несбалансирован по классам, может потребоваться повторная выборка набора данных. tf.data предоставляет для этого два метода. Набор данных о мошенничестве с кредитными картами - хороший пример такого рода проблем.

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 3s 0us/step

creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

Теперь проверьте распределение классов, оно сильно искажено:

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9964 0.0036]

Распространенный подход к обучению с несбалансированным набором данных - сбалансировать его. tf.data включает в себя несколько методов, обеспечивающих этот рабочий процесс:

Выборка наборов данных

Один из подходов к повторной sample_from_datasets набора данных - использовать sample_from_datasets . Это более применимо, когда у вас есть отдельный data.Dataset для каждого класса.

Здесь просто используйте фильтр, чтобы сгенерировать их из данных о мошенничестве с кредитными картами:

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fda54400400> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7fda54400400> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==0)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fda54440400> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert
WARNING: AutoGraph could not transform <function <lambda> at 0x7fda54440400> and will run it as-is.
Cause: could not parse the source code:

    .filter(lambda features, label: label==1)

This error may be avoided by creating the lambda in a standalone statement.

To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert

for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

Чтобы использовать tf.data.experimental.sample_from_datasets передайте наборы данных и вес для каждого:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

Теперь набор данных дает примеры каждого класса с вероятностью 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 0 1 0 0 0 1 1 0 1]
[0 0 1 1 1 0 0 0 1 1]
[0 0 1 1 1 0 1 0 1 1]
[1 0 0 0 0 1 1 1 1 1]
[0 1 0 0 0 0 1 1 0 1]
[1 0 1 1 0 0 1 0 0 0]
[0 0 1 0 1 0 0 1 0 0]
[0 1 1 0 1 1 1 1 0 1]
[1 1 1 0 1 1 0 0 0 0]
[0 0 1 1 0 1 0 0 0 1]

Отклонение повторной выборки

Одна из проблем , с выше experimental.sample_from_datasets подхода заключается в том, что она требует отдельного tf.data.Dataset каждого класса. Использование Dataset.filter работает, но в результате все данные загружаются дважды.

data.experimental.rejection_resample можно применить к набору данных, чтобы сбалансировать его, при этом загружая его только один раз. Элементы будут удалены из набора данных для достижения баланса.

data.experimental.rejection_resample принимает аргумент class_func . Эта class_func применяется к каждому элементу набора данных и используется для определения того, к какому классу принадлежит пример, в целях балансировки.

Элементы creditcard_ds уже являются парами (features, label) . Поэтому class_func просто нужно вернуть эти метки:

def class_func(features, label):
  return label

Ресамплеру также необходимо целевое распределение и, возможно, начальная оценка распределения:

resampler = tf.data.experimental.rejection_resample(
    class_func, target_dist=[0.5, 0.5], initial_dist=fractions)

Ресамплер работает с отдельными примерами, поэтому перед применением unbatch вы должны unbatch набор данных:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:


Resampler возвращает пары (class, example) из вывода class_func . В этом случае example уже был парой (feature, label) , поэтому используйте map чтобы удалить дополнительную копию меток:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

Теперь набор данных дает примеры каждого класса с вероятностью 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[0 1 1 1 1 0 1 0 1 0]
[0 1 0 1 0 0 1 1 0 1]
[0 1 1 1 0 0 0 0 0 0]
[1 0 1 1 0 0 0 0 0 0]
[0 0 1 1 0 1 1 1 0 1]
[1 0 0 0 0 1 1 1 0 0]
[0 1 0 1 1 1 0 1 1 1]
[0 1 0 0 0 0 0 1 0 0]
[1 1 0 1 0 0 1 1 1 1]
[0 0 0 0 1 0 0 1 0 1]

Контрольные точки итератора

Tensorflow поддерживает выполнение контрольных точек, чтобы при перезапуске тренировочного процесса он мог восстановить последнюю контрольную точку для восстановления большей части своего прогресса. В дополнение к контрольным точкам переменных модели вы также можете проверять выполнение итератора набора данных. Это может быть полезно, если у вас большой набор данных и вы не хотите запускать набор данных с самого начала при каждом перезапуске. Однако обратите внимание, что контрольные точки итератора могут быть большими, поскольку преобразования, такие как shuffle и prefetch требуют элементов буферизации внутри итератора.

Чтобы включить ваш итератор в контрольную точку, передайте итератор в конструктор tf.train.Checkpoint .

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

Использование tf.data с tf.keras

API tf.keras упрощает многие аспекты создания и выполнения моделей машинного обучения. Его .fit() и .evaluate() и .predict() поддерживают наборы данных в качестве входных данных. Вот быстрый набор данных и настройка модели:

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

Передача набора данных пар (feature, label) - это все, что необходимо для Model.fit и Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
WARNING:tensorflow:Layer flatten is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because its dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

1875/1875 [==============================] - 3s 2ms/step - loss: 0.6033 - accuracy: 0.7961
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4633 - accuracy: 0.8411

<tensorflow.python.keras.callbacks.History at 0x7fdad007f320>

Если вы передаете бесконечный набор данных, например, вызывая Dataset.repeat() , вам просто нужно также передать аргумент steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4781 - accuracy: 0.8313
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4285 - accuracy: 0.8516

<tensorflow.python.keras.callbacks.History at 0x7fda5443cfd0>

Для оценки вы можете пройти несколько этапов оценки:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 3s 1ms/step - loss: 0.4449 - accuracy: 0.8468
Loss : 0.4448782205581665
Accuracy : 0.846750020980835

Для длинных наборов данных установите количество шагов для оценки:

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4509 - accuracy: 0.8438
Loss : 0.45086926221847534
Accuracy : 0.84375

При вызове Model.predict метки не требуются.

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

Но метки игнорируются, если вы передаете набор данных, содержащий их:

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)