tf.data: Xây dựng đường ống đầu vào TensorFlow

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHubTải xuống sổ ghi chép

API tf.data cho phép bạn xây dựng các đường ống đầu vào phức tạp từ các mảnh đơn giản, có thể tái sử dụng. Ví dụ: đường dẫn cho một mô hình hình ảnh có thể tổng hợp dữ liệu từ các tệp trong hệ thống tệp phân tán, áp dụng nhiễu ngẫu nhiên cho mỗi hình ảnh và hợp nhất các hình ảnh được chọn ngẫu nhiên thành một lô để đào tạo. Quy trình cho mô hình văn bản có thể liên quan đến việc trích xuất các ký hiệu từ dữ liệu văn bản thô, chuyển đổi chúng để nhúng các số nhận dạng với một bảng tra cứu và kết hợp các chuỗi có độ dài khác nhau lại với nhau. API tf.data giúp nó có thể xử lý lượng lớn dữ liệu, đọc từ các định dạng dữ liệu khác nhau và thực hiện các phép biến đổi phức tạp.

API tf.data giới thiệu một trừu tượng tf.data.Dataset đại diện cho một chuỗi các phần tử, trong đó mỗi phần tử bao gồm một hoặc nhiều thành phần. Ví dụ: trong một đường ống hình ảnh, một phần tử có thể là một ví dụ huấn luyện đơn lẻ, với một cặp thành phần tensor đại diện cho hình ảnh và nhãn của nó.

Có hai cách riêng biệt để tạo tập dữ liệu:

  • Nguồn dữ liệu xây dựng Dataset từ dữ liệu được lưu trữ trong bộ nhớ hoặc trong một hoặc nhiều tệp.

  • Một phép biến đổi dữ liệu xây dựng một tập dữ liệu từ một hoặc nhiều đối tượng tf.data.Dataset .

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

Cơ học cơ bản

Để tạo một đường dẫn đầu vào, bạn phải bắt đầu với một nguồn dữ liệu. Ví dụ: để tạo Dataset từ dữ liệu trong bộ nhớ, bạn có thể sử dụng tf.data.Dataset.from_tensors() hoặc tf.data.Dataset.from_tensor_slices() . Ngoài ra, nếu dữ liệu đầu vào của bạn được lưu trữ trong tệp ở định dạng TFRecord được khuyến nghị, bạn có thể sử dụng tf.data.TFRecordDataset() .

Khi bạn có một đối tượng Dataset , bạn có thể biến đổi nó thành một Dataset mới bằng cách chuỗi các cuộc gọi phương thức trên đối tượng tf.data.Dataset . Ví dụ: bạn có thể áp dụng các phép biến đổi cho mỗi phần tử như Dataset.map() và các phép biến đổi nhiều phần tử như Dataset.batch() . Xem tài liệu về tf.data.Dataset để biết danh sách đầy đủ các phép biến đổi.

Đối tượng Dataset là một Python có thể lặp lại. Điều này làm cho nó có thể sử dụng các phần tử của nó bằng cách sử dụng vòng lặp for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset element_spec=TensorSpec(shape=(), dtype=tf.int32, name=None)>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

Hoặc bằng cách tạo một trình lặp Python một cách rõ ràng bằng cách sử dụng iter và sử dụng các phần tử của nó bằng cách sử dụng next :

it = iter(dataset)

print(next(it).numpy())
8

Ngoài ra, các phần tử của tập dữ liệu có thể được sử dụng bằng cách sử dụng phép biến đổi reduce , làm giảm tất cả các phần tử để tạo ra một kết quả duy nhất. Ví dụ sau minh họa cách sử dụng phép biến đổi reduce để tính tổng của một tập dữ liệu gồm các số nguyên.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

Cấu trúc tập dữ liệu

Tập dữ liệu tạo ra một chuỗi các phần tử , trong đó mỗi phần tử là cấu trúc các thành phần giống nhau (lồng nhau). Các thành phần riêng lẻ của cấu trúc có thể thuộc bất kỳ kiểu nào mà tf.TypeSpec có thể đại diện, bao gồm tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray hoặc tf.data.Dataset .

