Odpowiedz już dziś na lokalne wydarzenie TensorFlow Everywhere!
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Załaduj dane CSV

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na 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 w formę odpowiednią do treningu.

Ten samouczek skupia 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 i samouczku dotyczącym warstw wstępnego przetwarzania .

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 model 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 [==============================] - 0s 1ms/step - loss: 60.2584
Epoch 2/10
104/104 [==============================] - 0s 1ms/step - loss: 11.4741
Epoch 3/10
104/104 [==============================] - 0s 1ms/step - loss: 8.5354
Epoch 4/10
104/104 [==============================] - 0s 1ms/step - loss: 8.0559
Epoch 5/10
104/104 [==============================] - 0s 1ms/step - loss: 7.6156
Epoch 6/10
104/104 [==============================] - 0s 1ms/step - loss: 7.2644
Epoch 7/10
104/104 [==============================] - 0s 1ms/step - loss: 6.9853
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 6.7824
Epoch 9/10
104/104 [==============================] - 0s 1ms/step - loss: 6.6324
Epoch 10/10
104/104 [==============================] - 0s 1ms/step - loss: 6.5348

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

Właśnie zobaczyłeś najbardziej podstawowy sposób trenowania modelu przy użyciu danych CSV. Następnie nauczysz się, jak zastosować przetwarzanie wstępne do normalizacji kolumn liczbowych.

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 danych.

normalize.adapt(abalone_features)

Następnie użyj warstwy normalizacji w 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: 93.4577
Epoch 2/10
104/104 [==============================] - 0s 1ms/step - loss: 54.7412
Epoch 3/10
104/104 [==============================] - 0s 2ms/step - loss: 16.7861
Epoch 4/10
104/104 [==============================] - 0s 2ms/step - loss: 5.8002
Epoch 5/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9725
Epoch 6/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9332
Epoch 7/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9074
Epoch 8/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9035
Epoch 9/10
104/104 [==============================] - 0s 1ms/step - loss: 4.9028
Epoch 10/10
104/104 [==============================] - 0s 1ms/step - loss: 4.8998

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

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 wymaga indywidualnego traktowania.

Jedną z opcji jest wstępne przetwarzanie danych w trybie offline (przy użyciu dowolnego narzędzia) w celu konwersji kolumn kategorialnych na kolumny liczbowe, a następnie przekazanie przetworzonych danych wyjściowych 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ą 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
<tf.Tensor 'AddV2:0' shape=(None,) dtype=float32>
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': <tf.Tensor 'sex:0' shape=(None, 1) dtype=string>,
 'age': <tf.Tensor 'age:0' shape=(None, 1) dtype=float32>,
 'n_siblings_spouses': <tf.Tensor 'n_siblings_spouses:0' shape=(None, 1) dtype=float32>,
 'parch': <tf.Tensor 'parch:0' shape=(None, 1) dtype=float32>,
 'fare': <tf.Tensor 'fare:0' shape=(None, 1) dtype=float32>,
 'class': <tf.Tensor 'class:0' shape=(None, 1) dtype=string>,
 'deck': <tf.Tensor 'deck:0' shape=(None, 1) dtype=string>,
 'embark_town': <tf.Tensor 'embark_town:0' shape=(None, 1) dtype=string>,
 'alone': <tf.Tensor 'alone:0' shape=(None, 1) dtype=string>}

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
<tf.Tensor 'normalization_1/truediv:0' shape=(None, 4) dtype=float32>

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

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 Pandas DataFrames ponieważ nie jest jasne, czy należy je przekonwertować na jeden tensor, czy na słownik tensorów. Więc przekonwertuj to 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)

Podczas trenowania modelu 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 [==============================] - 0s 3ms/step - loss: 0.6595
Epoch 2/10
20/20 [==============================] - 0s 3ms/step - loss: 0.5576
Epoch 3/10
20/20 [==============================] - 0s 3ms/step - loss: 0.5002
Epoch 4/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4719
Epoch 5/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4533
Epoch 6/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4415
Epoch 7/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4346
Epoch 8/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4299
Epoch 9/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4285
Epoch 10/10
20/20 [==============================] - 0s 3ms/step - loss: 0.4261

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

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')
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: test/assets
WARNING:tensorflow:5 out of the last 5 calls to <function recreate_function.<locals>.restored_function_body at 0x7f3f60030950> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/tutorials/customization/performance#python_or_tensor_args and https://www.tensorflow.org/api_docs/python/tf/function for  more details.

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

