Chuẩn bị dữ liệu với Tensorflow Dataset

1200x630

Vào một ngày đẹp trời nào đó, lúc đang train thử một model, tôi với tiêu dùng câu lệnh nvidia-smi -l để check xem gpu máy mình với đang hoạt động hay ko và chợt nhìn thấy với gì đó sai sai. GPU máy tôi đang ko hoạt động hiệu quả.

Bạn đang xem: Chuẩn bị dữ liệu với Tensorflow Dataset

Ví dụ minh họa: Như bạn thấy ở trên, GPU Memory Usage sắp full 11gb nhưng Volatile GPU-Util lại chỉ với 0%.

Vậy vì sao lại xảy ra vấn đề này và cách khắc phục như thế nào. To be …

I. Nguyên nhân

Nếu bạn thấy GPU Util nhỏ hơn 80% thì đây là tín hiệu của việc load data đang với vấn đề (input pipeline bottleneck). Nói chung là GPU phải ngồi đợi CPU load xong dữ liệu, mà việc đợi như thế này ngốn khá nhiều thời kì.

II. Cách khắc phục

Điều mà bạn muốn hiện nay là CPU cứ việc chuẩn bị dữ liệu trong lúc đó GPU cứ việc train. Nói chung là tiến trình song song, ở đây gọi là (prefetching): Nhưng mà, mà, mà, lỡ tiến trình chuẩn bị batch lâu hơn tiến trình train model thì chẳng phải GPU mãi chết đứng, cứ phải đợi thằng CPU xử lý xong batch sau như hòn vọng phu sao

Thực ra nếu vọc lại quá trình xử lý dữ liệu mà ta đang làm là xử lý tuần tự, tức là batch này xong thì mới tới batch sau. Vậy ta với thể tăng tốc việc chuẩn bị batch bằng cách chạy song song những tiến trình tiền xử lý: Tương tự ta còn với thể chạy song song I/O (input/output): Dông dài đủ rồi, vào việc chính nào, như tiêu đề để tránh khỏi bị bottleneck ở đầu vào nên tôi tiêu dùng Tensorflow Dataset API để xử lý dữ liệu.

The tf.data API enables you to build complex input pipelines from simple, reusable pieces. For example, the pipeline for an image model might aggregate data from files in a distributed file system, apply random perturbations to each image, and merge randomly selected images into a batch for training. The pipeline for a text model might involve extracting symbols from raw text data, converting them to embedding identifiers with a lookup table, and batching together sequences of different lengths. The tf.data API makes it possible to handle large amounts of data, read from different data formats, and perform complex transformations. 

Trên đây là lời giới thiệu của TensorFlow lúc bạn khởi đầu tìm hiểu cách tạo Một TensorFlow input pipeline. Tóm gọn lại là dòng API này tương trợ cho dữ liệu ảnh và dữ liệu text, với thể xử lý dữ liệu to, đọc được nhiều format khác nhau, bla…bla…

tf.dataAPI giới thiệu Một abtract class Dataset tf.data.Dataset chứa một hoặc nhiều phương tiện tương trợ xử lý dữ liệu. Ví dụ: trong Một image pipeline, Một điểm dữ liệu chứa Một cặp tensors: image và label.

Mang Hai cách khác nhau để tạo Một tập dữ liệu:

  • build từ source như raw data lưu ở trong ổ cứng (images) hay raw data trong Một hoặc nhiều file (text)
  • build từ Một hoặc nhiều tf.data.Dataset objects.

Vì mình đang tham khảo cách tạo Một input pipeline trên TensorFlowDoc nên mạn phép viết bài với cấu trúc giống hoặc sắp giống (vì với thể ngắn hơn)

Basic mechanics

Giả sử ta với Một bộ dữ liệu là Một tập ảnh chẳng hạn, tôi đang code trên ubuntu nên nhờ lib os mà extract ra Một list đường dẫn ảnh

all_image_paths 
'../input/train_images/1.jpg', '../input/train_images/2.jpg', '../input/train_images/3.jpg', '../input/train_images/4.jpg', '../input/train_images/5.jpg', 