Các cấu trúc Python có thể được sử dụng để thể hiện cấu trúc (lồng nhau) của các phần tử bao gồm tuple , dict , NamedTupleOrderedDict . Đặc biệt, list không phải là một cấu trúc hợp lệ để thể hiện cấu trúc của các phần tử tập dữ liệu. Điều này là do người dùng tf.data ban đầu cảm thấy mạnh mẽ về việc đầu vào list (ví dụ: được chuyển đến tf.data.Dataset.from_tensors ) được tự động đóng gói dưới dạng tensors và đầu ra list (ví dụ: giá trị trả về của các chức năng do người dùng xác định) được ép buộc thành một tuple . Do đó, nếu bạn muốn đầu vào list được coi như một cấu trúc, bạn cần chuyển đổi nó thành tuple và nếu bạn muốn đầu ra list là một thành phần duy nhất, thì bạn cần phải đóng gói nó một cách rõ ràng bằng tf.stack .

Thuộc tính Dataset.element_spec cho phép bạn kiểm tra kiểu của từng thành phần phần tử. Thuộc tính trả về một cấu trúc lồng nhau của các đối tượng tf.TypeSpec , khớp với cấu trúc của phần tử, có thể là một thành phần đơn lẻ, một bộ thành phần hoặc một bộ thành phần lồng nhau. Ví dụ:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

Các phép biến đổi Dataset hỗ trợ các tập dữ liệu của bất kỳ cấu trúc nào. Khi sử dụng các phép biến đổi Dataset.map()Dataset.filter() , áp dụng một hàm cho mỗi phần tử, cấu trúc phần tử sẽ xác định các đối số của hàm:

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset element_spec=TensorSpec(shape=(10,), dtype=tf.int32, name=None)>
for z in dataset1:
  print(z.numpy())
[3 3 7 5 9 8 4 2 3 7]
[8 9 6 7 5 6 1 6 2 3]
[9 8 4 4 8 7 1 5 6 7]
[5 9 5 4 2 5 7 8 8 8]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset element_spec=(TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None))>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset element_spec=(TensorSpec(shape=(10,), dtype=tf.int32, name=None), (TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None)))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

Đọc dữ liệu đầu vào

Tiêu thụ mảng NumPy

Xem Tải mảng NumPy để biết thêm ví dụ.

Nếu tất cả dữ liệu đầu vào của bạn phù hợp với bộ nhớ, cách đơn giản nhất để tạo Dataset từ chúng là chuyển đổi chúng thành đối tượng tf.Tensor và sử dụng Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset element_spec=(TensorSpec(shape=(28, 28), dtype=tf.float64, name=None), TensorSpec(shape=(), dtype=tf.uint8, name=None))>

Sử dụng trình tạo Python

Một nguồn dữ liệu phổ biến khác có thể dễ dàng được nhập dưới dạng tf.data.Dataset là trình tạo python.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Phương thức khởi tạo Dataset.from_generator chuyển đổi trình tạo python thành tf.data.Dataset đầy đủ chức năng.

Hàm tạo nhận một đầu vào có thể gọi được, không phải là một trình lặp. Điều này cho phép nó khởi động lại máy phát khi kết thúc. Nó nhận một đối số args tùy chọn, được truyền làm đối số của có thể gọi.

Đối số output_types là bắt buộc vì tf.data xây dựng một tf.Graph bên trong và các cạnh biểu đồ yêu cầu một tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

Đối số output_shapes không bắt buộc nhưng rất được khuyến khích vì nhiều phép toán TensorFlow không hỗ trợ các tensor có thứ hạng không xác định. Nếu độ dài của một trục cụ thể là không xác định hoặc có thể thay đổi, hãy đặt nó là None trong output_shapes .

Điều quan trọng cần lưu ý là output_shapesoutput_types tuân theo các quy tắc lồng ghép giống như các phương thức tập dữ liệu khác.

Đây là một trình tạo ví dụ minh họa cả hai khía cạnh, nó trả về các bộ giá trị của mảng, trong đó mảng thứ hai là một vectơ có độ dài chưa biết.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [0.3939]
1 : [ 0.9282 -0.0158  1.0096  0.7155  0.0491  0.6697 -0.2565  0.487 ]
2 : [-0.4831  0.37   -1.3918 -0.4786  0.7425 -0.3299]
3 : [ 0.1427 -1.0438  0.821  -0.8766 -0.8369  0.4168]
4 : [-1.4984 -1.8424  0.0337  0.0941  1.3286 -1.4938]
5 : [-1.3158 -1.2102  2.6887 -1.2809]
6 : []

