Загрузить данные CSV

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

В этом руководстве представлены примеры использования данных CSV с TensorFlow.

Это состоит из двух основных частей:

  1. Загрузка данных с диска
  2. Предварительная обработка его в форму, пригодную для тренировок.

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

Настраивать

import pandas as pd
import numpy as np

# Make numpy values easier to read.
np.set_printoptions(precision=3, suppress=True)

import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.layers.experimental import preprocessing

В данных памяти

Для любого небольшого набора данных CSV самый простой способ обучить на нем модель TensorFlow - это загрузить ее в память как фрейм данных pandas или массив NumPy.

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

  • Набор данных невелик.
  • Все входные функции представляют собой значения с плавающей запятой ограниченного диапазона.

Вот как загрузить данные в DataFrame Pandas :

abalone_train = pd.read_csv(
    "https://storage.googleapis.com/download.tensorflow.org/data/abalone_train.csv",
    names=["Length", "Diameter", "Height", "Whole weight", "Shucked weight",
           "Viscera weight", "Shell weight", "Age"])

abalone_train.head()

Набор данных содержит набор измерений морского морского ушка , разновидности морской улитки.

раковина морского ушка

«Панцирь морского ушка» (автор: Ники Дуган Пог , CC BY-SA 2.0)

Номинальная задача для этого набора данных - предсказать возраст по другим измерениям, поэтому разделите функции и метки для обучения:

abalone_features = abalone_train.copy()
abalone_labels = abalone_features.pop('Age')

Для этого набора данных вы будете рассматривать все объекты одинаково. Упакуйте функции в один массив NumPy .:

abalone_features = np.array(abalone_features)
abalone_features
array([[0.435, 0.335, 0.11 , ..., 0.136, 0.077, 0.097],
       [0.585, 0.45 , 0.125, ..., 0.354, 0.207, 0.225],
       [0.655, 0.51 , 0.16 , ..., 0.396, 0.282, 0.37 ],
       ...,
       [0.53 , 0.42 , 0.13 , ..., 0.374, 0.167, 0.249],
       [0.395, 0.315, 0.105, ..., 0.118, 0.091, 0.119],
       [0.45 , 0.355, 0.12 , ..., 0.115, 0.067, 0.16 ]])

Затем сделайте регрессионную модель для прогнозирования возраста. Поскольку существует только один входной тензор, keras.Sequential здесь достаточно keras.Sequential модели.

abalone_model = tf.keras.Sequential([
  layers.Dense(64),
  layers.Dense(1)
])

abalone_model.compile(loss = tf.losses.MeanSquaredError(),
                      optimizer = tf.optimizers.Adam())

Чтобы обучить эту модель, передайте функции и метки в Model.fit :

abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 1s 2ms/step - loss: 68.1297
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 13.3981
Epoch 3/10
104/104 [==============================] - 0s 1ms/step - loss: 8.9458
Epoch 4/10
104/104 [==============================] - 0s 1ms/step - loss: 8.3894
Epoch 5/10
104/104 [==============================] - 0s 1ms/step - loss: 7.8835
Epoch 6/10
104/104 [==============================] - 0s 1ms/step - loss: 7.4897
Epoch 7/10
104/104 [==============================] - 0s 1ms/step - loss: 7.1716
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 6.9468
Epoch 9/10
104/104 [==============================] - 0s 1ms/step - loss: 6.7714
Epoch 10/10
104/104 [==============================] - 0s 1ms/step - loss: 6.6458
<tensorflow.python.keras.callbacks.History at 0x7f7bf0178110>

Вы только что познакомились с простейшим способом обучения модели с использованием данных CSV. Далее вы узнаете, как применить предварительную обработку для нормализации числовых столбцов.

Базовая предварительная обработка

Хорошая практика - нормализовать входные данные для вашей модели. Слои experimental.preprocessing предоставляют удобный способ встроить эту нормализацию в вашу модель.

Слой будет предварительно вычислять среднее значение и дисперсию каждого столбца и использовать их для нормализации данных.

Сначала вы создаете слой:

normalize = preprocessing.Normalization()

Затем вы используете метод Normalization.adapt() для адаптации уровня нормализации к вашим данным.

