Эта страница была переведа с помощью 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 . Например, вы можете применять преобразования для каждого элемента, такие как 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())
 
[8 1 2 6 1 7 2 6 1 3]
[6 5 6 5 3 5 2 5 3 6]
[5 8 4 8 3 1 4 6 4 8]
[2 4 5 8 3 5 7 9 4 2]

 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 [==============================] - 0s 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 является генератор питонов.

 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 не является обязательным, но настоятельно рекомендуется, так как многие операции tenorflow не поддерживают тензоры с неизвестным рангом. Если длина конкретной оси неизвестна или является переменной, установите ее как 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.978  -1.0531  0.1959 -2.1618]
1 : [ 1.9185 -0.1874  0.5084]
2 : [0.1441 0.3987 0.7737 0.9266 1.5057 0.9151]
3 : [ 0.681  -0.6155 -0.1231 -0.2429  0.6892  1.2571 -1.7588 -1.6575 -0.5375]
4 : [-0.5567  1.5298  0.7242  0.2213]
5 : [ 1.5572 -0.6856]
6 : [-1.0965 -0.336   1.2405  0.6006]

Первый вывод - 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())
 
[ 2 10 18  3  6 15 25 23  0  4]

[[ 1.2665 -0.6274  0.4076  1.0146  0.      0.      0.      0.    ]
 [ 0.8091 -0.0683 -0.1464  0.2734  0.7461 -0.1009  0.      0.    ]
 [-0.9381  1.5075  0.      0.      0.      0.      0.      0.    ]
 [ 1.5705  0.4438  0.      0.      0.      0.      0.      0.    ]
 [-0.4692 -1.8328 -2.2838  0.7418  0.0172 -0.3547 -1.4502 -1.2786]
 [-1.574   0.      0.      0.      0.      0.      0.      0.    ]
 [-0.9274  1.4758  0.      0.      0.      0.      0.      0.    ]
 [-0.5043  0.7066  0.9599 -1.2986  0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.4893 -0.6937  0.      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 [==============================] - 2s 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 позволяет вам передавать содержимое одного или нескольких файлов 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 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, index_col=None)
df.head()
 

Если ваши данные Dataset.from_tensor_slices в память, тот же метод 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 функция.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 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [28. 42. 43. 21.]
  'n_siblings_spouses': [0 0 0 0]
  'parch'             : [0 0 0 0]
  'fare'              : [47.1    13.      8.05    8.6625]
  'class'             : [b'First' b'Second' b'Third' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Southampton' b'Southampton']
  'alone'             : [b'y' b'y' b'y' b'y']

Вы можете использовать аргумент 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 1 1]
  'fare'              : [24.15    0.     13.8583 53.1   ]
  'class'             : [b'Third' b'Second' b'Second' b'First']

Существует также experimental.CsvDataset класс более низкого уровня. 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/dandelion/7243478942_30bf542a2d_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/4525067924_177ea3bfb4.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/7002703410_3e97b29da5_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/6299910262_336309ffa5_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/6140661443_bb48344226.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\x01\x00H\x00H\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'tulips'

Элементы набора данных

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

Простейшая форма пакетирования объединяет 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.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.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())
 
[ 63  48 101  12 103  52   6  39   4   9  93  91   5  86  79  64  95  33
 102  50]

Как и в случае с 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:

[613 609 624 553 608 583 493 617 611 610]
[217 508 579 601 319 616 606 549 618 623]
[416 567 404 622 283 458 503 602]
[ 87  68  56  16   6  62   1  89  58 106]
[98 80 43 10 67 44 19 34 13 57]

 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 0x7fe0a00a1d68>

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:

[440  15   8 599 567  18 550   5  19  17]
[ 12 501 571 473 466  21 531 596 580 555]
[  3 573  38 563  25 416 595  29  46 602]
[485 566 561  16 331 615 386  28 609  41]
[611 622 575  10 589  61 598 527  52  35]
[ 55 597  42  23  13  47  11 505  68 582]
[612 613  75  43   7 392  74 452  82 509]
[  9  44  62 491  71 343  51 590  60  98]
[  6  95 619  86 625 537 617  85 465   0]
[ 88  27  92 101 109 111 104  24  36 113]
[103 118  79  53  70  40 121 100  65  33]
[562 588 124 125  64  84  83  67 610 130]
[  4 142 131  90 518 129 143 112   2 551]
[377  91 140  76  50  48 526 553 156 591]
[105 128  69 114  93 520 154  56 145 115]

 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 0x7fe0582fbb70>

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)
 
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 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.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 0x7fe0582dbbf8> 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 0x7fe0582dbbf8> 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 [==============================] - 10s 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.996 0.004]

Обычный подход к обучению с использованием несбалансированного набора данных - это сбалансировать его. 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())
 
WARNING:tensorflow:AutoGraph could not transform <function <lambda> at 0x7fe0a01fd1e0> 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 0x7fe0a01fd1e0> 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 0x7fe058159620> 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 0x7fe058159620> 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())
 
[0 0 0 1 0 0 1 1 0 0]
[1 1 0 0 1 1 0 0 0 1]
[1 1 0 0 0 0 0 0 1 1]
[1 0 0 1 1 0 0 0 1 0]
[1 1 0 0 0 1 1 0 0 1]
[0 0 1 1 1 0 0 1 1 0]
[0 0 0 1 0 0 0 1 1 1]
[1 1 0 1 0 0 1 0 1 1]
[0 1 0 0 1 1 0 0 0 1]
[0 1 0 0 1 0 0 1 1 0]

Отказ от повторной выборки

Одна из проблем, связанных с вышеупомянутым experimental.sample_from_datasets подходом.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 набор данных перед применением ресэмплера:

 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:


