شکل 13-2. بارگذاری و Preprocess داده از چند CSV File
Prefetching و Pipeline پرسرعت
prefetch(1) Dataset را وادار میکند تا تا حد امکان همیشه یک Batch از Consumer جلوتر باشد. در حالی که GPU Batch فعلی را Train میکند، CPU میتواند Batch بعدی را از Disk بخواند و Preprocess کند. اگر interleave() و map() نیز با Parallelism اجرا شوند، آمادهسازی Batch میتواند در چند Core انجام شود و GPU کمتر منتظر Data بماند.
شکل 13-3. همپوشانی CPU و GPU با Prefetching
در بیشتر موارد Prefetch یک Batch کافی است، ولی میتوان مقدار بزرگتر یا tf.data.AUTOTUNE را انتخاب کرد. اگر Dataset پس از Load و Preprocess در RAM جا میشود، cache() میتواند Training Epochهای بعدی را سریع کند. Cache معمولاً بعد از Load/Preprocess و قبل از Shuffle/Repeat/Batch/Prefetch قرار میگیرد تا دادهٔ پردازششده دوباره از Disk خوانده نشود، اما Random Order در Epochهای مختلف همچنان حفظ شود.
استفاده از Dataset در Keras
وقتی Dataset Itemهایی به شکل (features, labels) و Batch مناسب تولید کند، مستقیماً به fit() داده میشود:
train_set = csv_reader_dataset(train_filepaths)
valid_set = csv_reader_dataset(valid_filepaths, seed=None)
test_set = csv_reader_dataset(test_filepaths, seed=None)
model.fit(train_set, validation_data=valid_set, epochs=10)
model.evaluate(test_set)
Keras خودش روی Batchهای Dataset Iterate میکند. همین Pipeline را میتوان در Training Loop سفارشی نیز استفاده کرد؛ کافی است روی Dataset Iterate کنیم.
فرمت TFRecord
TFRecord فرمت Binary استاندارد TensorFlow برای ذخیرهٔ حجم زیاد Data است. هر Record یک Byte String با طول دلخواه است و فایل ساختار سادهای دارد که خواندن Sequential آن سریع است. برای Write:
with tf.io.TFRecordWriter("my_data.tfrecord") as f:
f.write(b"This is the first record")
f.write(b"And this is the second record")
برای Read از TFRecordDataset استفاده میکنیم:
filepaths = ["my_data.tfrecord"]
dataset = tf.data.TFRecordDataset(filepaths)
for item in dataset:
print(item)
اگر چند TFRecord File وجود دارد، num_parallel_reads میتواند Fileها را موازی بخواند و Recordها را Interleave کند.
TFRecord فشرده
برای صرفهجویی در Disk/Network میتوان Compression را هنگام Write و Read فعال کرد:
options = tf.io.TFRecordOptions(compression_type="GZIP")
with tf.io.TFRecordWriter("my_data.tfrecord.gz", options) as f:
f.write(b"compressed record")
dataset = tf.data.TFRecordDataset(
["my_data.tfrecord.gz"], compression_type="GZIP")
آشنایی کوتاه با Protocol Buffer
Recordهای TFRecord اغلب شامل Protocol Buffer یا Protobuf هستند. Protobuf فرمت Binary متنباز، Compact و Cross-Language است. Schema در File با پسوند .proto تعریف میشود و Compiler برای Languageهای مختلف Class تولید میکند. مثال مفهومی:
syntax = "proto3";
message Person {
string name = 1;
int32 id = 2;
repeated string email = 3;
}
بعد از Compile، Object را میتوان Construct، Serialize و Parse کرد. TensorFlow برای Protobufهای عمومی API سطح بالای کاملی ارائه نمیکند، اما برای Protobufهای استاندارد مورد استفاده در TFRecord، Operationهای اختصاصی دارد.
Protobufهای TensorFlow: Example و Feature
رایجترین ساختار tf.train.Example است. یک Example در اصل Dictionaryای از Featureهای نامدار است. هر Feature یکی از سه نوع List را نگه میدارد:
BytesList برای Byte Stringها؛
FloatList برای Float؛
Int64List برای Integer 64-bit.
برای نمونه یک Contact با Name، ID و چند Email:
from tensorflow.train import BytesList, FloatList, Int64List
from tensorflow.train import Feature, Features, Example
person_example = Example(
features=Features(
feature={
"name": Feature(bytes_list=BytesList(value=[b"Alice"])),
"id": Feature(int64_list=Int64List(value=[123])),
"emails": Feature(bytes_list=BytesList(
value=[b"a@b.com", b"c@d.com"]))
}))
Object با SerializeToString() به Byte String تبدیل و در TFRecord نوشته میشود:
with tf.io.TFRecordWriter("my_contacts.tfrecord") as f:
for _ in range(5):
f.write(person_example.SerializeToString())
در Production معمولاً Script تبدیل، Source Format را میخواند، برای هر Instance یک Example میسازد، Recordها را Shuffle میکند و آنها را در چند TFRecord File ذخیره میکند.
Load و Parse کردن Example
برای Parse باید Description هر Feature را مشخص کنیم. Feature با طول ثابت از FixedLenFeature و Feature با تعداد Value متغیر از VarLenFeature استفاده میکند:
feature_description = {
"name": tf.io.FixedLenFeature([], tf.string, default_value=""),
"id": tf.io.FixedLenFeature([], tf.int64, default_value=0),
"emails": tf.io.VarLenFeature(tf.string),
}
def parse(serialized_example):
return tf.io.parse_single_example(
serialized_example, feature_description)
dataset = tf.data.TFRecordDataset(
["my_contacts.tfrecord"]).map(parse)
Feature ثابت به Tensor معمولی و Feature متغیر به SparseTensor تبدیل میشود. در صورت نیاز tf.sparse.to_dense() یا Attribute values برای دستیابی به مقدارها استفاده میشود. برای Parse کردن Batch بهجای تک Record، tf.io.parse_example() مناسب است.
BytesList میتواند هر Binary Payload را نگه دارد: Image را میتوان با tf.io.encode_jpeg() ذخیره و با tf.io.decode_jpeg() یا tf.io.decode_image() بازیابی کرد. هر Tensor دلخواه نیز با tf.io.serialize_tensor() Serialize و با tf.io.parse_tensor() Parse میشود.
Example برای بسیاری از Datasetها کافی است، اما Dataهایی با Listهای Nested، مانند Document شامل Sentenceهای متغیر و هر Sentence شامل Wordهای متغیر، با SequenceExample طبیعیتر نمایش داده میشوند؛ این موضوع در بخش بعد ادامه مییابد.