normalize.adapt(abalone_features)

Затем используйте слой нормализации в своей модели:

norm_abalone_model = tf.keras.Sequential([
  normalize,
  layers.Dense(64),
  layers.Dense(1)
])

norm_abalone_model.compile(loss = tf.losses.MeanSquaredError(),
                           optimizer = tf.optimizers.Adam())

norm_abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 0s 2ms/step - loss: 91.9882
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 52.3517
Epoch 3/10
104/104 [==============================] - 0s 1ms/step - loss: 16.0901
Epoch 4/10
104/104 [==============================] - 0s 1ms/step - loss: 5.8372
Epoch 5/10
104/104 [==============================] - 0s 1ms/step - loss: 5.0929
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 5.0442
Epoch 7/10
104/104 [==============================] - 0s 1ms/step - loss: 5.0062
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9882
Epoch 9/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9629
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9666
<tensorflow.python.keras.callbacks.History at 0x7f7be008f910>

Смешанные типы данных

Набор данных «Титаник» содержит информацию о пассажирах на «Титанике». Номинальная задача этого набора данных - предсказать, кто выжил.

Титаник

Изображение из Викимедиа

Необработанные данные можно легко загрузить как DataFrame Pandas, но их нельзя сразу использовать в качестве входных данных для модели TensorFlow.

titanic = pd.read_csv("https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic.head()
titanic_features = titanic.copy()
titanic_labels = titanic_features.pop('survived')

Из-за разных типов данных и диапазонов вы не можете просто сложить функции в массив NumPy и передать его модели keras.Sequential . Каждый столбец нужно обрабатывать индивидуально.

В качестве одного из вариантов вы можете предварительно обработать данные в автономном режиме (используя любой инструмент, который вам нравится), чтобы преобразовать категориальные столбцы в числовые столбцы, а затем передать обработанный вывод в вашу модель TensorFlow. Недостатком этого подхода является то, что если вы сохраняете и экспортируете свою модель, предварительная обработка не сохраняется вместе с ней. Слои experimental.preprocessing Предварительной обработки позволяют избежать этой проблемы, поскольку они являются частью модели.

В этом примере вы создадите модель, которая реализует логику предварительной обработки с использованием функционального API Keras . Вы также можете сделать это путем создания подклассов .

Функциональный API оперирует «символическими» тензорами. Нормальные «нетерпеливые» тензоры имеют значение. В отличие от этих «символических» тензоров нет. Вместо этого они отслеживают, какие операции выполняются над ними, и создают представление расчета, которое вы можете выполнить позже. Вот небольшой пример:

# Create a symbolic input
input = tf.keras.Input(shape=(), dtype=tf.float32)

# Do a calculation using is
result = 2*input + 1

# the result doesn't have a value
result
<KerasTensor: shape=(None,) dtype=float32 (created by layer 'tf.__operators__.add')>
calc = tf.keras.Model(inputs=input, outputs=result)
print(calc(1).numpy())
print(calc(2).numpy())
3.0
5.0

Чтобы построить модель предварительной обработки, начните с создания набора символических объектов keras.Input , соответствующих именам и типам данных столбцов CSV.

inputs = {}

for name, column in titanic_features.items():
  dtype = column.dtype
  if dtype == object:
    dtype = tf.string
  else:
    dtype = tf.float32

  inputs[name] = tf.keras.Input(shape=(1,), name=name, dtype=dtype)

inputs
{'sex': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'sex')>,
 'age': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'age')>,
 'n_siblings_spouses': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'n_siblings_spouses')>,
 'parch': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'parch')>,
 'fare': <KerasTensor: shape=(None, 1) dtype=float32 (created by layer 'fare')>,
 'class': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'class')>,
 'deck': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'deck')>,
 'embark_town': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'embark_town')>,
 'alone': <KerasTensor: shape=(None, 1) dtype=string (created by layer 'alone')>}

Первый шаг в вашей логике предварительной обработки - объединить числовые входные данные вместе и пропустить их через уровень нормализации:

numeric_inputs = {name:input for name,input in inputs.items()
                  if input.dtype==tf.float32}