Đầu ra đầu tiên là int32 , đầu ra thứ hai là float32 .

Mục đầu tiên là một đại lượng vô hướng, hình dạng () và mục thứ hai là một vectơ có độ dài không xác định, hình dạng (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset element_spec=(TensorSpec(shape=(), dtype=tf.int32, name=None), TensorSpec(shape=(None,), dtype=tf.float32, name=None))>

Bây giờ nó có thể được sử dụng như một tf.data.Dataset thông thường. Lưu ý rằng khi nhóm một tập dữ liệu có hình dạng thay đổi, bạn cần sử dụng Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[ 8 10 18  1  5 19 22 17 21 25]

[[-0.6098  0.1366 -2.15   -0.9329  0.      0.    ]
 [ 1.0295 -0.033  -0.0388  0.      0.      0.    ]
 [-0.1137  0.3552  0.4363 -0.2487 -1.1329  0.    ]
 [ 0.      0.      0.      0.      0.      0.    ]
 [-1.0466  0.624  -1.7705  1.4214  0.9143 -0.62  ]
 [-0.9502  1.7256  0.5895  0.7237  1.5397  0.    ]
 [ 0.3747  1.2967  0.      0.      0.      0.    ]
 [-0.4839  0.292  -0.7909 -0.7535  0.4591 -1.3952]
 [-0.0468  0.0039 -1.1185 -1.294   0.      0.    ]
 [-0.1679 -0.3375  0.      0.      0.      0.    ]]

Để có một ví dụ thực tế hơn, hãy thử gói preprocessing.image.ImageDataGenerator dưới dạng tf.data.Dataset .

Đầu tiên tải xuống dữ liệu:

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 10s 0us/step
228827136/228813984 [==============================] - 10s 0us/step

Tạo hình image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

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

Sử dụng dữ liệu TFRecord

Xem phần Tải TFRecords để biết ví dụ từ đầu đến cuối.

API tf.data hỗ trợ nhiều định dạng tệp khác nhau để bạn có thể xử lý các tập dữ liệu lớn không vừa với bộ nhớ. Ví dụ: định dạng tệp TFRecord là một định dạng nhị phân hướng bản ghi đơn giản mà nhiều ứng dụng TensorFlow sử dụng cho dữ liệu đào tạo. Lớp tf.data.TFRecordDataset cho phép bạn truyền trực tuyến nội dung của một hoặc nhiều tệp TFRecord như một phần của đường dẫn đầu vào.

Đây là một ví dụ bằng cách sử dụng tệp thử nghiệm từ Bảng hiệu Tên đường của Pháp (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 1s 0us/step
7913472/7904079 [==============================] - 1s 0us/step

Đối số filenames cho bộ khởi tạo TFRecordDataset có thể là một chuỗi, một danh sách các chuỗi hoặc một tf.Tensor của các chuỗi. Do đó, nếu bạn có hai bộ tệp cho mục đích đào tạo và xác thực, bạn có thể tạo một phương thức gốc tạo ra tập dữ liệu, lấy tên tệp làm đối số đầu vào:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

Nhiều dự án TensorFlow sử dụng các bản ghi tf.train.Example được tuần tự hóa trong các tệp TFRecord của họ. Chúng cần được giải mã trước khi có thể được kiểm tra:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

Sử dụng dữ liệu văn bản

Xem phần Tải văn bản để biết ví dụ kết thúc.

Nhiều bộ dữ liệu được phân phối dưới dạng một hoặc nhiều tệp văn bản. tf.data.TextLineDataset cung cấp một cách dễ dàng để trích xuất các dòng từ một hoặc nhiều tệp văn bản. Cho một hoặc nhiều tên tệp, một TextLineDataset sẽ tạo ra một phần tử có giá trị chuỗi trên mỗi dòng của các tệp đó.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

Đây là vài dòng đầu tiên của tệp đầu tiên:

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

Để thay thế các dòng giữa các tệp, hãy sử dụng Dataset.interleave . Điều này giúp việc trộn các tệp với nhau dễ dàng hơn. Đây là dòng đầu tiên, thứ hai và thứ ba từ mỗi bản dịch:

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

Theo mặc định, TextLineDataset tạo ra mọi dòng của mỗi tệp, điều này có thể không mong muốn, ví dụ: nếu tệp bắt đầu bằng dòng tiêu đề hoặc chứa các chú thích. Các dòng này có thể được loại bỏ bằng cách sử dụng các phép biến đổi Dataset.skip() hoặc Dataset.filter() . Tại đây, bạn bỏ qua dòng đầu tiên, sau đó lọc để chỉ tìm những người sống sót.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

Sử dụng dữ liệu CSV

Xem Tải tệp CSVTải dữ liệu gấu trúc để biết thêm ví dụ.

Định dạng tệp CSV là một định dạng phổ biến để lưu trữ dữ liệu dạng bảng ở dạng văn bản thuần túy.

Ví dụ:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()

Nếu dữ liệu của bạn vừa trong bộ nhớ thì phương thức Dataset.from_tensor_slices tương tự hoạt động trên từ điển, cho phép dễ dàng nhập dữ liệu này:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

Một cách tiếp cận có thể mở rộng hơn là tải từ đĩa khi cần thiết.

Mô-đun tf.data cung cấp các phương pháp để trích xuất bản ghi từ một hoặc nhiều tệp CSV tuân thủ RFC 4180 .

Hàm experimental.make_csv_dataset là giao diện cấp cao để đọc bộ tệp csv. Nó hỗ trợ suy luận kiểu cột và nhiều tính năng khác, như chia lô và xáo trộn, để làm cho việc sử dụng trở nên đơn giản.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 0 0 0]
features:
  'sex'               : [b'female' b'female' b'male' b'male']
  'age'               : [32. 28. 37. 50.]
  'n_siblings_spouses': [0 3 0 0]
  'parch'             : [0 1 1 0]
  'fare'              : [13.     25.4667 29.7    13.    ]
  'class'             : [b'Second' b'Third' b'First' b'Second']
  'deck'              : [b'unknown' b'unknown' b'C' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
  'alone'             : [b'y' b'n' b'n' b'y']

Bạn có thể sử dụng đối số select_columns nếu bạn chỉ cần một tập hợp con các cột.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 1 1 0]
  'fare'              : [ 7.05 15.5  26.25  8.05]
  'class'             : [b'Third' b'Third' b'Second' b'Third']

Ngoài ra còn có một lớp.CsvDataset experimental.CsvDataset cấp thấp hơn cung cấp khả năng kiểm soát chi tiết hơn. Nó không hỗ trợ suy luận kiểu cột. Thay vào đó, bạn phải chỉ định loại của mỗi cột.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

Nếu một số cột trống, giao diện cấp thấp này cho phép bạn cung cấp các giá trị mặc định thay vì các loại cột.

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset element_spec=TensorSpec(shape=(4,), dtype=tf.int32, name=None)>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

Theo mặc định, CsvDataset tạo ra mọi cột của mọi dòng của tệp, điều này có thể không được mong muốn, ví dụ: nếu tệp bắt đầu bằng dòng tiêu đề cần được bỏ qua hoặc nếu một số cột không được yêu cầu trong đầu vào. Có thể xóa các dòng và trường này bằng các đối số headerselect_cols tương ứng.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset element_spec=TensorSpec(shape=(2,), dtype=tf.int32, name=None)>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

Tiêu thụ bộ tệp

Có nhiều tập dữ liệu được phân phối dưới dạng một tập hợp các tệp, trong đó mỗi tệp là một ví dụ.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

Thư mục gốc chứa một thư mục cho mỗi lớp:

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

Các tệp trong mỗi thư mục lớp là ví dụ:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/5018120483_cc0421b176_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/8642679391_0805b147cb_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/8266310743_02095e782d_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13176521023_4d7cc74856_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/19437578578_6ab1b3c984.jpg'

Đọc dữ liệu bằng cách sử dụng hàm tf.io.read_file và trích xuất nhãn từ đường dẫn, trả về các cặp (image, label) :

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'daisy'

Kết hợp các phần tử tập dữ liệu

Lô đơn giản

Dạng đơn giản nhất của việc phân lô xếp n phần tử liên tiếp của một tập dữ liệu thành một phần tử duy nhất. Phép biến đổi Dataset.batch() thực hiện chính xác điều này, với các ràng buộc tương tự như toán tử tf.stack() , được áp dụng cho từng thành phần của các phần tử: nghĩa là đối với mỗi thành phần i , tất cả các phần tử phải có một tensor có cùng hình dạng chính xác.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

Trong khi tf.data cố gắng truyền tải thông tin hình dạng, cài đặt mặc định của Dataset.batch dẫn đến kích thước lô không xác định vì lô cuối cùng có thể không đầy. Lưu ý None s trong hình dạng:

batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.int64, name=None))>