Lúc với Một list đường dẫn ảnh, tôi tiêu dùng hàm from_tensor_slices() để tạo Một Dataset objects

Example 1

dataset = tf.data.Dataset.from_tensor_slices(all_image_paths) print(dataset)  

Example 2

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1]) print(dataset)  

Ngoài hàm from_tensor_slices(), ta còn với thể sử dụng hàm from_tensors(). Điểm khác nhau của Hai hàm này là from_tensors() trả về dataset chứa duy nhất Một phần tử, trái lại from_tensor_slices() trả về dataset chứa nhiều phần tử.

dataset = tf.data.Dataset.from_tensors([1, 2, 3]) print(dataset)  

Nếu dữ liệu của bạn với format TFRecord, bạn với thể tiêu dùng tf.data.TFRecorDataset()

Sau lúc đã với cho mình Một Dataset object, bạn với thể tùy chỉnh, biến đổi, tạo thành Một bộ dữ liệu mới nhờ những phương thức của Dataset object như Dataset.map(), Dataset.batch(), … Nếu bạn muốn check dữ liệu với thể wrap dataset với hàm iter() bởi Dataset object là Một Python iterable

print(elem.numpy() for elem in dataset) print(next(iter(dataset)).numpy()) 

Dataset structure

Phần này mình nói qua thôi, bởi ko quan yếu cho lắm (aka với mình), bạn tiêu dùng hàm Dataset.element_spec với thể quan sát type của mỗi phần tử trong Một dataset

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10])) # dữ liệu với shape [4, 10] == 4 phần tử với shape (10,) print(dataset1.element_spec) TensorSpec(shape=(10,), dtype=tf.float32, name=None) # Một điểm dữ liệu 

Mang những dạng cấu trúc dữ liệu sau: tf.TypeSpec, tf.Tensor, tf.sparse.SparseTensor, tf.RaggedTensor, tf.TensorArray, tf.data.Dataset.

Reading input data

Như tiêu đề, tiêu dùng tf.data.Dataset để transform những dạng dữ liệu.

  • Với numpy arrays: nếu bạn với thể load tất cả dữ liệu đầu vào mà ko bị lỗi là thiếu memory thì xin chúc mừng, bạn chỉ cần thuần tuý convert thành dạng tf.Tensor object là xong. VD:
train_dataset = tf.data.Dataset.from_tensor_slices((train_examples, train_labels)) test_dataset = tf.data.Dataset.from_tensor_slices((test_examples, test_labels)) 
  • Với python generator: ko khuyến khích tiêu dùng do khó scale. Đầu vào Một hàm generator (not iterator), cho phép tái sử dụng generator nhìu lần . VD:

Đây là Một hàm generator

def count(stop): i = 0 while i<stop: yield i i += Một print(count(5))  

Đây là ví dụ về việc muốn tái sử dụng lại generator thì phải duplicate generator

y = FunctionWithYield() y, y_backup = tee(y) for x in y: print(x) for x in y_backup: print(x) 

Nhưng chúng ta đang sử dụng tf dataset api. Ở đây chúng ta với hàm Dataset.from_generator() chuyên nhận generator

@staticmethod from_generator( generator, output_types, output_shapes=None, args=None ) # args: param truyền vào generator nếu với, nói chung là dòng stop ở hàm count() trên kia # output_types: cần vì tf.data build tf.Graph và những cạnh của graph đòi hỏi tf.dtype # output_shapes: ko cần nhưng khuyến khích thêm bởi nhiều operation của tensorflow ko tương trợ unknown rank (rank là bậc của tensor như scalar, vector, matrix) 
ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), ) print(ds_counter)  

Một VD nữa về keras ImageDataGenerator trong tensorflow guide

Down về bộ dữ liệu hoa

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 [==============================] - 113s 0us/step 

Tạo Một generator từ bộ dữ liệu trên

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20) 

Convert về tf dataset

ds = tf.data.Dataset.from_generator(img_gen.flow_from_directory, args=[flowers], output_types=(tf.float32, tf.float32), output_shapes=([32,256,256,3], [32,5])) print(ds)  # or without output_shapes ds = tf.data.Dataset.from_generator(img_gen.flow_from_directory, args=[flowers], output_types=(tf.float32, tf.float32)) print(ds) <FlatMapDataset shapes: (, ), types: (tf.float32, tf.float32)> 
  • Với TFRecord data:

Trước nhất ta cần hiểu Một chút tfrecord data format: là Một format thuần tuý chứa những bản ghi là những chuỗi binary, nói thuần tuý nhưng lúc convert data sang tfrecord format cũng khá mệt . Ok chỉ cần biết thế thôi, class mà chúng ta sử dụng là tf.data.TFRecordDataset

tf.data.TFRecordDataset(filenames, compression_type=None, buffer_size=None, num_parallel_reads=None) # filenames: Một tensor tên file ([filename]) hoặc tf.data.Dataset chứa nhiều tên file # compression_type: định dạng file # buffer_size: số bytes thông qua read buffer # num_parallel_reads: số files đọc song song 

Mình lấy VD của tensorflow guide đi. First, download bộ dữ liệu French Street Name Signs

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 [==============================] - 4s 1us/step 

Convert TFRecord to TFRecordDataset

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file]) print(dataset)  

Để rà soát dữ liệu chúng ta tiêu dùng serialized tf.train.Example (dòng này thường đồng hành với TFRecord để dễ decode numpy)

raw_example = next(iter(dataset)) parsed = tf.train.Example.FromString(raw_example.numpy()) print(parsed.features.feature['image/text']) bytes_list{value: "Rue Perreyon"} 
  • Với dữ liệu là text: tf.data.TextLineDataset sẽ giúp bạn trích xuất từng dòng trong Một hoặc nhiều file text.
tf.data.TextLineDataset(filenames, compression_type=None, buffer_size=None, num_parallel_reads=None) 

VD: tôi với 1 file.txt

I use Tensorflow You use PyTorch Both are great 

Đọc file này = API

dataset = tf.data.TextLineDataset("file.txt") print(dataset)  print(next(iter(data)))  

Để shuffle những line nếu đọc từ nhiều file khác nhau, ta với thể tiêu dùng Dataset.interleave

files_ds = tf.data.Dataset.from_tensor_slices(file_paths) # file_paths: Một líst những file lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3) print(lines_ds)  

Nếu bạn gặp phải header hay comment lúc đọc trong file thì với thể sử dụng Dataset.skip() hoặc Dataset.filter(). Ví dụ cụ thể bạn với thể tham khảo trên guide của tensorflow (bởi nó dài)

  • Với dữ liệu CSV: nếu ko sợ tràn ram thì bạn với thể tiêu dùng Dataset.from_tensor_slices, do ví dụ cũng dài nên bạn thông cảm tham khảo trong guide của tensorflow.

tf.data sản xuất nhiều phương thức trích xuất bản ghi từ Một hoặc nhiều file CSV với định dạng chuẩn RFC 4180.

experimental.make_csv_dataset đọc file csv, tương trợ đọc theo cột, shuffle, batch dữ liệu, v.v…

Tương tự, experimental.CsvDataset cũng là class đọc file csv, nhưng ko tương trợ đọc theo cột.

  • Với Một sets files: tức thị mỗi file là Một điểm dữ liệu, thường thì ta tiêu dùng thư viện glob để đọc file recursive. Trong API, với thể tiêu dùng Dataset.list_files, cụ thể tôi tiêu dùng bộ dữ liệu hoa ở trên

Ở đây ví dụ đầu tôi thử tiêu dùng glob

import pathlib flowers_root = pathlib.Path(flowers_root) print([item.name for item in flowers_root.glob("*")]) 
sunflowers daisy LICENSE.txt roses tulips dandelion 

Với kết quả tương tự cũng thu được lúc tiêu dùng Dataset.list_files

import pathlib flowers_root = pathlib.Path(flowers_root) list_ds = tf.data.Dataset.list_files(str(flowers_root/'*')) print([f.numpy() for f in list_ds.take(5)]) 
b'/home/username/.keras/datasets/flower_photos/roses' b'/home/username/.keras/datasets/flower_photos/tulips' b'/home/username/.keras/datasets/flower_photos/sunflowers' b'/home/username/.keras/datasets/flower_photos/LICENSE.txt' b'/home/username/.keras/datasets/flower_photos/daisy' 