x = layers.Concatenate()(list(numeric_inputs.values()))
norm = preprocessing.Normalization()
norm.adapt(np.array(titanic[numeric_inputs.keys()]))
all_numeric_inputs = norm(x)

all_numeric_inputs
<KerasTensor: shape=(None, 4) dtype=float32 (created by layer 'normalization_1')>

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

preprocessed_inputs = [all_numeric_inputs]

Для строковых входов используйте функцию preprocessing.StringLookup чтобы преобразовать строки в целочисленные индексы в словаре. Затем используйте preprocessing.CategoryEncoding чтобы преобразовать индексы в данные float32 соответствующие модели.

Настройки по умолчанию для слоя preprocessing.CategoryEncoding создают один горячий вектор для каждого ввода. layers.Embedding также layers.Embedding . См. Руководство и руководство по предварительной обработке слоев для получения дополнительной информации по этой теме.

for name, input in inputs.items():
  if input.dtype == tf.float32:
    continue

  lookup = preprocessing.StringLookup(vocabulary=np.unique(titanic_features[name]))
  one_hot = preprocessing.CategoryEncoding(max_tokens=lookup.vocab_size())

  x = lookup(input)
  x = one_hot(x)
  preprocessed_inputs.append(x)
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.
WARNING:tensorflow:vocab_size is deprecated, please use vocabulary_size.
WARNING:tensorflow:max_tokens is deprecated, please use num_tokens instead.

С помощью набора inputs и processed_inputs вы можете объединить все предварительно обработанные входные данные вместе и построить модель, которая обрабатывает предварительную обработку:

preprocessed_inputs_cat = layers.Concatenate()(preprocessed_inputs)

titanic_preprocessing = tf.keras.Model(inputs, preprocessed_inputs_cat)

tf.keras.utils.plot_model(model = titanic_preprocessing , rankdir="LR", dpi=72, show_shapes=True)

PNG

Эта model просто содержит предварительную обработку ввода. Вы можете запустить его, чтобы увидеть, что он делает с вашими данными. Модели DataFrames не конвертируют Pandas DataFrames автоматически, потому что неясно, следует ли преобразовывать их в один тензор или в словарь тензоров. Так что преобразуйте его в словарь тензоров:

titanic_features_dict = {name: np.array(value) 
                         for name, value in titanic_features.items()}

Вырежьте первый обучающий пример и передайте его этой модели предварительной обработки, вы увидите, что числовые функции и строковые однозначные числа объединены вместе:

features_dict = {name:values[:1] for name, values in titanic_features_dict.items()}
titanic_preprocessing(features_dict)
<tf.Tensor: shape=(1, 33), dtype=float32, numpy=
array([[-0.61 ,  0.395, -0.479, -0.497,  0.   ,  0.   ,  0.   ,  1.   ,

         0.   ,  0.   ,  0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,
         0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  0.   ,  1.   ,  0.   ,
         0.   ,  0.   ,  0.   ,  1.   ,  0.   ,  0.   ,  0.   ,  1.   ,
         0.   ]], dtype=float32)>

Теперь построим модель поверх этого:

def titanic_model(preprocessing_head, inputs):
  body = tf.keras.Sequential([
    layers.Dense(64),
    layers.Dense(1)
  ])

  preprocessed_inputs = preprocessing_head(inputs)
  result = body(preprocessed_inputs)
  model = tf.keras.Model(inputs, result)

  model.compile(loss=tf.losses.BinaryCrossentropy(from_logits=True),
                optimizer=tf.optimizers.Adam())
  return model

titanic_model = titanic_model(titanic_preprocessing, inputs)

При обучении модели передайте словарь функций как x , а метку как y .

titanic_model.fit(x=titanic_features_dict, y=titanic_labels, epochs=10)
Epoch 1/10
20/20 [==============================] - 1s 3ms/step - loss: 0.5665
Epoch 2/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4980
Epoch 3/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4643
Epoch 4/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4463
Epoch 5/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4333
Epoch 6/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4297
Epoch 7/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4252
Epoch 8/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4234
Epoch 9/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4213
Epoch 10/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4202
<tensorflow.python.keras.callbacks.History at 0x7f7c8ff43510>

