Помогают защитить Большой Барьерный Риф с TensorFlow на Kaggle Присоединяйтесь вызов

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

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

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

tf.data API представляет 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 объекта. Например, можно применить за элемент преобразования , такие как 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 .

Конструкции Python , которые могут быть использованы для выражения (вложенная) структуры элементов включают tuple , dict , NamedTuple и OrderedDict . В частности, list не является допустимым конструкт для выражения структуры набора данных элементов. Это происходит потому , что первые пользователи tf.data чувствовали сильно о list входов (например , передается tf.data.Dataset.from_tensors ) , которые автоматически упаковывается как тензоры и list результатов (например , возвращаемых значений функций , определяемых пользователем) принуждают в tuple . Как следствие, если вы хотите получить list ввода следует рассматривать как структуру, вы должны преобразовать его в tuple , и если вы хотите получить list вывода , чтобы быть одним компонентом, то вам нужно явно упаковать его , используя tf.stack .

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())
[3 1 4 9 2 5 1 3 1 1]
[4 8 6 1 5 2 4 6 2 4]
[6 4 7 7 6 7 8 9 2 5]
[3 6 3 8 2 2 3 8 1 6]
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
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/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
4431872/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 конструктор преобразует генератор питона в полностью функциональную 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 аргумент не требуется , но очень рекомендуется , как многие операции tensorflow не поддерживают тензоры с неизвестным рангом. Если длина конкретной оси неизвестна или переменный, установить его в качестве 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 : [-1.2659 -0.0164  0.8822  0.9887 -0.5352 -1.1873  0.6209]
1 : [-0.492  -2.3205 -0.5924 -0.2635  0.6136  1.9545  1.7315  0.4508]
2 : [0.2972]
3 : [ 0.2433  0.0502 -1.0612 -0.1499 -0.0939  0.5694  0.2207]
4 : [-1.8696 -0.4797]
5 : [ 0.3438 -0.4853 -0.36  ]
6 : [-0.9741]

Первый выход является int32 второй является float32 .

Первый элемент является скаляром, форма () , а второй вектор неизвестной длины, формы (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())
[ 5  7 10  4  2 13 25 18 17 27]

[[ 0.8387  0.2497  1.7194  0.      0.      0.      0.      0.      0.    ]
 [-2.3122 -1.1238 -1.3219 -0.0437 -0.0156 -1.5395  1.0858  0.      0.    ]
 [-0.8438  0.6191 -0.9143 -0.0623  0.1734 -0.5583  0.8883  0.      0.    ]
 [-0.955   0.3145  0.4488  0.6816  0.      0.      0.      0.      0.    ]
 [ 0.3774  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.0288  0.5001 -1.4579  0.576  -0.8185  0.1437  0.8146 -0.9032  0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.7994 -0.8724 -0.4017  0.      0.      0.      0.      0.      0.    ]
 [-0.0224  0.9805  0.0618 -0.0507 -0.3333 -0.4813  0.7181  0.351   0.097 ]]

Для более реалистичного примера, попробуйте упаковка 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 [==============================] - 5s 0us/step
228827136/228813984 [==============================] - 5s 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(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

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

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

tf.data API поддерживает различные форматы файлов , так что вы можете обрабатывать большие массивы данных , которые не помещаются в памяти. Например, формат файла TFRecord - это простой двоичный формат, ориентированный на записи, который многие приложения TensorFlow используют для обучающих данных. 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
7913472/7904079 [==============================] - 0s 0us/step

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

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

Многие проекты TensorFlow использовать сериализованное 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
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/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
40960/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 - файлов и Загрузка панды DataFrames для большего количества примеров.

Формат файла 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)
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': [1 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [45.  2. 22. 37.]
  'n_siblings_spouses': [0 0 0 1]
  'parch'             : [0 1 0 0]
  'fare'              : [ 8.05   12.2875  7.7958 26.    ]
  'class'             : [b'Third' b'Third' b'Third' b'Second']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'y' b'n' b'y' 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 0 0 0]
  'fare'              : [30.      7.0542  8.05   79.2   ]
  'class'             : [b'First' b'Third' b'Third' b'First']

Существует также более низкого уровня experimental.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 дает каждый столбец каждой строки файла, который не может быть желательным, например , если файл начинается со строки заголовка , который должен быть проигнорирован, или если некоторые столбцы не требуется на входе. Эти линии и поля могут быть удалены с 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/daisy/2045022175_ad087f5f60_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/19425920580_cdc8f49aed_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/160456948_38c3817c6a_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/17012955700_7141d29eee.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/4933823300_39fd4420b6.jpg'