Batching dataset elements

Tiếp, lúc bạn với một đống dữ liệu, bạn ko thể nào cho tất cả vào model để train được mà cần phải chia ra thành từng batch một, ở đây tôi tiêu dùng Dataset.batch để chia ra data. Ví dụ

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) # mỗi batch bao gồm 4 điểm dữ liệu # Lấy 4 batch 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])] 

Do nhiều lúc dữ liệu ko chia đều được, nên batch_dataset trả về để batch_size unknown

print(batched_dataset)  
batched_dataset = dataset.batch(7, drop_remainder=True) print(batched_dataset)  

Từ từ, nếu những tensors đều với cùng size thì cách chia trên sẽ chả với vấn đề gì, nhưng thường thì dữ liệu trong nhiều bài toán khá là rộng rãi, với chiều dài khác nhau. Thế nên API sản xuất cho chúng ta Dataset.padded_batch sẽ biến những tensors với độ dài khác nhau trong Một batch thành với độ dài bằng nhau.

A = tf.data.Dataset.range(1, 5, output_type=tf.int32).map(lambda x: tf.fill([x], x)) for element in A.as_numpy_iterator(): print(element) B = A.padded_batch(4) # default value là 0 for element in B.as_numpy_iterator(): print(element) 
[1] [2 2] [3 3 3] [4 4 4 4] [[1 0 0 0] [2 2 0 0] [3 3 3 0] [4 4 4 4]] 

Training workflows

tf.data sản xuất Hai cách để train batch qua nhiều epoch.

  • Cách 1: Tiêu dùng Dataset.repeat().
data_batches = data.batch(128).repeat(3) 
  • Cách 2: Custom hàm train
epochs = 3 dataset = data.batch(128) for epoch in range(epochs): for batch in dataset: print(batch.shape) print("End of epoch: ", epoch) 

Việc custom sẽ với lợi hơn, bạn biết đấy vì với nhiều model ko chỉ cho chạy epoch là xong chuyện

Trước lúc tạo batch, ta cần shuffle dữ liệu bằng Dataset.shuffle

Preprocessing data

Sau lúc đã biến đổi dữ liệu thành Dataset, với một hôm bạn muốn thêm thắt, sửa lại dữ liệu Một chút, bạn ko thể nào load lại đống dữ liệu rồi lại chuyển về tensorflow dataset nhỉ. Đừng lo, Tensorflow Dataset API với sản xuất hàm Dataset.map(f). Hàm này hoạt động ra sao ??

Chắc bạn cũng biết hàm map của python, đầu vào là Một function và Một list, mỗi phần tử của list sẽ trở thành param truyền vào function kia và trả về kết quả. Thì hàm Dataset.map(f) cũng với tác dụng tương tự với hàm map của python, hàm f sẽ nhận param là 1 tf.Tensor và trả về 1 tf.Tensor mới

VD:

import tensorflow as tf dataset = tf.data.Dataset.range(3) print([i.numpy() for i in dataset]) [0, 1, 2] dataset = dataset.map(lambda x: x + 2) print([i.numpy() for i in dataset]) [2, 3, 4] 

Decoding image data and resizing it

VD:

Mình sẽ tiêu dùng bộ dữ liệu hoa làm ví dụ test cho bạn

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

Mình lấy luôn ví dụ trên tensorflow guide, đỡ phải nghĩ . Tạo hàm xử lý ảnh với param là đường dẫn ảnh

def parse_image(filename): parts = tf.strings.split(filename, os.sep) label = parts[-2] # đọc file, image là một tensor với type string binary image = tf.io.read_file(filename) # decode từ tensor về ảnh (cũng là tensor nhưng với shape là w, h, 3) image = tf.image.decode_jpeg(image) # convert dtype từ uint8 -> float32 image = tf.image.convert_image_dtype(image, tf.float32) # resize ảnh image = tf.image.resize(image, [128, 128]) return image, label 

Test thử với Một ảnh trong bộ dữ liệu

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) 

Vận dụng hàm parse_image cho tất cả phần tử trong bộ dữ liệu

