Oglądaj prezentacje, sesje produktowe, warsztaty i nie tylko z playlisty Google I / O See

Załaduj dane CSV

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło w serwisie GitHubPobierz notatnik

Ten samouczek zawiera przykłady użycia danych CSV z TensorFlow.

Istnieją dwie główne części tego:

  1. Ładowanie danych z dysku
  2. Wstępne przetwarzanie go w formę nadającą się do treningu.

Ten samouczek koncentruje się na ładowaniu i podaje kilka szybkich przykładów przetwarzania wstępnego. Samouczek, który koncentruje się na aspekcie przetwarzania wstępnego, znajduje się w przewodniku po warstwach przetwarzania wstępnego i samouczku .

Ustawiać

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

W danych pamięci

W przypadku każdego małego zestawu danych CSV najprostszym sposobem wytrenowania modelu TensorFlow na nim jest załadowanie go do pamięci jako pandy Dataframe lub tablica NumPy.

Stosunkowo prostym przykładem jest zbiór danych uchowca .

  • Zbiór danych jest mały.
  • Wszystkie funkcje wejściowe są wartościami zmiennoprzecinkowymi o ograniczonym zakresie.

Oto jak pobrać dane do Pandas DataFrame :

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()

Zbiór danych zawiera zestaw pomiarów uchowca , gatunku ślimaka morskiego.

muszla uchowca

„Abalone shell” (autor: Nicki Dugan Pogue , CC BY-SA 2.0)

Nominalne zadanie dla tego zbioru danych polega na przewidywaniu wieku na podstawie innych pomiarów, więc oddziel funkcje i etykiety do treningu:

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

W przypadku tego zbioru danych wszystkie funkcje będą traktowane identycznie. Spakuj funkcje do jednej tablicy 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 ]])

Następnie wykonaj model regresji przewidujący wiek. Ponieważ istnieje tylko jeden tensor wejściowy, wystarczy tutaj keras.Sequential Model keras.Sequential .

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

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

Aby wytrenować ten model, przekaż funkcje i etykiety do Model.fit :

abalone_model.fit(abalone_features, abalone_labels, epochs=10)
Epoch 1/10
104/104 [==============================] - 1s 2ms/step - loss: 61.2118
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 11.9831
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 8.7308
Epoch 4/10
104/104 [==============================] - 0s 1ms/step - loss: 8.1889
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 7.6973
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 7.2868
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 6.9842
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 6.7556
Epoch 9/10
104/104 [==============================] - 0s 1ms/step - loss: 6.6239
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 6.5126
<tensorflow.python.keras.callbacks.History at 0x7f4bf8402410>

Właśnie zobaczyłeś najbardziej podstawowy sposób trenowania modelu przy użyciu danych CSV. Następnie dowiesz się, jak zastosować przetwarzanie wstępne, aby znormalizować kolumny liczbowe.

Podstawowe przetwarzanie wstępne

Dobrą praktyką jest znormalizowanie danych wejściowych do modelu. Warstwy experimental.preprocessing przetwarzania wstępnego zapewniają wygodny sposób wbudowania tej normalizacji w model.

Warstwa wstępnie obliczy średnią i wariancję każdej kolumny i użyje ich do normalizacji danych.

Najpierw tworzysz warstwę:

normalize = preprocessing.Normalization()

Następnie użyj metody Normalization.adapt() , aby dostosować warstwę normalizacji do swoich danych.

normalize.adapt(abalone_features)

Następnie użyj warstwy normalizacji w swoim modelu:

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: 92.2295
Epoch 2/10
104/104 [==============================] - 0s 2ms/step - loss: 52.6606
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 15.8834
Epoch 4/10
104/104 [==============================] - 0s 2ms/step - loss: 5.6510
Epoch 5/10
104/104 [==============================] - 0s 2ms/step - loss: 5.0132
Epoch 6/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9937
Epoch 7/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9871
Epoch 8/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9607
Epoch 9/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9417
Epoch 10/10
104/104 [==============================] - 0s 2ms/step - loss: 4.9781
<tensorflow.python.keras.callbacks.History at 0x7f4bf8188490>

Mieszane typy danych

Zbiór danych „Titanic” zawiera informacje o pasażerach Titanica. Nominalne zadanie w tym zbiorze danych polega na przewidywaniu, kto przeżył.

Titanic

Zdjęcie z Wikimedia