Sử dụng đối số drop_remainder để bỏ qua lô cuối cùng đó và nhận được sự truyền bá hình dạng đầy đủ:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(7,), dtype=tf.int64, name=None), TensorSpec(shape=(7,), dtype=tf.int64, name=None))>

Kết hợp các máy căng với lớp đệm

Công thức trên áp dụng cho các tenxơ có cùng kích thước. Tuy nhiên, nhiều mô hình (ví dụ: mô hình trình tự) làm việc với dữ liệu đầu vào có thể có kích thước khác nhau (ví dụ: chuỗi có độ dài khác nhau). Để xử lý trường hợp này, phép chuyển đổi Dataset.padded_batch cho phép bạn ghép hàng loạt các bộ căng có hình dạng khác nhau bằng cách chỉ định một hoặc nhiều kích thước mà chúng có thể được đệm.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]

Phép biến đổi Dataset.padded_batch cho phép bạn đặt các vùng đệm khác nhau cho từng thứ nguyên của từng thành phần và nó có thể có độ dài thay đổi (được ký hiệu bằng None trong ví dụ trên) hoặc độ dài không đổi. Cũng có thể ghi đè giá trị đệm, giá trị này được mặc định là 0.

Quy trình đào tạo

Xử lý nhiều kỷ nguyên

