直接用tf.train.Example写入海量小图易OOM,因Python层反复分配内存;字段名须与解析时严格一致;TFRecord不支持并发写入;decode_image返回动态shape需显式reshape或resize。

为什么直接用 tf.train.Example 写入海量小图会卡死或OOM?
因为每张图读取后调用 tf.io.encode_jpeg() 或 cv2.imencode() 生成 bytes,再塞进 tf.train.Feature(bytes_list=tf.train.BytesList(value=[...])),整个流程在 Python 层反复分配内存。10 万张 50KB 的图,光原始像素数据就占 5GB,加上编码中间对象、Python GC 延迟,很容易触发内存抖动甚至被系统 kill。
实操建议:
- 用
cv2.imread(path, cv2.IMREAD_UNCHANGED)替代PIL.Image.open(),减少 PIL 内部缓存开销 - 读图后立刻
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)(如需),然后立即del img,别等 GC - 批量控制:每次只处理 500–1000 张图就 flush 一次
tf.io.TFRecordWriter,避免 writer 缓冲区堆积 - 禁用
tf.data流式写入——它不适用于写 TFRecord,仅用于读取
如何让 tf.train.Example 的字段名与后续 tf.io.parse_single_example 对齐?
字段名不一致是训练时 KeyError 的最常见原因。比如写入用了 'image/encoded',但解析时写了 'image',就会报 Failed to find any matching files(实际是 key 找不到,TF 却误导性报错)。
实操建议:
- 统一用带命名空间的 key:
'image/encoded'、'image/format'、'image/height'、'image/width'、'image/class/label' -
'image/format'必须是b'jpg'或b'png'字节串,不能是字符串'jpg';否则tf.io.decode_image()会静默失败 - 写入前加校验:
assert isinstance(encoded_bytes, bytes),避免传入None或str - 示例片段:
example = tf.train.Example(features=tf.train.Features(feature={ 'image/encoded': tf.train.Feature(bytes_list=tf.train.BytesList(value=[encoded_bytes])), 'image/format': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'jpg'])), 'image/class/label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])) })) writer.write(example.SerializeToString())怎样避免多进程打包时文件损坏或并发写入冲突?
TFRecord 不支持并发写入。用
multiprocessing.Pool直接往同一个tf.io.TFRecordWriter写,会导致文件头损坏,后续tf.data.TFRecordDataset读取时抛DataLossError: corrupted record at XXX。实操建议:
- 每个进程单独打开一个输出文件,例如
train_0001.tfrecord、train_0002.tfrecord,最后用 shell 合并:cat *.tfrecord > full.tfrecord(注意:仅当所有文件都是完整 record 序列时才安全) - 更稳妥的做法:主进程分发路径列表,子进程返回
(shard_id, list_of_examples_bytes),主进程按序合并写入单个文件 - 禁用
threading写 TFRecord——GIL 也救不了,底层 write() 是原子系统调用,但 TFRecord 格式本身无锁 - 写完务必调用
writer.close(),否则末尾 record 可能截断
为什么用
tf.io.decode_image()解析出来的 shape 是(None, None, 3)?因为
decode_image默认不校验尺寸,且对 JPEG/PNG 元信息不做预读。训练时若依赖固定 shape(如model.fit()),会报Input to reshape is a tensor with X values, but the requested shape has Y。实操建议:
- 写入时存入
'image/height'和'image/width',解析后用tf.reshape(decoded, [height, width, 3]) - 或改用
tf.io.decode_jpeg(..., channels=3)+tf.ensure_shape(img, [h, w, 3]),显式声明 shape - 若图尺寸差异大(如 32×32 到 512×512),不要强 reshape,应在解析后统一 resize:
tf.image.resize(img, [224, 224]) - 注意:
decode_png默认带 alpha 通道,channels=3会丢弃 alpha;需先tf.image.convert_image_dtype再裁切
真正麻烦的不是怎么写进去,而是写完之后发现 label 错位、尺寸不一致、或者某批图莫名全黑——这些问题往往源于写入时没校验单张图的 decode 结果,或者把不同来源的图片混进了同一 shard。建议在写入循环里加一行
assert len(cv2.imdecode(buf, -1)) > 0,省得跑完一晚上才发现 20% 的样本是空的。 - 每个进程单独打开一个输出文件,例如
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