Поскольку предварительная обработка является частью модели, вы можете сохранить модель и перезагрузить ее где-нибудь еще и получить идентичные результаты:

titanic_model.save('test')
reloaded = tf.keras.models.load_model('test')
INFO:tensorflow:Assets written to: test/assets
features_dict = {name:values[:1] for name, values in titanic_features_dict.items()}

before = titanic_model(features_dict)
after = reloaded(features_dict)
assert (before-after)<1e-3
print(before)
print(after)
tf.Tensor([[-1.843]], shape=(1, 1), dtype=float32)
tf.Tensor([[-1.843]], shape=(1, 1), dtype=float32)

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

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

Если вам нужен больший контроль над конвейером входных данных или вам нужно использовать данные, которые не помещаются в память: используйте tf.data .

Дополнительные примеры см. В руководстве tf.data .

Включены данные в памяти

В качестве первого примера применения tf.data к данным CSV рассмотрим следующий код, чтобы вручную нарезать словарь функций из предыдущего раздела. Для каждого индекса требуется этот индекс для каждой функции:

import itertools

def slices(features):
  for i in itertools.count():
    # For each feature take index `i`
    example = {name:values[i] for name, values in features.items()}
    yield example

Запустите это и распечатайте первый пример:

for example in slices(titanic_features_dict):
  for name, value in example.items():
    print(f"{name:19s}: {value}")
  break
sex                : male
age                : 22.0
n_siblings_spouses : 1
parch              : 0
fare               : 7.25
class              : Third
deck               : unknown
embark_town        : Southampton
alone              : n

Самый простойtf.data.Dataset в загрузчике данных в памяти - это конструктор Dataset.from_tensor_slices . Это возвращаетtf.data.Dataset который реализует обобщенную версию вышеупомянутой функции slices в TensorFlow.

features_ds = tf.data.Dataset.from_tensor_slices(titanic_features_dict)

Вы можете перебиратьtf.data.Dataset как и любой другой итерируемый python:

for example in features_ds:
  for name, value in example.items():
    print(f"{name:19s}: {value}")
  break
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'

Функция from_tensor_slices может обрабатывать любую структуру вложенных словарей или кортежей. Следующий код создает набор данных из пар (features_dict, labels) :

titanic_ds = tf.data.Dataset.from_tensor_slices((titanic_features_dict, titanic_labels))

Чтобы обучить модель с использованием этого Dataset , вам нужно хотя бы shuffle и batch данные.

titanic_batches = titanic_ds.shuffle(len(titanic_labels)).batch(32)

Вместо передачи features и labels в Model.fit вы передаете набор данных:

titanic_model.fit(titanic_batches, epochs=5)
Epoch 1/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4199
Epoch 2/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4199
Epoch 3/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4192
Epoch 4/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4189
Epoch 5/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4185
<tensorflow.python.keras.callbacks.History at 0x7f7c8e8ee810>

Из одного файла

До сих пор это руководство работало с данными в памяти. tf.data - это хорошо масштабируемый инструментарий для построения конвейеров данных, который предоставляет несколько функций для загрузки файлов CSV.

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

Теперь прочтите данные CSV из файла и создайтеtf.data.Dataset .

(Полную документацию см. В tf.data.experimental.make_csv_dataset )

titanic_csv_ds = tf.data.experimental.make_csv_dataset(
    titanic_file_path,
    batch_size=5, # Artificially small to make examples easier to show.
    label_name='survived',
    num_epochs=1,
    ignore_errors=True,)

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

  • Использование заголовков столбцов в качестве ключей словаря.
  • Автоматическое определение типа каждого столбца.
for batch, label in titanic_csv_ds.take(1):
  for key, value in batch.items():
    print(f"{key:20s}: {value}")
  print()
  print(f"{'label':20s}: {label}")
sex                 : [b'male' b'female' b'male' b'male' b'male']
age                 : [28. 28. 70.  1. 28.]
n_siblings_spouses  : [1 0 1 5 1]
parch               : [0 0 1 2 0]
fare                : [82.171  7.225 71.    46.9   15.85 ]
class               : [b'First' b'Third' b'First' b'Third' b'Third']
deck                : [b'unknown' b'unknown' b'B' b'unknown' b'unknown']
embark_town         : [b'Cherbourg' b'Cherbourg' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'n' b'y' b'n' b'n' b'n']