Surowe dane można łatwo załadować jako Pandas DataFrame , ale nie można ich natychmiast użyć jako danych wejściowych do modelu 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')

Ze względu na różne typy danych i zakresy nie można po prostu umieścić funkcji w tablicy NumPy i przekazać jej do modelu keras.Sequential . Każda kolumna musi być traktowana indywidualnie.

Jedną z opcji jest wstępne przetwarzanie danych w trybie offline (przy użyciu dowolnego narzędzia), aby przekonwertować kolumny kategorialne na kolumny liczbowe, a następnie przekazać przetworzone dane wyjściowe do modelu TensorFlow. Wadą tego podejścia jest to, że jeśli zapiszesz i wyeksportujesz model, przetwarzanie wstępne nie zostanie z nim zapisane. Warstwy experimental.preprocessing przetwarzania wstępnego pozwalają uniknąć tego problemu, ponieważ są częścią modelu.

W tym przykładzie utworzysz model, który implementuje logikę przetwarzania wstępnego przy użyciu funkcjonalnego interfejsu API Keras . Możesz to również zrobić, tworząc podklasy .

Funkcjonalne API działa na „symbolicznych” tensorach. Normalne „chętne” tensory mają pewną wartość. W przeciwieństwie do tych „symbolicznych” tensorów nie. Zamiast tego śledzą, które operacje są na nich wykonywane, i tworzą reprezentację obliczeń, którą można wykonać później. Oto krótki przykład:

# 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

Aby zbudować model przetwarzania wstępnego, zacznij od zbudowania zestawu symbolicznych obiektów keras.Input , dopasowujących nazwy i typy danych kolumn 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')>}

Pierwszym krokiem w logice przetwarzania wstępnego jest połączenie razem danych liczbowych i przepuszczenie ich przez warstwę normalizacji:

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')>

Zbierz wszystkie symboliczne wyniki przetwarzania wstępnego, aby później je połączyć.

preprocessed_inputs = [all_numeric_inputs]

W przypadku wejściowych ciągów znaków użyj funkcji preprocessing.StringLookup aby odwzorować ciągi na indeksy całkowite w słowniku. Następnie użyj preprocessing.CategoryEncoding aby przekonwertować indeksy na dane typu float32 odpowiednie dla modelu.

Domyślne ustawienia warstwy preprocessing.CategoryEncoding tworzą jeden gorący wektor dla każdego wejścia. layers.Embedding również zadziała. Więcej informacji na ten temat zawiera przewodnik i samouczek dotyczący wstępnego przetwarzania warstw .

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.

Dzięki kolekcji danych inputs i processed_inputs można połączyć wszystkie wstępnie przetworzone dane wejściowe razem i zbudować model, który obsługuje przetwarzanie wstępne:

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

Ten model zawiera tylko wstępne przetwarzanie danych wejściowych. Możesz go uruchomić, aby zobaczyć, co robi z Twoimi danymi. Modele Keras nie konwertują automatycznie DataFrames Pandas, ponieważ nie jest jasne, czy należy je przekonwertować na jeden tensor, czy na słownik tensorów. Więc przekonwertuj go na słownik tensorów:

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

Wytnij pierwszy przykład szkoleniowy i przekaż go do tego modelu przetwarzania wstępnego, zobaczysz, że funkcje numeryczne i ciągi jedności są połączone razem:

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)>

Teraz zbuduj model na podstawie tego:

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)

Kiedy trenujesz model, przekaż słownik elementów jako x , a etykietę jako y .

titanic_model.fit(x=titanic_features_dict, y=titanic_labels, epochs=10)
Epoch 1/10
20/20 [==============================] - 1s 4ms/step - loss: 0.6321
Epoch 2/10
20/20 [==============================] - 0s 4ms/step - loss: 0.5221
Epoch 3/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4835
Epoch 4/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4595
Epoch 5/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4463
Epoch 6/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4381
Epoch 7/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4299
Epoch 8/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4272
Epoch 9/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4238
Epoch 10/10
20/20 [==============================] - 0s 4ms/step - loss: 0.4236
<tensorflow.python.keras.callbacks.History at 0x7f4c97f3a0d0>

Ponieważ wstępne przetwarzanie jest częścią modelu, możesz zapisać model i załadować go ponownie w innym miejscu i uzyskać identyczne wyniki:

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.878]], shape=(1, 1), dtype=float32)
tf.Tensor([[-1.878]], shape=(1, 1), dtype=float32)