API tf.data cung cấp hai cách chính để xử lý nhiều kỷ nguyên của cùng một dữ liệu.

Cách đơn giản nhất để lặp qua tập dữ liệu trong nhiều kỷ nguyên là sử dụng phép biến đổi Dataset.repeat() . Đầu tiên, hãy tạo một tập dữ liệu về dữ liệu titanic:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

Việc áp dụng phép biến đổi Dataset.repeat() không có đối số sẽ lặp lại đầu vào vô thời hạn.

Phép biến đổi Dataset.repeat nối các đối số của nó mà không báo hiệu sự kết thúc của một kỷ nguyên và sự bắt đầu của kỷ nguyên tiếp theo. Do đó, một Dataset.batch được áp dụng sau Dataset.repeat sẽ mang lại các lô phân chia ranh giới kỷ nguyên:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

png

Nếu bạn cần phân tách kỷ nguyên rõ ràng, hãy đặt Dataset.batch trước khi lặp lại:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

png

Nếu bạn muốn thực hiện một phép tính tùy chỉnh (ví dụ: để thu thập số liệu thống kê) vào cuối mỗi kỷ nguyên thì cách đơn giản nhất là khởi động lại quá trình lặp lại tập dữ liệu trên mỗi kỷ nguyên:

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

Ngẫu nhiên trộn dữ liệu đầu vào

Phép biến đổi Dataset.shuffle() duy trì một bộ đệm có kích thước cố định và chọn ngẫu nhiên phần tử tiếp theo một cách đồng nhất từ ​​bộ đệm đó.

Thêm chỉ mục vào tập dữ liệu để bạn có thể thấy hiệu ứng:

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.string, name=None))>

Vì kích thước vùng buffer_size là 100 và kích thước lô là 20, nên lô đầu tiên không chứa phần tử nào có chỉ số trên 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 52  94  22  70  63  96  56 102  38  16  27 104  89  43  41  68  42  61
 112   8]

Như với Dataset.batch thứ tự liên quan đến Dataset.repeat vấn đề.

Dataset.shuffle không báo hiệu kết thúc một kỷ nguyên cho đến khi bộ đệm xáo trộn trống. Vì vậy, một lần xáo trộn được đặt trước một lần lặp lại sẽ hiển thị mọi phần tử của một kỷ nguyên trước khi chuyển sang kỷ nguyên tiếp theo:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[509 595 537 550 555 591 480 627 482 519]
[522 619 538 581 569 608 531 558 461 496]
[548 489 379 607 611 622 234 525]
[ 59  38   4  90  73  84  27  51 107  12]
[77 72 91 60  7 62 92 47 70 67]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f7e7061c650>

png