images_ds = list_ds.map(parse_image) for image, label in images_ds.take(2): show(image, label) 

Applying arbitrary Python logic

Sử dụng TensorFlow với thể tăng hiệu suất xử lý ảnh nhưng việc gọi thư viện Python bên ngoài vào để tương trợ xử lý ảnh vẫn phải với bởi với Một số thư viện tương trợ tốt hơn tensorflow nên bên tensorflow cũng sản xuất hàm tf.py_function.

Dưới đây là ví dụ chỉ ứng dụng thư viện spicy để xử lý ảnh

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) 

Wrap hàm random_rotate_image với hàm tf.py_function

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 

Time series windowing

Dữ liệu time series với dạng:

[0 1 2 3 4 5 6 7 8 9] 

Như tiêu đề, đây là Một trick để xử lý dạng dữ liệu time series, thay đổi vị trí, hay dịch chuyển sang trái-phải để thêm hoặc xóa dữ liệu. Ở đây tôi chỉ giới thiệu phương thức tiêu dùng batch để tách chuỗi như mong muốn, còn phương thức tiêu dùng window những bạn tham khảo thêm tensorflow guide.

# gen dữ liệu mẫu từ 0 tới 100 range_ds = tf.data.Dataset.range(100) # tạo batch chứa 10 điểm dữ liệu 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] 

Biến đổi dữ liệu, shift Một phần tử liên kết với phần tử trước hoặc sau

def dense_1_step(batch): 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ựa trên trick này ta còn với thể dự đoán những điểm dữ liệu sau do dữ liệu time series với tính tuần tự, tăng lên và ko giảm đi.

batches = range_ds.batch(15, drop_remainder=True) def label_next_5_steps(batch): return (batch[:-5], batch[-5:]) 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] 

Resampling

Lúc làm việc với bộ dữ liệu ko thăng bằng, ví như Một bộ dữ liệu với Hai class A và B, bên A với 100 features trong lúc bên B chỉ với 30. Chính vì vậy bạn cần resample bộ dữ liệu. tf.data sản xuất Hai cách để thực hiện.

  • Datasets sampling

Sử dụng tf.data.experimental.sample_from_datasets

tf.data.experimental.sample_from_datasets(datasets, weights=None, seed=None) # datasets: bộ dữ liệu # weights: độ quan yếu của feature tác động tới xác suất được lấy mẫu # seed: nói chung liên quan tới cơ chế random (tôi ko tìm hiểu sâu :() 
  • Rejection resampling

Hàm tf.data.experimental.sample_from_datasets với Một vấn đề là nó cần tf.data.Dataset riêng của mỗi lớp, giả sử với 10 lớp thì muốn tăng dữ liệu thì hàm sample_from_datasets cần 10 bộ dữ liệu từ 10 lớp kể trên. Hàm Dataset.filter khắc phục vấn đề này nhưng kết quả sẽ bị lặp lại Hai lần.

Hàm data.experimental.rejection_resample nhận Một dataset và tái thăng bằng nó, trong lúc chỉ load đúng Một lần. Nói chung như tên, hàm này loại bỏ Một vài yếu tố để tái thăng bằng bộ dữ liệu, trái ngược với cách trên.

tf.data.experimental.rejection_resample(class_func, target_dist, initial_dist=None, seed=None) def class_func(features, label): return label 

Thực sự do mỏi tay quá nên tôi xin kết thúc ở đây, bài viết chưa được hoàn thiện, còn thiếu nhiều tình huống khác trong xử lý dữ liệu, nhưng cũng đem lại Một dòng nhìn cơ bản về API Dataset của Tensorflow. Chi tiết những bạn với thể tham khảo thêm những link dưới đây (do mình cũng tham khảo từ đó ra)

  • https://www.tensorflow.org/guide/data
  • https://cs230.stanford.edu/blog/datapipeline/#an-overview-of-tfdata
  • https://stackoverflow.com/questions/47298447/how-to-fix-low-volatile-gpu-util-with-tensorflow-gpu-and-keras

Was this helpful?

0 / 0

Bạn đang đọc bài viết từ chuyên mục Tổng hợp tại website https://tungchinguyen.com.