Korzystanie z tf.data

W poprzedniej sekcji podczas uczenia modelu polegałeś na tasowaniu i wsadowaniu danych wbudowanych w model.

Jeśli potrzebujesz większej kontroli nad tf.data danych wejściowych lub potrzebujesz danych, które nie mieszczą się łatwo w pamięci: użyj tf.data .

Więcej przykładów znajduje się w przewodniku tf.data .

Dane w pamięci włączone

Jako pierwszy przykład zastosowania tf.data do danych CSV rozważ poniższy kod, aby ręcznie tf.data słownik funkcji z poprzedniej sekcji. Dla każdego indeksu przyjmuje ten indeks dla każdej funkcji:

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

Uruchom to i wydrukuj pierwszy przykład:

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

Najbardziej podstawowymtf.data.Dataset w programie ładującym dane pamięci jest konstruktor Dataset.from_tensor_slices . Zwracatf.data.Dataset który implementuje uogólnioną wersję powyższej funkcji slices w TensorFlow.

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

Możesz iterować potf.data.Dataset jak każdy innytf.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'

Funkcja from_tensor_slices może obsługiwać dowolną strukturę zagnieżdżonych słowników lub krotek. Poniższy kod tworzy zbiór danych par (features_dict, labels) :

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

Aby wytrenować model przy użyciu tego Dataset , musisz przynajmniej shuffle i batch dane.

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

Zamiast przekazywać features i labels do Model.fit , przekazujesz zbiór danych:

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

Z jednego pliku

Do tej pory ten samouczek działał z danymi w pamięci. tf.data to wysoce skalowalny zestaw narzędzi do tworzenia potoków danych i zapewnia kilka funkcji do obsługi ładowania plików 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

Teraz przeczytaj dane CSV z pliku i utwórztf.data.Dataset .

(Aby uzyskać pełną dokumentację, zobacz 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,)

Ta funkcja zawiera wiele wygodnych funkcji, dzięki czemu praca z danymi jest łatwa. To zawiera:

  • Używanie nagłówków kolumn jako kluczy słownika.
  • Automatyczne określanie typu każdej kolumny.
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'male' b'male' b'male' b'male']
age                 : [28. 44. 40. 16. 25.]
n_siblings_spouses  : [8 0 0 0 1]
parch               : [2 0 0 0 0]
fare                : [69.55   7.925  0.    10.5   91.079]
class               : [b'Third' b'Third' b'First' b'Second' b'First']
deck                : [b'unknown' b'unknown' b'B' b'unknown' b'B']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Southampton' b'Cherbourg']
alone               : [b'n' b'y' b'y' b'y' b'n']

label               : [0 1 0 0 1]

Może również dekompresować dane w locie. Oto plik CSV spakowany gzipem, zawierający zbiór danych o ruchu na drogach międzystanowych dla metra

Korek.

Zdjęcie z Wikimedia

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

Ustaw compression_type argumentu czytać bezpośrednio z pliku skompresowanego:

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                : [291.08 273.08 296.58 301.02 288.18]
rain_1h             : [0.   0.   0.25 0.   0.  ]
snow_1h             : [0. 0. 0. 0. 0.]
clouds_all          : [ 1 90  0  0  0]
weather_main        : [b'Clear' b'Clouds' b'Rain' b'Clear' b'Clear']
weather_description : [b'sky is clear' b'overcast clouds' b'light rain' b'Sky is Clear'
 b'Sky is Clear']
date_time           : [b'2013-05-17 01:00:00' b'2013-04-11 08:00:00' b'2013-06-12 19:00:00'
 b'2013-08-26 23:00:00' b'2013-07-25 05:00:00']

label               : [ 475 3069 3342 1133 2903]

Buforowanie

Analiza danych CSV wiąże się z pewnym obciążeniem. W przypadku małych modeli może to stanowić wąskie gardło w treningu.

W zależności od przypadku użycia dobrym pomysłem może być użycie Dataset.cache lub data.experimental.snapshot aby dane csv były analizowane tylko w pierwszej epoce.

Główna różnica między metodami cache i snapshot polega na tym, że pliki cache mogą być używane tylko przez proces TensorFlow, który je utworzył, ale pliki snapshot mogą być odczytywane przez inne procesy.

Na przykład wykonanie iteracji przez traffic_volume_csv_gz_ds 20 razy zajmuje ~ 15 sekund bez buforowania lub ~ 2s z buforowaniem.