Nhưng một lần lặp lại trước khi xáo trộn sẽ trộn các ranh giới kỷ nguyên với nhau:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[  6   8 528 604  13 492 308 441 569 475]
[  5 626 615 568  20 554 520 454  10 607]
[510 542   0 363  32 446 395 588  35   4]
[  7  15  28  23  39 559 585  49 252 556]
[581 617  25  43  26 548  29 460  48  41]
[ 19  64  24 300 612 611  36  63  69  57]
[287 605  21 512 442  33  50  68 608  47]
[625  90  91 613  67  53 606 344  16  44]
[453 448  89  45 465   2  31 618 368 105]
[565   3 586 114  37 464  12 627  30 621]
[ 82 117  72  75  84  17 571 610  18 600]
[107 597 575  88 623  86 101  81 456 102]
[122  79  51  58  80  61 367  38 537 113]
[ 71  78 598 152 143 620 100 158 133 130]
[155 151 144 135 146 121  83  27 103 134]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f7e706013d0>

png

Tiền xử lý dữ liệu

Phép biến đổi Dataset.map(f) tạo ra một tập dữ liệu mới bằng cách áp dụng một hàm f đã cho cho từng phần tử của tập dữ liệu đầu vào. Nó dựa trên hàm map() thường được áp dụng cho danh sách (và các cấu trúc khác) trong các ngôn ngữ lập trình hàm. Hàm f nhận các đối tượng tf.Tensor đại diện cho một phần tử duy nhất trong đầu vào và trả về các đối tượng tf.Tensor sẽ đại diện cho một phần tử duy nhất trong tập dữ liệu mới. Việc triển khai nó sử dụng các phép toán TensorFlow tiêu chuẩn để chuyển đổi một phần tử này thành một phần tử khác.

Phần này bao gồm các ví dụ phổ biến về cách sử dụng Dataset.map() .

Giải mã dữ liệu hình ảnh và thay đổi kích thước

Khi đào tạo mạng nơ-ron trên dữ liệu hình ảnh trong thế giới thực, thông thường cần phải chuyển đổi các hình ảnh có kích thước khác nhau thành một kích thước chung, để chúng có thể được ghép thành một kích thước cố định.

Xây dựng lại tập dữ liệu tên tệp hoa:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

Viết một hàm thao tác với các phần tử của tập dữ liệu.

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

Kiểm tra xem nó hoạt động.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

png

Ánh xạ nó qua tập dữ liệu.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

png

png

Áp dụng logic Python tùy ý

Vì lý do hiệu suất, hãy sử dụng các hoạt động TensorFlow để xử lý trước dữ liệu của bạn bất cứ khi nào có thể. Tuy nhiên, đôi khi sẽ hữu ích khi gọi các thư viện Python bên ngoài khi phân tích cú pháp dữ liệu đầu vào của bạn. Bạn có thể sử dụng phép tf.py_function() trong phép biến đổi Dataset.map() .

Ví dụ: nếu bạn muốn áp dụng một vòng quay ngẫu nhiên, mô-đun tf.image chỉ có tf.image.rot90 , không hữu ích lắm cho việc tăng cường hình ảnh.

Để chứng minh tf.py_function , hãy thử sử dụng hàm scipy.ndimage.rotate thay thế:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

Để sử dụng hàm này với Dataset.map , áp dụng các lưu ý tương tự như với Dataset.from_generator , bạn cần mô tả các hình dạng và kiểu trả về khi bạn áp dụng hàm:

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

png

Phân tích cú pháp thông báo bộ đệm giao thức tf.Example

Nhiều đường ống đầu vào trích xuất thông báo bộ đệm giao thức tf.train.Example từ định dạng TFRecord. Mỗi bản ghi tf.train.Example chứa một hoặc nhiều "tính năng" và đường dẫn đầu vào thường chuyển đổi các tính năng này thành tensor.

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

Bạn có thể làm việc với các tf.train.Example bên ngoài tf.data.Dataset để hiểu dữ liệu:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

png

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset element_spec=(TensorSpec(shape=(), dtype=tf.string, name=None), TensorSpec(shape=(), dtype=tf.string, name=None))>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

Cửa sổ chuỗi thời gian

Để biết ví dụ về chuỗi thời gian từ đầu đến cuối, hãy xem: Dự báo chuỗi thời gian .

Dữ liệu chuỗi thời gian thường được sắp xếp với trục thời gian nguyên vẹn.

Sử dụng Dataset.range đơn giản để chứng minh:

range_ds = tf.data.Dataset.range(100000)

