The Story — Shipping Containers
Training data has the same problem. A million tiny image files are slow to open one by one, especially from cloud storage. A TFRecord file is a container: it packs many examples into one big file that is read from start to end, very fast. Each example inside is a standard "parcel" called tf.train.Example.
tf.train.Example — The Standard Parcel
An Example is a dictionary from feature names to lists of values. There are only three list types:
import tensorflow as tf
def bytes_feature(v): return tf.train.Feature(bytes_list=tf.train.BytesList(value=v))
def float_feature(v): return tf.train.Feature(float_list=tf.train.FloatList(value=v))
def int64_feature(v): return tf.train.Feature(int64_list=tf.train.Int64List(value=v))
example = tf.train.Example(features=tf.train.Features(feature={
"city": bytes_feature([b"Baddi"]),
"income": float_feature([91000.0]),
"age": int64_feature([47]),
"scores": float_feature([0.5, 0.9, 0.2]),
}))
data = example.SerializeToString(deterministic=True)
print(example)
print("serialized size:", len(data), "bytes")
Try It — Example Builder
Add features, pick their type and type values separated by commas. The builder shows the Python code, the parsing spec, the exact serialized size and the raw bytes — computed with the real protobuf encoding rules. Notice how small integers take fewer bytes than floats.
Python
Parsing spec
Writing TFRecord Files
import numpy as np, os
rng = np.random.default_rng(0)
def make_example(i):
return tf.train.Example(features=tf.train.Features(feature={
"x": float_feature(rng.normal(size=8).astype("float32").tolist()),
"label": int64_feature([int(i % 3)]),
"id": bytes_feature([f"sample-{i}".encode()]),
})).SerializeToString()
with tf.io.TFRecordWriter("/tmp/data.tfrecord") as w:
for i in range(1000):
w.write(make_example(i))
gzip = tf.io.TFRecordOptions(compression_type="GZIP")
with tf.io.TFRecordWriter("/tmp/data.tfrecord.gz", options=gzip) as w:
for i in range(1000):
w.write(make_example(i))
print("plain:", os.path.getsize("/tmp/data.tfrecord"), "bytes")
print("gzip :", os.path.getsize("/tmp/data.tfrecord.gz"), "bytes")
Here GZIP cut the file by more than half, because the feature names and labels repeat in every record. Random float values themselves barely compress, and data that is already compressed (JPEG, PNG) hardly shrinks at all. Compression saves disk and network, but costs some CPU when reading.
Reading and Parsing
A TFRecordDataset gives raw bytes. You describe the features you expect, then parse.
feature_spec = {
"x": tf.io.FixedLenFeature([8], tf.float32),
"label": tf.io.FixedLenFeature([], tf.int64),
"id": tf.io.FixedLenFeature([], tf.string),
}
raw = tf.data.TFRecordDataset("/tmp/data.tfrecord")
print("raw element:", raw.element_spec)
def parse(record):
ex = tf.io.parse_single_example(record, feature_spec)
return ex["x"], ex["label"]
ds = raw.map(parse, num_parallel_calls=tf.data.AUTOTUNE)
for x, y in ds.take(2):
print(x.numpy().round(2), y.numpy())
Faster: Batch First, Then Parse the Whole Batch
def parse_batch(records):
ex = tf.io.parse_example(records, feature_spec) # parses many at once
return ex["x"], ex["label"]
fast = (tf.data.TFRecordDataset("/tmp/data.tfrecord.gz", compression_type="GZIP")
.batch(32)
.map(parse_batch, num_parallel_calls=tf.data.AUTOTUNE)
.prefetch(tf.data.AUTOTUNE))
x, y = next(iter(fast))
print(x.shape, y.shape, y[:10].numpy())
| Spec | Use for | Gives |
|---|---|---|
FixedLenFeature([n], dtype) | Every example has exactly n values | A dense tensor |
FixedLenFeature([], dtype, default_value=…) | One value; may be missing | A scalar (default if missing) |
VarLenFeature(dtype) | Lists of different lengths (e.g. tags) | A SparseTensor |
RaggedFeature(dtype) | Lists of different lengths | A RaggedTensor |
Images and Whole Tensors
Store images as their encoded PNG/JPEG bytes (small) and decode while reading. For any other tensor, tf.io.serialize_tensor turns it into bytes.
img = tf.cast(tf.random.uniform([16, 16, 3], 0, 255, seed=1), tf.uint8)
emb = tf.random.normal([4, 5], seed=2)
ex = tf.train.Example(features=tf.train.Features(feature={
"image": bytes_feature([tf.io.encode_png(img).numpy()]),
"embedding": bytes_feature([tf.io.serialize_tensor(emb).numpy()]),
}))
spec = {"image": tf.io.FixedLenFeature([], tf.string),
"embedding": tf.io.FixedLenFeature([], tf.string)}
back = tf.io.parse_single_example(ex.SerializeToString(), spec)
img2 = tf.io.decode_png(back["image"])
emb2 = tf.io.parse_tensor(back["embedding"], out_type=tf.float32)
print("image :", img2.shape, "same?", bool(tf.reduce_all(img == img2)))
print("tensor :", emb2.shape, "same?", bool(tf.reduce_all(emb == emb2)))
Many Files: Sharding
# write: train-00000-of-00016.tfrecord ... train-00015-of-00016.tfrecord
files = tf.data.Dataset.list_files("gs://my-bucket/train-*.tfrecord", shuffle=True)
ds = files.interleave(tf.data.TFRecordDataset, # read several files at once
cycle_length=8, num_parallel_calls=tf.data.AUTOTUNE)
When Should I Use TFRecord?
Golden Rules
tf.train.Example.parse_example for speed; read many shards with interleave.serialize_tensor.