%%time
for i, (batch, label) in enumerate(traffic_volume_csv_gz_ds.repeat(20)):
  if i % 40 == 0:
    print('.', end='')
print()
...............................................................................................
CPU times: user 16.8 s, sys: 4.11 s, total: 20.9 s
Wall time: 12.9 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.55 s, sys: 198 ms, total: 1.75 s
Wall time: 1.37 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.31 s, sys: 396 ms, total: 2.7 s
Wall time: 1.64 s

Jeśli ładowanie danych jest spowolnione przez ładowanie plików csv, a cache i snapshot są niewystarczające dla Twojego przypadku użycia, rozważ ponowne zakodowanie danych do bardziej usprawnionego formatu.

Wiele plików

Wszystkie dotychczasowe przykłady w tej sekcji można by łatwo wykonać bez tf.data . Jednym z miejsc, w których tf.data może naprawdę uprościć tf.data jest tf.data ze zbiorami plików.

Na przykład zestaw danych obrazów czcionek znaków jest rozpowszechniany jako zbiór plików csv, po jednym na czcionkę.

Czcionki

Obraz Willi Heidelbach z Pixabay

Pobierz zestaw danych i spójrz na znajdujące się w nim pliki:

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

Gdy mamy do czynienia z wieloma plikami, do funkcji experimental.make_csv_dataset można przekazać globalny file_pattern . Kolejność plików jest tasowana w każdej iteracji.

Użyj argumentu num_parallel_reads aby ustawić, ile plików jest odczytywanych równolegle i przeplatanych razem.

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)

Te pliki csv mają obrazy spłaszczone w jednym wierszu. Nazwy kolumn są sformatowane r{row}c{column} . Oto pierwsza partia:

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'CANDARA' b'SWIS721' b'BROADWAY' b'MONOTYPE' b'VIVALDI' b'SWIS721'
 b'MINGLIU' b'SNAP' b'BROADWAY' b'OCRA']
fontVariant         : [b'CANDARA' b'SWIS721 LTEX BT' b'BROADWAY' b'MONOTYPE CORSIVA' b'VIVALDI'
 b'SWIS721 LTEX BT' b'MINGLIU_HKSCS-EXTB' b'SNAP ITC' b'BROADWAY'
 b'scanned']
m_label             : [7901  172  163 1056  214  229  215   48  123   50]
strength            : [0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4]
italic              : [0 1 1 0 1 1 1 0 1 0]
orientation         : [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
m_top               : [37 56 35 42 25 31 42 40 35  0]
m_left              : [23 36 22 25 26 27 22 22 34  0]
originalH           : [48 16 51 40 56 54 35 49 54 32]
originalW           : [39 41 49 40 57 40 40 53 36 22]
h                   : [20 20 20 20 20 20 20 20 20 20]
w                   : [20 20 20 20 20 20 20 20 20 20]
r0c0                : [  1 131   1   1   1   1   1   1   1  24]
r0c1                : [  1 255   1   1   1   1   1   1   1 227]
r0c2                : [  1 255   1   1   1   1   1   1   1  96]
r0c3                : [142 255   1   1   1   1   1   1   1 154]
...
[total: 412 features]

Opcjonalnie: pola pakowania

Prawdopodobnie nie chcesz pracować z każdym pikselem w oddzielnych kolumnach, takich jak ta. Przed próbą użycia tego zestawu danych należy spakować piksele do tensora obrazu.

Oto kod, który analizuje nazwy kolumn, aby utworzyć obrazy dla każdego przykładu:

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

Zastosuj tę funkcję do każdej partii w zbiorze danych:

fonts_image_ds = fonts_ds.map(make_images)

for features in fonts_image_ds.take(1):
  break

Wykreśl wynikowe obrazy:

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')
/home/kbuilder/.local/lib/python3.7/site-packages/matplotlib/backends/backend_agg.py:240: RuntimeWarning: Glyph 3926 missing from current font.
  font.set_text(s, 0.0, flags=flags)
/home/kbuilder/.local/lib/python3.7/site-packages/matplotlib/backends/backend_agg.py:203: RuntimeWarning: Glyph 3926 missing from current font.
  font.set_text(s, 0, flags=flags)

png

Funkcje niższego poziomu

Jak dotąd ten samouczek skupiał się na narzędziach najwyższego poziomu do odczytywania danych csv. Istnieją inne dwa interfejsy API, które mogą być pomocne dla zaawansowanych użytkowników, jeśli Twój przypadek użycia nie pasuje do podstawowych wzorców.

Ta sekcja odtwarza funkcjonalność dostarczaną przez make_csv_dataset , aby zademonstrować, jak można wykorzystać tę funkcjonalność niższego poziomu.

tf.io.decode_csv

Ta funkcja dekoduje ciąg lub listę ciągów do listy kolumn.

W przeciwieństwie do make_csv_dataset ta funkcja nie próbuje odgadnąć typów danych kolumn. Typy kolumn określa się, podając listę record_defaults zawierającą wartość odpowiedniego typu dla każdej kolumny.

Aby odczytać dane Titanica jako ciągi za pomocą decode_csv , powiedziałbyś:

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,)

