TensorFlow Fundamentals · lesson 22/27
TFRecords & Dataset API
Training throughput is usually set by input, not compute. TFRecords store examples in a compact binary format that streams efficiently, and tf.data.Dataset is the pipeline that reads, decodes, augments, and batches them. Learned together, they keep the accelerator fed.
The idea
- A TFRecord file is a sequence of length-prefixed records; here each record is a serialized
tf.train.Example. - An
Examplemaps string keys totf.train.Featurevalues, which hold aBytesList,FloatList, orInt64List. Images are stored as raw encoded bytes, not decoded arrays. - The feature schema lives in your code, not the file, so reads need
tf.io.FixedLenFeature([], tf.string)and friends to parse. tf.datais lazy and graph-based:map,batch, andprefetchbuild a pipeline the runtime can parallelize.
Ordering rules of thumb: shuffle before batch, cache before an expensive map, prefetch(tf.data.AUTOTUNE) last, and num_parallel_calls on any map doing real work.
Worked example
Write 1,000 tiny (28, 28) float arrays.
- Each example holds
{"image": bytes_feature(x.tobytes()), "label": int64_feature(y)}. - Write in shards of about 200 records:
part-00000.tfrecord, and so on. - Read with
tf.data.TFRecordDataset(files), thenmap(_parse),shuffle(1000),batch(32),prefetch(AUTOTUNE). _parsesetsimage = tf.io.decode_raw(f["image"], tf.float32)and reshapes to(28, 28).
Sharding matters: one giant file serializes reads, while many files let interleave run in parallel.
In code
import numpy as np, tensorflow as tf
def _bytes(x): return tf.train.Feature(bytes_list=tf.train.BytesList(value=[x]))
def _int(x): return tf.train.Feature(int64_list=tf.train.Int64List(value=[x]))
with tf.io.TFRecordWriter("/tmp/data/part-00000.tfrecord") as w:
for i in range(1000):
img = np.random.rand(28, 28).astype("float32")
ex = tf.train.Example(features=tf.train.Features(feature={
"image": _bytes(img.tobytes()),
"label": _int(i % 10),
}))
w.write(ex.SerializeToString())
def parse(proto):
f = tf.io.parse_single_example(proto, {
"image": tf.io.FixedLenFeature([], tf.string),
"label": tf.io.FixedLenFeature([], tf.int64),
})
return tf.reshape(tf.io.decode_raw(f["image"], tf.float32), (28, 28)), f["label"]
ds = (tf.data.TFRecordDataset(["/tmp/data/part-00000.tfrecord"])
.map(parse, num_parallel_calls=tf.data.AUTOTUNE)
.shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE))
print(next(iter(ds))[0].shape) # (32, 28, 28)Check yourself
- Why does the reader need the feature schema when the writer already serialized the data?
- Why should
shufflecome beforebatch? - What does
prefetch(AUTOTUNE)overlap, and why does it help?
Key takeaways
- TFRecords store raw bytes; the parse schema reconstructs structure at read time.
tf.databuilds a lazy pipeline that overlaps input work with training steps.- Sharding, shuffle-before-batch, and prefetch are the three highest-value defaults.