Считывание данных с использованием 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'dandelion'

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

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

Простая форма дозирования стеки 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 результата в неизвестном размере партии , так как последняя партия не могут быть полными. Обратите внимание на None S в виде:

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.

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

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

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

Самый простой способ итерации набора данных в нескольких эпохах является использование 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 даст партии , что автоконтейнеровозов границ эпохи:

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())
[ 83  24   4  14   6  53  37   2  18  71  27  40  77  76 110  11  30  47
  46  28]

Как 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:

[612 458 485 439 305 506 624 570 625 523]
[529 544 597 620 610 615 592 487 566 454]
[611 572 585   0 520 601 398 594]
[ 65  22  75  78  73  55 105  86   8  94]
[ 14  52   4  36  85  66  69 100  38  33]
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 0x7fcea41e1a50>

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:

[593 580 524 619 506 555 617 526  24 520]
[586 445 356  26 600  20  16 528 504  39]
[583 594 576  37 560  13 626 314  41 501]
[571  46 574 542  32  52   4  10  40  35]
[590 541  34 625  56 609 529 446 618  50]
[ 64 611 582 538 443  72 604  23 613  79]
[ 36  14  17 557   2  59 622  25 395 565]
[400 483 588  89  51 599  67 591  12 458]
[ 69  33 373 101  29  98 102 468 104  94]
[548  21  68  30  61  49 581 513  44 360]
[ 75 121  97 603  58  62 577  15 452  80]
[426  48  65  90 105 616 124 100  66 134]
[ 99  74 620 136  82 587  88  96  31 127]
[138  87 151 131 143 471  86 140 146 117]
[  0 514 119 130  45   8   9 110 107  55]
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 0x7fcdd84fd5d0>

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.io.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)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

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 Протос снаружи от 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],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

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 = 3

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

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

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

Используя window

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

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=' ')
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 набор данных первым:

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 [==============================] - 1s 0us/step
69165056/69155632 [==============================] - 1s 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.9973 0.0027]

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

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

Один из подходов к передискретизации набора данных является использование 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())
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)
WARNING:tensorflow:From /tmp/ipykernel_11206/929671245.py:2: sample_from_datasets_v2 (from tensorflow.python.data.experimental.ops.interleave_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.sample_from_datasets(...)`.

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

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 1 0 1 0 1 0 0 1 1]
[0 0 0 0 0 0 1 1 1 0]
[0 1 1 0 1 0 0 0 0 1]
[1 1 1 0 0 0 0 0 0 1]
[0 1 1 1 1 1 1 1 1 0]
[0 0 1 0 0 1 1 1 1 1]
[1 1 1 1 1 0 1 1 1 0]
[0 0 0 0 0 0 0 1 1 0]
[0 1 1 1 0 1 1 1 1 0]
[0 1 0 1 0 1 0 1 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)
WARNING:tensorflow:From /tmp/ipykernel_11206/2462412449.py:2: rejection_resample (from tensorflow.python.data.experimental.ops.resampling) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.rejection_resample(...)`.

В Resampler дела с отдельными примерами, так что вы должны unbatch набора данных перед нанесением Resampler:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py:6097: 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())
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
Proportion of examples rejected by sampler is high: [0.997265637][0.997265637 0.00273437495][0 1]
[1 1 1 0 1 1 1 0 1 1]
[0 1 1 0 1 1 0 0 1 0]
[1 0 1 1 0 1 0 0 0 1]
[0 1 0 1 0 1 1 0 0 0]
[0 0 0 1 0 1 1 0 0 0]
[1 0 1 0 0 1 1 1 1 0]
[0 1 1 1 1 1 0 1 0 0]
[1 1 1 0 0 1 1 1 0 0]
[0 1 1 0 1 0 1 0 0 0]
[0 0 1 0 1 1 1 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

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

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
1875/1875 [==============================] - 4s 2ms/step - loss: 0.5979 - accuracy: 0.7986
Epoch 2/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4610 - accuracy: 0.8429
<keras.callbacks.History at 0x7fce40120590>

Если передать бесконечный набор данных, например , по телефону 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.4489 - accuracy: 0.8484
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4426 - accuracy: 0.8625
<keras.callbacks.History at 0x7fce4029cfd0>

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4352 - accuracy: 0.8513
Loss : 0.4351990818977356
Accuracy : 0.8513000011444092

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4348 - accuracy: 0.8750
Loss : 0.43478837609291077
Accuracy : 0.875

Этикетки не требуется при вызове 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)