Thông thường, các mô hình dựa trên loại dữ liệu này sẽ muốn có một lát thời gian liền kề.

Cách tiếp cận đơn giản nhất sẽ là hàng loạt dữ liệu:

Sử dụng batch

batches = range_ds.batch(10, drop_remainder=True)

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

Hoặc để đưa ra các dự đoán dày đặc một bước trong tương lai, bạn có thể thay đổi các tính năng và nhãn từng bước một so với nhau:

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

Để dự đoán toàn bộ cửa sổ thay vì một khoảng chênh lệch cố định, bạn có thể chia các lô thành hai phần:

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

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

Để cho phép một số chồng chéo giữa các tính năng của một lô và các nhãn của lô khác, hãy sử dụng Dataset.zip :

feature_length = 10
label_length = 3

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

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

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

Sử dụng window

Trong khi sử dụng Dataset.batch hoạt động, có những tình huống mà bạn có thể cần kiểm soát tốt hơn. Phương thức Dataset.window cung cấp cho bạn quyền kiểm soát hoàn toàn, nhưng cần phải cẩn thận: nó trả về một Dataset của các Tập Datasets . Xem cấu trúc Dataset để biết thêm chi tiết.

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>

Phương thức Dataset.flat_map có thể lấy một tập dữ liệu của các tập dữ liệu và làm phẳng nó thành một tập dữ liệu duy nhất:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

Trong hầu hết các trường hợp, bạn sẽ muốn .batch tập dữ liệu trước:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

Bây giờ, bạn có thể thấy rằng đối số shift kiểm soát mức độ di chuyển của mỗi cửa sổ.

Kết hợp điều này lại với nhau, bạn có thể viết hàm này:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

Sau đó, thật dễ dàng để trích xuất nhãn, như trước đây:

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

Lấy mẫu lại

Khi làm việc với tập dữ liệu rất mất cân bằng về lớp, bạn có thể muốn lấy mẫu lại tập dữ liệu. tf.data cung cấp hai phương pháp để thực hiện việc này. Tập dữ liệu gian lận thẻ tín dụng là một ví dụ điển hình cho loại vấn đề này.

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 2s 0us/step
69165056/69155632 [==============================] - 2s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

Bây giờ, hãy kiểm tra sự phân bố của các lớp, nó rất lệch:

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9956 0.0044]

Một cách tiếp cận phổ biến để đào tạo với một tập dữ liệu không cân bằng là cân bằng nó. tf.data bao gồm một số phương pháp cho phép dòng công việc này:

Lấy mẫu tập dữ liệu

Một cách tiếp cận để lấy lại mẫu tập dữ liệu là sử dụng sample_from_datasets . Điều này áp dụng hơn khi bạn có một data.Dataset riêng biệt cho từng lớp.

Ở đây, chỉ cần sử dụng bộ lọc để tạo chúng từ dữ liệu gian lận thẻ tín dụng:

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

Để sử dụng tf.data.Dataset.sample_from_datasets , hãy chuyển các tập dữ liệu và trọng số cho mỗi:

balanced_ds = tf.data.Dataset.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

Bây giờ tập dữ liệu tạo ra các ví dụ về từng lớp với xác suất 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 0 1 0 1 0 1 1 1 1]
[0 0 1 1 0 1 1 1 1 1]
[1 1 1 1 0 0 1 0 1 0]
[1 1 1 0 1 0 0 1 1 1]
[0 1 0 1 1 1 0 1 1 0]
[0 1 0 0 0 1 0 0 0 0]
[1 1 1 1 1 0 0 1 1 0]
[0 0 0 1 0 1 1 1 0 0]
[0 0 1 1 1 1 0 1 1 1]
[1 0 0 1 1 1 1 0 1 1]

Lấy lại mẫu từ chối

Một vấn đề với cách tiếp cận Dataset.sample_from_datasets ở trên là nó cần một tf.data.Dataset riêng cho mỗi lớp. Bạn có thể sử dụng Dataset.filter để tạo hai tập dữ liệu đó, nhưng điều đó dẫn đến tất cả dữ liệu được tải hai lần.

Phương thức data.Dataset.rejection_resample có thể được áp dụng cho tập dữ liệu để cân bằng lại nó, trong khi chỉ tải nó một lần. Các phần tử sẽ bị loại bỏ khỏi tập dữ liệu để đạt được sự cân bằng.