Ресамплер возвращает создаваемые (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())
 
[1 0 1 1 1 1 0 1 1 1]
[0 0 1 1 1 0 1 0 1 1]
[1 0 0 1 0 0 0 0 0 1]
[1 1 1 1 1 1 0 1 1 1]
[1 1 0 1 0 0 0 0 1 0]
[1 0 0 0 1 0 1 0 1 0]
[0 0 0 0 0 0 1 0 0 0]
[0 0 0 1 1 0 0 1 0 1]
[0 0 1 1 1 1 0 0 1 1]
[0 0 1 1 0 1 0 1 1 0]

Проверка итератора

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]

Использование высокоуровневых API

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 it's 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 [==============================] - 4s 2ms/step - loss: 0.6031 - accuracy: 0.7937
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4620 - accuracy: 0.8416

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

Если вы передаете бесконечный набор данных, например, вызывая 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.4050 - accuracy: 0.8672
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4077 - accuracy: 0.8703

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

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

 loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
 
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4474 - accuracy: 0.8439
Loss : 0.4474281072616577
Accuracy : 0.843916654586792

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

 loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
 
10/10 [==============================] - 0s 2ms/step - loss: 0.5262 - accuracy: 0.8156
Loss : 0.5262183547019958
Accuracy : 0.815625011920929

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

tf.estimator

Чтобы использовать Dataset в input_fn tf.estimator.Estimator , просто верните Dataset из input_fn и фреймворк позаботится о потреблении его элементов за вас. Например:

 import tensorflow_datasets as tfds

def train_input_fn():
  titanic = tf.data.experimental.make_csv_dataset(
      titanic_file, batch_size=32,
      label_name="survived")
  titanic_batches = (
      titanic.cache().repeat().shuffle(500)
      .prefetch(tf.data.experimental.AUTOTUNE))
  return titanic_batches
 
 embark = tf.feature_column.categorical_column_with_hash_bucket('embark_town', 32)
cls = tf.feature_column.categorical_column_with_vocabulary_list('class', ['First', 'Second', 'Third']) 
age = tf.feature_column.numeric_column('age')
 
 import tempfile
model_dir = tempfile.mkdtemp()
model = tf.estimator.LinearClassifier(
    model_dir=model_dir,
    feature_columns=[embark, cls, age],
    n_classes=2
)
 
INFO:tensorflow:Using default config.
INFO:tensorflow:Using config: {'_model_dir': '/tmp/tmpefmfuc4o', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_experimental_max_worker_delay_secs': None, '_session_creation_timeout_secs': 7200, '_service': None, '_cluster_spec': ClusterSpec({}), '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}

 model = model.train(input_fn=train_input_fn, steps=100)
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1666: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/training_util.py:236: Variable.initialized_value (from tensorflow.python.ops.variables) is deprecated and will be removed in a future version.
Instructions for updating:
Use Variable.read_value. Variables in 2.X are initialized automatically both in eager and graph (inside tf.defun) contexts.
INFO:tensorflow:Calling model_fn.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/feature_column/feature_column_v2.py:540: Layer.add_variable (from tensorflow.python.keras.engine.base_layer_v1) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `layer.add_weight` method instead.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/optimizer_v2/ftrl.py:144: calling Constant.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 0...
INFO:tensorflow:Saving checkpoints for 0 into /tmp/tmpefmfuc4o/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 0...
INFO:tensorflow:loss = 0.6931472, step = 0
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 100...
INFO:tensorflow:Saving checkpoints for 100 into /tmp/tmpefmfuc4o/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 100...
INFO:tensorflow:Loss for final step: 0.58668363.

 result = model.evaluate(train_input_fn, steps=10)

for key, value in result.items():
  print(key, ":", value)
 
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2020-07-23T01:23:29Z
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmpefmfuc4o/model.ckpt-100
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Evaluation [1/10]
INFO:tensorflow:Evaluation [2/10]
INFO:tensorflow:Evaluation [3/10]
INFO:tensorflow:Evaluation [4/10]
INFO:tensorflow:Evaluation [5/10]
INFO:tensorflow:Evaluation [6/10]
INFO:tensorflow:Evaluation [7/10]
INFO:tensorflow:Evaluation [8/10]
INFO:tensorflow:Evaluation [9/10]
INFO:tensorflow:Evaluation [10/10]
INFO:tensorflow:Inference Time : 0.83507s
INFO:tensorflow:Finished evaluation at 2020-07-23-01:23:30
INFO:tensorflow:Saving dict for global step 100: accuracy = 0.675, accuracy_baseline = 0.58125, auc = 0.71750116, auc_precision_recall = 0.6480325, average_loss = 0.64111984, global_step = 100, label/mean = 0.41875, loss = 0.64111984, precision = 0.85714287, prediction/mean = 0.30204886, recall = 0.26865673
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 100: /tmp/tmpefmfuc4o/model.ckpt-100
accuracy : 0.675
accuracy_baseline : 0.58125
auc : 0.71750116
auc_precision_recall : 0.6480325
average_loss : 0.64111984
label/mean : 0.41875
loss : 0.64111984
precision : 0.85714287
prediction/mean : 0.30204886
recall : 0.26865673
global_step : 100

 for pred in model.predict(train_input_fn):
  for key, value in pred.items():
    print(key, ":", value)
  break
 
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmpefmfuc4o/model.ckpt-100
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
logits : [-0.5965]
logistic : [0.3551]
probabilities : [0.6449 0.3551]
class_ids : [0]
classes : [b'0']
all_class_ids : [0 1]
all_classes : [b'0' b'1']