Aby przeanalizować je z ich rzeczywistymi typami, utwórz listę record_defaults odpowiednich typów:

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

Klasa tf.data.experimental.CsvDataset zapewnia minimalny interfejs CSV Dataset bez wygodnych funkcji funkcji make_csv_dataset : analizowanie nagłówków kolumn, wnioskowanie o typach kolumn, automatyczne tasowanie, przeplatanie plików.

Poniższy konstruktor używa record_defaults taki sam sposób jak 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']

Powyższy kod jest w zasadzie równoważny z:

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']

Wiele plików

Aby przeanalizować zestaw danych czcionek przy użyciu experimental.CsvDataset , należy najpierw określić typy kolumn dla parametru record_defaults . Zacznij od sprawdzenia pierwszego wiersza jednego pliku:

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

Tylko dwa pierwsze pola to ciągi, reszta to liczby całkowite lub zmiennoprzecinkowe, a całkowitą liczbę funkcji można uzyskać, licząc przecinki:

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

Konstruktor CsvDatasaet może pobrać listę plików wejściowych, ale czyta je sekwencyjnie. Pierwszy plik na liście plików AGENCY.csv to AGENCY.csv :

font_csvs[0]
'fonts/AGENCY.csv'

Więc kiedy przekazujesz listę plików do CsvDataaset rekordy z AGENCY.csv są czytane jako pierwsze:

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'

Aby przeplatać wiele plików, użyj Dataset.interleave .

Oto początkowy zbiór danych zawierający nazwy plików CSV:

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

Powoduje to tasowanie nazw plików w każdej epoce:

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/MAIANDRA.csv'
     b'fonts/MODERN.csv'
     b'fonts/VIN.csv'
     b'fonts/CONSTANTIA.csv'
     b'fonts/PROXY.csv'
    ...

Epoch 2:
     b'fonts/ROMAN.csv'
     b'fonts/TAHOMA.csv'
     b'fonts/BRITANNIC.csv'
     b'fonts/OCRA.csv'
     b'fonts/TW.csv'
    ...

Metoda interleave przyjmuje map_func która tworzy map_func Dataset dla każdego elementu nadrzędnego Dataset .

Tutaj chcesz utworzyć CsvDataset z każdego elementu zbioru danych plików:

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

Zestaw Dataset zwrócony przez przeplot zwraca elementy, cyklicznie po kilku podrzędnych zestawach Dataset . Zwróć uwagę poniżej, w jaki sposób zestaw danych przechodzi cyklicznie przez cycle_length)=3 trzy pliki czcionek:

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)

Występ

Wcześniej zauważono, że io.decode_csv jest bardziej wydajne, gdy jest uruchamiane na partii ciągów.

Możliwe jest wykorzystanie tego faktu, gdy używasz dużych rozmiarów partii, aby poprawić wydajność ładowania CSV (ale najpierw spróbuj buforować ).

Z wbudowanym modułem ładującym 20, 2048 przykładowych partii zajmuje około 17 sekund.

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 27.5 s, sys: 1.53 s, total: 29 s
Wall time: 11.4 s

Przekazywanie partii linii tekstu do decode_csv przebiega szybciej, w około 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.23 s, sys: 0 ns, total: 9.23 s
Wall time: 1.52 s

Aby zapoznać się z innym przykładem zwiększania wydajności CSV przy użyciu dużych partii, zobacz samouczek dotyczący nadmiernego i niedopasowania .

Takie podejście może działać, ale rozważ inne opcje, takie jak cache i snapshot , lub ponowne kodowanie danych w bardziej usprawnionym formacie.