Korzystanie z tf.data

W poprzedniej sekcji podczas uczenia modelu polegałeś na tasowaniu i wsadowaniu danych wbudowanym 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 można znaleźć 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 4ms/step - loss: 0.4233
Epoch 2/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4223
Epoch 3/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4225
Epoch 4/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4213
Epoch 5/5
20/20 [==============================] - 0s 4ms/step - loss: 0.4209

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

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, które ułatwiają pracę z danymi. 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'female' b'male' b'female']
age                 : [28. 11. 29. 28. 34.]
n_siblings_spouses  : [1 0 1 0 0]
parch               : [0 0 0 0 0]
fare                : [ 24.15   18.788  26.    221.779  10.5  ]
class               : [b'Third' b'Third' b'Second' b'First' b'Second']
deck                : [b'unknown' b'unknown' b'unknown' b'C' b'F']
embark_town         : [b'Queenstown' b'Cherbourg' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'n' b'y' b'n' b'y' b'y']

label               : [0 0 1 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 argument compression_type na odczyt 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                : [280.66 283.22 284.23 280.7  287.79]
rain_1h             : [0. 0. 0. 0. 0.]
snow_1h             : [0. 0. 0. 0. 0.]
clouds_all          : [92 90 90 64 75]
weather_main        : [b'Mist' b'Drizzle' b'Rain' b'Clouds' b'Clouds']
weather_description : [b'mist' b'light intensity drizzle' b'light rain' b'broken clouds'
 b'broken clouds']
date_time           : [b'2012-10-19 21:00:00' b'2012-10-25 21:00:00' b'2013-05-23 16:00:00'
 b'2013-11-19 14:00:00' b'2013-05-16 08:00:00']

label               : [2942 2587 6305 5242 6404]

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 15.4 s, sys: 3.98 s, total: 19.4 s
Wall time: 12.4 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.48 s, sys: 179 ms, total: 1.65 s
Wall time: 1.32 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.09 s, sys: 449 ms, total: 2.54 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 uproszczonego 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 pliki w środku:

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 można przekazać glob stylu file_pattern do experimental.make_csv_dataset funkcji. 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'HARRINGTON' b'ISOC' b'GEORGIA' b'CAMBRIA' b'PROXY' b'SNAP'
 b'COUNTRYBLUEPRINT' b'PROXY' b'VIVALDI' b'GILL']
fontVariant         : [b'HARRINGTON' b'ISOCTEUR' b'GEORGIA' b'CAMBRIA' b'PROXY 9' b'SNAP ITC'
 b'COUNTRYBLUEPRINT' b'PROXY 9' b'VIVALDI'
 b'GILL SANS ULTRA BOLD CONDENSED']
m_label             : [   94  8800  8539 10659   305  8223  8217   728   170   115]
strength            : [0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4]
italic              : [0 0 0 0 1 0 0 1 0 0]
orientation         : [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
m_top               : [49 55 35 46 52 41 38 36 36 46]
m_left              : [22 33 24 22 27 23 24 40 27 21]
originalH           : [17 30 48 37 34 59 54  9 25 41]
originalW           : [30 21 62 39 12 34 25 24 27 26]
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 255 255 213   1   1]
r0c1                : [  1   1   3   1   1 255 255   1   1   1]
r0c2                : [  1   1  88   1   1 255 255   1   1   1]
r0c3                : [  1   1 232   1   1 255 255   1   1   1]
...
[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 zestawie 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')

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 przy użyciu 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 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/ELEPHANT.csv'
     b'fonts/NINA.csv'
     b'fonts/COPPERPLATE.csv'
     b'fonts/GOTHICE.csv'
     b'fonts/SWIS721.csv'
    ...

Epoch 2:
     b'fonts/PALACE.csv'
     b'fonts/GABRIOLA.csv'
     b'fonts/COURIER.csv'
     b'fonts/CONSTANTIA.csv'
     b'fonts/QUICKTYPE.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 programem ł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 28.9 s, sys: 2.76 s, total: 31.7 s
Wall time: 11.7 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 5.4 s, sys: 0 ns, total: 5.4 s
Wall time: 4.84 s

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

Takie podejście może działać, ale weź pod uwagę inne opcje, takie jak cache i snapshot , lub ponowne kodowanie danych do bardziej usprawnionego formatu.