label               : [0 1 0 0 0]

Он также может распаковывать данные на лету. Вот сжатый файл CSV, содержащий набор данных о межгосударственном трафике в метро.

Пробка.

Изображение из Викимедиа

traffic_volume_csv_gz = tf.keras.utils.get_file(
    'Metro_Interstate_Traffic_Volume.csv.gz', 
    "https://archive.ics.uci.edu/ml/machine-learning-databases/00492/Metro_Interstate_Traffic_Volume.csv.gz",
    cache_dir='.', cache_subdir='traffic')
Downloading data from https://archive.ics.uci.edu/ml/machine-learning-databases/00492/Metro_Interstate_Traffic_Volume.csv.gz
409600/405373 [==============================] - 1s 2us/step

Задайте аргумент compression_type для чтения непосредственно из сжатого файла:

traffic_volume_csv_gz_ds = tf.data.experimental.make_csv_dataset(
    traffic_volume_csv_gz,
    batch_size=256,
    label_name='traffic_volume',
    num_epochs=1,
    compression_type="GZIP")

for batch, label in traffic_volume_csv_gz_ds.take(1):
  for key, value in batch.items():
    print(f"{key:20s}: {value[:5]}")
  print()
  print(f"{'label':20s}: {label[:5]}")
holiday             : [b'None' b'None' b'None' b'None' b'None']
temp                : [275.36 264.13 265.53 278.63 289.91]
rain_1h             : [0.   0.   0.   0.   1.52]
snow_1h             : [0. 0. 0. 0. 0.]
clouds_all          : [90 90 75 90 80]
weather_main        : [b'Rain' b'Clouds' b'Clouds' b'Rain' b'Mist']
weather_description : [b'light rain' b'overcast clouds' b'broken clouds' b'light rain' b'mist']
date_time           : [b'2013-03-10 19:00:00' b'2013-01-02 19:00:00' b'2012-12-06 06:00:00'
 b'2013-04-25 18:00:00' b'2013-07-31 04:00:00']

label               : [2743 2687 5545 5020  822]

Кеширование

Есть некоторые накладные расходы на синтаксический анализ данных csv. Для небольших моделей это может быть узким местом в обучении.

В зависимости от вашего Dataset.cache использования может быть хорошей идеей использовать Dataset.cache или data.experimental.snapshot чтобы данные CSV анализировались только в первую эпоху.

Основное различие между методами cache и snapshot заключается в том, что файлы cache могут использоваться только процессом TensorFlow, который их создал, но файлы snapshot могут быть прочитаны другими процессами.

Например, повторение traffic_volume_csv_gz_ds 20 раз, занимает ~ 15 секунд без кеширования или ~ 2 секунды с кешированием.

