2.3 从文件读取:TFRecord 与图片目录


文档摘要

2.3 从文件读取:TFRecord 与图片目录 本节摘要:数据规模超出内存后,管道的第一环从"切数组"变成"读文件"。本节讲两条主路:TFRecord——TensorFlow 的二进制记录格式,顺序读快、支持并行分片、自带序列化协议;以及图片目录流——按目录结构组织、列出文件路径、读字节、解码四步。重点是 TFRecord 的写入与特征解析全流程,这是大规模训练数据的事实标准,也是分布式读取(5.2 节)的前提。 读完你应当能做到 阅读完本节,你应当能够: 用 tf.io.TFRecordWriter 写入带浮点与整型特征的记录,并解释 Example 协议的结构; 编写特征描述字典与 parse 函数,从 TFRecord 流还原出张量;

2.3 从文件读取:TFRecord 与图片目录

本节摘要:数据规模超出内存后,管道的第一环从"切数组"变成"读文件"。本节讲两条主路:TFRecord——TensorFlow 的二进制记录格式,顺序读快、支持并行分片、自带序列化协议;以及图片目录流——按目录结构组织、列出文件路径、读字节、解码四步。重点是 TFRecord 的写入与特征解析全流程,这是大规模训练数据的事实标准,也是分布式读取(5.2 节)的前提。

读完你应当能做到

阅读完本节,你应当能够:

  1. 用 tf.io.TFRecordWriter 写入带浮点与整型特征的记录,并解释 Example 协议的结构;
  2. 编写特征描述字典与 parse 函数,从 TFRecord 流还原出张量;
  3. 搭一条图片目录读取管道:列路径、标签对齐、读字节、解码、缩放;
  4. 解释为什么大规模训练首选 TFRecord 而非散装图片或 CSV。

TFRecord:给每个样本办一张标准装箱单

散装数据(一图一文件、一行一 CSV)在大规模训练下问题很多:小文件读放大严重、随机寻道拖垮吞吐、解析逻辑散在 Python 层。TFRecord 的思路是把每个样本序列化成一个二进制记录,多条记录首尾相接写进分片文件,读取时顺序扫、按分片并行。记录内容用 Example 协议描述:一个特征字典,每个特征要么是字节串列表、要么是浮点列表、要么是整型列表。

import tensorflow as tf import numpy as np # 造 5 个合成样本:8 维特征 + 1 个标签 X = np.random.rand(5, 8).astype("float32") y = np.random.rand(5).astype("float32") def to_example(vec, label): # 特征字典 → Example 协议消息 feature = { "features": tf.train.Feature(float_list=tf.train.FloatList(value=vec.tolist())), "label": tf.train.Feature(float_list=tf.train.FloatList(value=[label])), } return tf.train.Example(features=tf.train.Features(feature=feature)) with tf.io.TFRecordWriter("housing.tfrecord") as writer: for vec, label in zip(X, y): writer.write(to_example(vec, label).SerializeToString()) print("written 5 records") # 输出:written 5 records # 磁盘上出现一个二进制文件,人眼不可读,机器顺序可扫

写入端的三个关键调用:tf.train.Feature 包单个特征,FloatList、BytesList、Int64List 三选一;tf.train.Example 是记录容器;SerializeToString 把消息压成字节串落盘。

图 9 TFRecord 从写入到解析的双程

图 9 TFRecord 从写入到解析的双程

解析端:特征描述是双向契约

读回记录必须告诉解析器每个特征的类型与形状——这份声明(特征描述字典)就是写读两端的契约,写端装箱的键名与类型必须与它一致:

feature_desc = { "features": tf.io.FixedLenFeature([8], tf.float32), "label": tf.io.FixedLenFeature([1], tf.float32), } def parse(serialized): parsed = tf.io.parse_single_example(serialized, feature_desc) return parsed["features"], parsed["label"] raw_ds = tf.data.TFRecordDataset("housing.tfrecord") parsed = raw_ds.map(parse).batch(2) for xb, yb in parsed.take(1): print(xb.shape, yb.shape) # 输出:(2, 8) (2, 1) # 字节串按契约还原成规格张量,后续管道与内存数据完全同构

解析发生在 map 里是刻意的:parse 是纯张量运算,能被 2.5 节的并行 map 调度到多个线程,这是散装解析做不到的。变长特征用 VarLenFeature,读到的是稀疏张量,再 dense 转稠密——处理文本词序列时常见。

图片目录流:路径即标签的四步法

文件夹分类的图片集(train 下每类一个子目录)不走 TFRecord 也能高效读取,思路是"路径本身当数据":先在 Python 层列出路径与标签做成数据集,再在 map 里读文件、解码、整形。

# 四步法:列路径 → 配标签 → 读字节 → 解码整形 # 假设数据已按 每类一个子目录 组织(如 cifar 二分类子集) import pathlib data_dir = pathlib.Path("cifar2/train") class_names = sorted(p.name for p in data_dir.iterdir() if p.is_dir()) print(class_names) # 输出示例:['airplane', 'bird'] # 第一步与第二步:路径与标签配对 all_paths = [str(p) for p in data_dir.glob("*/*.png")] labels = [class_names.index(pathlib.Path(p).parent.name) for p in all_paths] path_ds = tf.data.Dataset.from_tensor_slices(all_paths) label_ds = tf.data.Dataset.from_tensor_slices(labels) def load_image(path): raw = tf.io.read_file(path) # 第三步:读字节串 img = tf.image.decode_png(raw, channels=3) # 第四步:解码为张量 img = tf.image.resize(img, [32, 32]) img = tf.cast(img, tf.float32) / 255.0 # 归一到 0 到 1 return img img_ds = path_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE) ds = tf.data.Dataset.zip((img_ds, label_ds)).shuffle(2000).batch(64).prefetch(tf.data.AUTOTUNE) for xb, yb in ds.take(1): print(xb.shape, yb.dtype) # 输出:(64, 32, 32, 3) <dtype: int32>

路径列表转数据集发生在内存里、代价极小;昂贵的 IO 与解码被压进 map,由并行调度消化。文件名与路径只作为数据在程序内部流转,这套写法不依赖任何特定机器布局,换个数据目录改一个变量即可。

选型:什么时候坚持转 TFRecord

经验分界线在"百万级样本"与"反复训练多轮":数据少(几十万条以内)且原始文件可直读,图片目录流足够;数据上百万条、要反复迭代很多 epoch、或多机分布式训练,TFRecord 的收益显著——顺序读吞吐高、分片天然支持并行、解析可全并行。折中方案是先用目录流开发调通,确认数据管线定型后再一次性转成 TFRecord 沉淀。

⚠️ 常见坑:特征描述字典与写入端不一致是最常见故障——写端存的是长度 8 的浮点列表,读端声明了 FixedLenFeature([10]),解析直接报错。契约两端写在一个函数或一个模块常量里,别让它们隔着几百行代码。

💡 关键直觉:TFRecord 的价值不在格式本身,而在"把解析从 Python 搬进管道"——凡能搬进 map 的活,都能被并行调度;搬不进去的活,才是真正的瓶颈。

本节要点回顾

  • Example 协议:特征字典三种列表装箱,SerializeToString 落盘。
  • 特征描述是契约:FixedLenFeature 声明类型与形状,写读两端必须一致。
  • 解析放 map 里:纯张量运算才能被并行调度,这是格式优势的来源。
  • 图片四步法:列路径、配标签、读字节、解码整形,昂贵的活全压进 map。
  • 规模分界:百万级或反复多轮,转 TFRecord;否则目录流即可。

下节把"加工"这一环做厚:预处理与增强都搬进管道。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U