data.Dataset.rejection_resample nhận đối số class_func . class_func này được áp dụng cho từng phần tử tập dữ liệu và được sử dụng để xác định lớp mà một ví dụ thuộc về nhằm mục đích cân bằng.

Mục tiêu ở đây là cân bằng phân phối lable và các phần tử của creditcard_ds đã là các cặp (features, label) . Vì vậy, class_func chỉ cần trả về các nhãn đó:

def class_func(features, label):
  return label

Phương pháp lấy mẫu lại liên quan đến các ví dụ riêng lẻ, vì vậy trong trường hợp này, bạn phải unbatch nhóm dữ liệu trước khi áp dụng phương pháp đó.

Phương pháp cần có phân phối mục tiêu và tùy chọn ước tính phân phối ban đầu làm đầu vào.

resample_ds = (
    creditcard_ds
    .unbatch()
    .rejection_resample(class_func, target_dist=[0.5,0.5],
                        initial_dist=fractions)
    .batch(10))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py:5797: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

Phương thức rejection_resample trả về các cặp (class, example) trong đó class là đầu ra của class_func . Trong trường hợp này, example đã là một cặp (feature, label) , vì vậy hãy sử dụng map để thả bản sao bổ sung của các nhãn:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

Bây giờ tập dữ liệu tạo ra các ví dụ về từng lớp với xác suất 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
[0 1 1 1 0 1 1 0 1 1]
[1 1 0 1 0 0 0 0 1 1]
[1 1 1 1 0 0 0 0 1 1]
[1 0 0 1 0 0 1 0 1 1]
[1 0 0 0 0 1 0 0 0 0]
[1 0 0 1 1 0 1 1 1 0]
[1 1 0 0 0 0 0 0 0 1]
[0 0 1 0 0 0 1 0 1 1]
[0 1 0 1 0 1 0 0 0 1]
[0 0 0 0 0 0 0 0 1 1]

Điểm kiểm tra lặp lại

Tensorflow hỗ trợ lấy các điểm kiểm tra để khi quá trình đào tạo của bạn khởi động lại, nó có thể khôi phục điểm kiểm tra mới nhất để khôi phục hầu hết tiến trình của nó. Ngoài việc kiểm tra các biến mô hình, bạn cũng có thể kiểm tra tiến trình của trình lặp tập dữ liệu. Điều này có thể hữu ích nếu bạn có một tập dữ liệu lớn và không muốn khởi động tập dữ liệu từ đầu mỗi lần khởi động lại. Tuy nhiên, lưu ý rằng các điểm kiểm tra của trình vòng lặp có thể lớn, vì các phép biến đổi như shuffleprefetch yêu cầu các phần tử đệm bên trong trình vòng lặp.

Để bao gồm trình vòng lặp của bạn trong một điểm kiểm tra, hãy chuyển trình vòng lặp tới phương thức khởi tạo tf.train.Checkpoint .

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

Sử dụng tf.data với tf.keras

API tf.keras đơn giản hóa nhiều khía cạnh của việc tạo và thực thi các mô hình học máy. Các .fit().evaluate().predict() của nó hỗ trợ các tập dữ liệu làm đầu vào. Đây là tập dữ liệu nhanh và thiết lập mô hình:

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

Chuyển một tập dữ liệu về các cặp (feature, label) là tất cả những gì cần thiết cho Model.fitModel.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.5984 - accuracy: 0.7973
Epoch 2/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4607 - accuracy: 0.8430
<keras.callbacks.History at 0x7f7e70283110>

Nếu bạn truyền một tập dữ liệu vô hạn, chẳng hạn bằng cách gọi Dataset.repeat() , bạn cũng chỉ cần truyền đối số steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4574 - accuracy: 0.8672
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4216 - accuracy: 0.8562
<keras.callbacks.History at 0x7f7e144948d0>

Để đánh giá, bạn có thể vượt qua một số bước đánh giá:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4350 - accuracy: 0.8524
Loss : 0.4350026249885559
Accuracy : 0.8524333238601685

Đối với tập dữ liệu dài, hãy đặt số bước để đánh giá:

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4345 - accuracy: 0.8687
Loss : 0.43447819352149963
Accuracy : 0.8687499761581421

Các nhãn không được yêu cầu khi gọi Model.predict .

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

Nhưng các nhãn sẽ bị bỏ qua nếu bạn chuyển tập dữ liệu có chứa chúng:

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)