%%time
for i, (batch, label) in enumerate(traffic_volume_csv_gz_ds.repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 14.9 s, sys: 3.58 s, total: 18.5 s
Wall time: 11 s
%%time
caching = traffic_volume_csv_gz_ds.cache().shuffle(1000)

for i, (batch, label) in enumerate(caching.shuffle(1000).repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 1.42 s, sys: 115 ms, total: 1.53 s
Wall time: 1.22 s
%%time
snapshot = tf.data.experimental.snapshot('titanic.tfsnap')
snapshotting = traffic_volume_csv_gz_ds.apply(snapshot).shuffle(1000)

for i, (batch, label) in enumerate(snapshotting.shuffle(1000).repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 2.26 s, sys: 431 ms, total: 2.69 s
Wall time: 1.62 s

Если загрузка данных замедляется из-за загрузки файлов CSV, а cache и snapshot недостаточно для вашего варианта использования, рассмотрите возможность перекодирования данных в более упрощенный формат.

Несколько файлов

Все примеры, приведенные до сих пор в этом разделе, можно было бы легко выполнить без tf.data . Одно место, где tf.data может действительно упростить работу, - это работа с коллекциями файлов.

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

Шрифты

Изображение Willi Heidelbach с сайта Pixabay

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

fonts_zip = tf.keras.utils.get_file(
    'fonts.zip',  "https://archive.ics.uci.edu/ml/machine-learning-databases/00417/fonts.zip",
    cache_dir='.', cache_subdir='fonts',
    extract=True)
Downloading data from https://archive.ics.uci.edu/ml/machine-learning-databases/00417/fonts.zip
160317440/160313983 [==============================] - 8s 0us/step
import pathlib
font_csvs =  sorted(str(p) for p in pathlib.Path('fonts').glob("*.csv"))

font_csvs[:10]
['fonts/AGENCY.csv',
 'fonts/ARIAL.csv',
 'fonts/BAITI.csv',
 'fonts/BANKGOTHIC.csv',
 'fonts/BASKERVILLE.csv',
 'fonts/BAUHAUS.csv',
 'fonts/BELL.csv',
 'fonts/BERLIN.csv',
 'fonts/BERNARD.csv',
 'fonts/BITSTREAMVERA.csv']
len(font_csvs)
153

При работе с кучей файлов вы можете передать file_pattern в стиле file_pattern в функцию experimental.make_csv_dataset . Порядок файлов меняется на каждой итерации.

Используйте аргумент num_parallel_reads чтобы установить, сколько файлов читается параллельно и чередуется вместе.

fonts_ds = tf.data.experimental.make_csv_dataset(
    file_pattern = "fonts/*.csv",
    batch_size=10, num_epochs=1,
    num_parallel_reads=20,
    shuffle_buffer_size=10000)

В этих файлах csv изображения выровнены в одну строку. Имена столбцов имеют формат r{row}c{column} . Вот первая партия:

for features in fonts_ds.take(1):
  for i, (name, value) in enumerate(features.items()):
    if i>15:
      break
    print(f"{name:20s}: {value}")
print('...')
print(f"[total: {len(features)} features]")
font                : [b'GLOUCESTER' b'REFERENCE' b'TREBUCHET' b'MONEY' b'GLOUCESTER' b'MONEY'
 b'GLOUCESTER' b'JUICE' b'CAMBRIA' b'BRUSH']
fontVariant         : [b'GLOUCESTER MT EXTRA CONDENSED' b'MS REFERENCE SANS SERIF'
 b'TREBUCHET MS' b'scanned' b'GLOUCESTER MT EXTRA CONDENSED' b'scanned'
 b'GLOUCESTER MT EXTRA CONDENSED' b'JUICE ITC' b'CAMBRIA'
 b'BRUSH SCRIPT MT']
m_label             : [  116 63521   507    53   402    54  8747   213 10766  8776]
strength            : [0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4]
italic              : [1 0 0 0 0 0 0 1 0 1]
orientation         : [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
m_top               : [41 21 23  0 31  0 22 20 25 44]
m_left              : [25 26 23  0 15  0 20 27 23 24]
originalH           : [42 68 64 32 63 27 68 61 73 25]
originalW           : [18 30 30 19 22 16 18 33 32 36]
h                   : [20 20 20 20 20 20 20 20 20 20]
w                   : [20 20 20 20 20 20 20 20 20 20]
r0c0                : [1 1 1 1 1 1 1 1 1 1]
r0c1                : [ 1  1  1 91  1  1  1  1  1  1]
r0c2                : [  1   1   1 239   1   1   1   1   1  40]
r0c3                : [  1   1   1 255   1   1   1   1   1  74]
...
[total: 412 features]

Необязательно: поля упаковки

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

Вот код, который анализирует имена столбцов для создания изображений для каждого примера:

import re

def make_images(features):
  image = [None]*400
  new_feats = {}

  for name, value in features.items():
    match = re.match('r(\d+)c(\d+)', name)
    if match:
      image[int(match.group(1))*20+int(match.group(2))] = value
    else:
      new_feats[name] = value

  image = tf.stack(image, axis=0)
  image = tf.reshape(image, [20, 20, -1])
  new_feats['image'] = image

  return new_feats

Примените эту функцию к каждому пакету в наборе данных:

fonts_image_ds = fonts_ds.map(make_images)

for features in fonts_image_ds.take(1):
  break

Постройте получившиеся изображения:

from matplotlib import pyplot as plt

plt.figure(figsize=(6,6), dpi=120)

for n in range(9):
  plt.subplot(3,3,n+1)
  plt.imshow(features['image'][..., n])
  plt.title(chr(features['m_label'][n]))
  plt.axis('off')

PNG

Функции нижнего уровня

До сих пор это руководство было сосредоточено на утилитах самого высокого уровня для чтения данных csv. Есть еще два API, которые могут быть полезны опытным пользователям, если ваш вариант использования не соответствует основным шаблонам.

  • tf.io.decode_csv - функция для разбора строк текста в список тензоров столбцов CSV.
  • tf.data.experimental.CsvDataset - конструктор набора данных csv нижнего уровня.

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

tf.io.decode_csv

Эта функция декодирует строку или список строк в список столбцов.

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

Чтобы прочитать данные Титаника в виде строк с помощью decode_csv вы должны сказать:

text = pathlib.Path(titanic_file_path).read_text()
lines = text.split('\n')[1:-1]

all_strings = [str()]*10
all_strings
['', '', '', '', '', '', '', '', '', '']
features = tf.io.decode_csv(lines, record_defaults=all_strings) 

for f in features:
  print(f"type: {f.dtype.name}, shape: {f.shape}")
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)

Чтобы проанализировать их с их фактическими типами, создайте список record_defaults соответствующих типов:

print(lines[0])
0,male,22.0,1,0,7.25,Third,unknown,Southampton,n
titanic_types = [int(), str(), float(), int(), int(), float(), str(), str(), str(), str()]
titanic_types
[0, '', 0.0, 0, 0, 0.0, '', '', '', '']
features = tf.io.decode_csv(lines, record_defaults=titanic_types) 

for f in features:
  print(f"type: {f.dtype.name}, shape: {f.shape}")
type: int32, shape: (627,)
type: string, shape: (627,)
type: float32, shape: (627,)
type: int32, shape: (627,)
type: int32, shape: (627,)
type: float32, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)
type: string, shape: (627,)

tf.data.experimental.CsvDataset

Класс tf.data.experimental.CsvDataset предоставляет минимальный интерфейс Dataset CSV без удобных функций функции make_csv_dataset : синтаксический анализ заголовка столбца, определение типа столбца, автоматическое перемешивание, чередование файлов.

Этот конструктор использует record_defaults же, как io.parse_csv :

simple_titanic = tf.data.experimental.CsvDataset(titanic_file_path, record_defaults=titanic_types, header=True)

for example in simple_titanic.take(1):
  print([e.numpy() for e in example])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']

Приведенный выше код в основном эквивалентен:

def decode_titanic_line(line):
  return tf.io.decode_csv(line, titanic_types)

manual_titanic = (
    # Load the lines of text
    tf.data.TextLineDataset(titanic_file_path)
    # Skip the header row.
    .skip(1)
    # Decode the line.
    .map(decode_titanic_line)
)

for example in manual_titanic.take(1):
  print([e.numpy() for e in example])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']

Несколько файлов

Чтобы проанализировать набор данных шрифтов с помощью experimental.CsvDataset , вам сначала нужно определить типы столбцов для record_defaults . Начните с проверки первой строки одного файла:

font_line = pathlib.Path(font_csvs[0]).read_text().splitlines()[1]
print(font_line)
AGENCY,AGENCY FB,64258,0.400000,0,0.000000,35,21,51,22,20,20,1,1,1,21,101,210,255,255,255,255,255,255,255,255,255,255,255,255,255,255,1,1,1,93,255,255,255,176,146,146,146,146,146,146,146,146,216,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,141,141,141,182,255,255,255,172,141,141,141,115,1,1,1,1,163,255,255,255,255,255,255,255,255,255,255,255,255,255,255,209,1,1,1,1,163,255,255,255,6,6,6,96,255,255,255,74,6,6,6,5,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255,1,1,1,93,255,255,255,70,1,1,1,1,1,1,1,1,163,255,255,255

Только первые два поля являются строками, остальные - целыми или плавающими, и вы можете получить общее количество функций, посчитав запятые:

num_font_features = font_line.count(',')+1
font_column_types = [str(), str()] + [float()]*(num_font_features-2)

Конструктор CsvDatasaet может принимать список входных файлов, но читает их последовательно. Первый файл в списке CSV - AGENCY.csv :

font_csvs[0]
'fonts/AGENCY.csv'

Поэтому, когда вы передаете список файлов в CsvDataaset записи из AGENCY.csv :

simple_font_ds = tf.data.experimental.CsvDataset(
    font_csvs, 
    record_defaults=font_column_types, 
    header=True)
for row in simple_font_ds.take(10):
  print(row[0].numpy())
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'
b'AGENCY'

Чтобы чередовать несколько файлов, используйте Dataset.interleave .

Вот исходный набор данных, содержащий имена файлов csv:

font_files = tf.data.Dataset.list_files("fonts/*.csv")

Это перемешивает имена файлов каждую эпоху:

print('Epoch 1:')
for f in list(font_files)[:5]:
  print("    ", f.numpy())
print('    ...')
print()

print('Epoch 2:')
for f in list(font_files)[:5]:
  print("    ", f.numpy())
print('    ...')
Epoch 1:
     b'fonts/BROADWAY.csv'
     b'fonts/COPPERPLATE.csv'
     b'fonts/STENCIL.csv'
     b'fonts/COOPER.csv'
     b'fonts/GABRIOLA.csv'
    ...

Epoch 2:
     b'fonts/MONOSPAC821.csv'
     b'fonts/ONYX.csv'
     b'fonts/HARLOW.csv'
     b'fonts/TIMES.csv'
     b'fonts/JOKERMAN.csv'
    ...

Метод interleave принимает map_func который создает map_func Dataset для каждого элемента родительского Dataset .

Здесь вы хотите создать CsvDataset из каждого элемента набора данных файлов:

def make_font_csv_ds(path):
  return tf.data.experimental.CsvDataset(
    path, 
    record_defaults=font_column_types, 
    header=True)

Dataset возвращаемый Dataset interleave, возвращает элементы путем циклического перебора ряда дочерних наборов Dataset . Обратите внимание на то, как набор данных циклически cycle_length)=3 через cycle_length)=3 три файла шрифтов:

font_rows = font_files.interleave(make_font_csv_ds,
                                  cycle_length=3)
fonts_dict = {'font_name':[], 'character':[]}

for row in font_rows.take(10):
  fonts_dict['font_name'].append(row[0].numpy().decode())
  fonts_dict['character'].append(chr(row[2].numpy()))

pd.DataFrame(fonts_dict)

Представление

Ранее отмечалось, что io.decode_csv более эффективен при запуске с io.decode_csv строк.

Этим фактом можно воспользоваться при использовании пакетов больших размеров для повышения производительности загрузки CSV (но сначала попробуйте кэширование ).

Со встроенным загрузчиком 20, партии из 2048 экземпляров занимают около 17 с.

BATCH_SIZE=2048
fonts_ds = tf.data.experimental.make_csv_dataset(
    file_pattern = "fonts/*.csv",
    batch_size=BATCH_SIZE, num_epochs=1,
    num_parallel_reads=100)
%%time
for i,batch in enumerate(fonts_ds.take(20)):
  print('.',end='')

print()
....................
CPU times: user 26.8 s, sys: 1.75 s, total: 28.6 s
Wall time: 11.1 s

Передача пакетов текстовых строк в decode_csv выполняется быстрее, примерно за 5 decode_csv :

fonts_files = tf.data.Dataset.list_files("fonts/*.csv")
fonts_lines = fonts_files.interleave(
    lambda fname:tf.data.TextLineDataset(fname).skip(1), 
    cycle_length=100).batch(BATCH_SIZE)

fonts_fast = fonts_lines.map(lambda x: tf.io.decode_csv(x, record_defaults=font_column_types))
%%time
for i,batch in enumerate(fonts_fast.take(20)):
  print('.',end='')

print()
....................
CPU times: user 9.29 s, sys: 0 ns, total: 9.29 s
Wall time: 1.48 s

Другой пример увеличения производительности CSV за счет использования больших пакетов см. В руководстве по переобучению и недостаточному подгонке .

Такой подход может работать, но рассмотрите другие варианты, такие как cache и snapshot , или перекодирование ваших данных в более упрощенный формат.